@velajs/vela 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/application.d.ts +44 -0
- package/dist/application.js +112 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.js +43 -0
- package/dist/container/container.d.ts +25 -0
- package/dist/container/container.js +195 -0
- package/dist/container/decorators.d.ts +8 -0
- package/dist/container/decorators.js +36 -0
- package/dist/container/index.d.ts +4 -0
- package/dist/container/index.js +3 -0
- package/dist/container/types.d.ts +37 -0
- package/dist/container/types.js +11 -0
- package/dist/errors/http-exception.d.ts +61 -0
- package/dist/errors/http-exception.js +128 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/factory.d.ts +5 -0
- package/dist/factory.js +39 -0
- package/dist/http/decorators.d.ts +122 -0
- package/dist/http/decorators.js +276 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.js +2 -0
- package/dist/http/route.manager.d.ts +34 -0
- package/dist/http/route.manager.js +373 -0
- package/dist/http/types.d.ts +29 -0
- package/dist/http/types.js +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +26 -0
- package/dist/lifecycle/index.d.ts +20 -0
- package/dist/lifecycle/index.js +15 -0
- package/dist/metadata.d.ts +10 -0
- package/dist/metadata.js +29 -0
- package/dist/module/decorators.d.ts +5 -0
- package/dist/module/decorators.js +32 -0
- package/dist/module/index.d.ts +3 -0
- package/dist/module/index.js +2 -0
- package/dist/module/module-loader.d.ts +20 -0
- package/dist/module/module-loader.js +159 -0
- package/dist/module/types.d.ts +19 -0
- package/dist/module/types.js +1 -0
- package/dist/pipeline/component.manager.d.ts +18 -0
- package/dist/pipeline/component.manager.js +105 -0
- package/dist/pipeline/decorators.d.ts +10 -0
- package/dist/pipeline/decorators.js +50 -0
- package/dist/pipeline/index.d.ts +7 -0
- package/dist/pipeline/index.js +5 -0
- package/dist/pipeline/pipes.d.ts +25 -0
- package/dist/pipeline/pipes.js +52 -0
- package/dist/pipeline/reflector.d.ts +102 -0
- package/dist/pipeline/reflector.js +166 -0
- package/dist/pipeline/tokens.d.ts +33 -0
- package/dist/pipeline/tokens.js +27 -0
- package/dist/pipeline/types.d.ts +31 -0
- package/dist/pipeline/types.js +1 -0
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.js +1 -0
- package/dist/registry/metadata.registry.d.ts +61 -0
- package/dist/registry/metadata.registry.js +276 -0
- package/dist/registry/types.d.ts +55 -0
- package/dist/registry/types.js +2 -0
- package/package.json +72 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export declare class HttpException extends Error {
|
|
2
|
+
readonly statusCode: number;
|
|
3
|
+
readonly response?: unknown | undefined;
|
|
4
|
+
constructor(message: string, statusCode: number, response?: unknown | undefined);
|
|
5
|
+
getStatus(): number;
|
|
6
|
+
getResponse(): unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare class BadRequestException extends HttpException {
|
|
9
|
+
constructor(message?: string, response?: unknown);
|
|
10
|
+
}
|
|
11
|
+
export declare class UnauthorizedException extends HttpException {
|
|
12
|
+
constructor(message?: string, response?: unknown);
|
|
13
|
+
}
|
|
14
|
+
export declare class ForbiddenException extends HttpException {
|
|
15
|
+
constructor(message?: string, response?: unknown);
|
|
16
|
+
}
|
|
17
|
+
export declare class NotFoundException extends HttpException {
|
|
18
|
+
constructor(message?: string, response?: unknown);
|
|
19
|
+
}
|
|
20
|
+
export declare class MethodNotAllowedException extends HttpException {
|
|
21
|
+
constructor(message?: string, response?: unknown);
|
|
22
|
+
}
|
|
23
|
+
export declare class NotAcceptableException extends HttpException {
|
|
24
|
+
constructor(message?: string, response?: unknown);
|
|
25
|
+
}
|
|
26
|
+
export declare class RequestTimeoutException extends HttpException {
|
|
27
|
+
constructor(message?: string, response?: unknown);
|
|
28
|
+
}
|
|
29
|
+
export declare class ConflictException extends HttpException {
|
|
30
|
+
constructor(message?: string, response?: unknown);
|
|
31
|
+
}
|
|
32
|
+
export declare class GoneException extends HttpException {
|
|
33
|
+
constructor(message?: string, response?: unknown);
|
|
34
|
+
}
|
|
35
|
+
export declare class PayloadTooLargeException extends HttpException {
|
|
36
|
+
constructor(message?: string, response?: unknown);
|
|
37
|
+
}
|
|
38
|
+
export declare class UnsupportedMediaTypeException extends HttpException {
|
|
39
|
+
constructor(message?: string, response?: unknown);
|
|
40
|
+
}
|
|
41
|
+
export declare class UnprocessableEntityException extends HttpException {
|
|
42
|
+
constructor(message?: string, response?: unknown);
|
|
43
|
+
}
|
|
44
|
+
export declare class TooManyRequestsException extends HttpException {
|
|
45
|
+
constructor(message?: string, response?: unknown);
|
|
46
|
+
}
|
|
47
|
+
export declare class InternalServerErrorException extends HttpException {
|
|
48
|
+
constructor(message?: string, response?: unknown);
|
|
49
|
+
}
|
|
50
|
+
export declare class NotImplementedException extends HttpException {
|
|
51
|
+
constructor(message?: string, response?: unknown);
|
|
52
|
+
}
|
|
53
|
+
export declare class BadGatewayException extends HttpException {
|
|
54
|
+
constructor(message?: string, response?: unknown);
|
|
55
|
+
}
|
|
56
|
+
export declare class ServiceUnavailableException extends HttpException {
|
|
57
|
+
constructor(message?: string, response?: unknown);
|
|
58
|
+
}
|
|
59
|
+
export declare class GatewayTimeoutException extends HttpException {
|
|
60
|
+
constructor(message?: string, response?: unknown);
|
|
61
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export class HttpException extends Error {
|
|
2
|
+
statusCode;
|
|
3
|
+
response;
|
|
4
|
+
constructor(message, statusCode, response){
|
|
5
|
+
super(message), this.statusCode = statusCode, this.response = response;
|
|
6
|
+
this.name = 'HttpException';
|
|
7
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
8
|
+
}
|
|
9
|
+
getStatus() {
|
|
10
|
+
return this.statusCode;
|
|
11
|
+
}
|
|
12
|
+
getResponse() {
|
|
13
|
+
return this.response ?? {
|
|
14
|
+
statusCode: this.statusCode,
|
|
15
|
+
message: this.message
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// 4xx
|
|
20
|
+
export class BadRequestException extends HttpException {
|
|
21
|
+
constructor(message = 'Bad Request', response){
|
|
22
|
+
super(message, 400, response);
|
|
23
|
+
this.name = 'BadRequestException';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class UnauthorizedException extends HttpException {
|
|
27
|
+
constructor(message = 'Unauthorized', response){
|
|
28
|
+
super(message, 401, response);
|
|
29
|
+
this.name = 'UnauthorizedException';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class ForbiddenException extends HttpException {
|
|
33
|
+
constructor(message = 'Forbidden', response){
|
|
34
|
+
super(message, 403, response);
|
|
35
|
+
this.name = 'ForbiddenException';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class NotFoundException extends HttpException {
|
|
39
|
+
constructor(message = 'Not Found', response){
|
|
40
|
+
super(message, 404, response);
|
|
41
|
+
this.name = 'NotFoundException';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class MethodNotAllowedException extends HttpException {
|
|
45
|
+
constructor(message = 'Method Not Allowed', response){
|
|
46
|
+
super(message, 405, response);
|
|
47
|
+
this.name = 'MethodNotAllowedException';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class NotAcceptableException extends HttpException {
|
|
51
|
+
constructor(message = 'Not Acceptable', response){
|
|
52
|
+
super(message, 406, response);
|
|
53
|
+
this.name = 'NotAcceptableException';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class RequestTimeoutException extends HttpException {
|
|
57
|
+
constructor(message = 'Request Timeout', response){
|
|
58
|
+
super(message, 408, response);
|
|
59
|
+
this.name = 'RequestTimeoutException';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class ConflictException extends HttpException {
|
|
63
|
+
constructor(message = 'Conflict', response){
|
|
64
|
+
super(message, 409, response);
|
|
65
|
+
this.name = 'ConflictException';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export class GoneException extends HttpException {
|
|
69
|
+
constructor(message = 'Gone', response){
|
|
70
|
+
super(message, 410, response);
|
|
71
|
+
this.name = 'GoneException';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export class PayloadTooLargeException extends HttpException {
|
|
75
|
+
constructor(message = 'Payload Too Large', response){
|
|
76
|
+
super(message, 413, response);
|
|
77
|
+
this.name = 'PayloadTooLargeException';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export class UnsupportedMediaTypeException extends HttpException {
|
|
81
|
+
constructor(message = 'Unsupported Media Type', response){
|
|
82
|
+
super(message, 415, response);
|
|
83
|
+
this.name = 'UnsupportedMediaTypeException';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export class UnprocessableEntityException extends HttpException {
|
|
87
|
+
constructor(message = 'Unprocessable Entity', response){
|
|
88
|
+
super(message, 422, response);
|
|
89
|
+
this.name = 'UnprocessableEntityException';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export class TooManyRequestsException extends HttpException {
|
|
93
|
+
constructor(message = 'Too Many Requests', response){
|
|
94
|
+
super(message, 429, response);
|
|
95
|
+
this.name = 'TooManyRequestsException';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 5xx
|
|
99
|
+
export class InternalServerErrorException extends HttpException {
|
|
100
|
+
constructor(message = 'Internal Server Error', response){
|
|
101
|
+
super(message, 500, response);
|
|
102
|
+
this.name = 'InternalServerErrorException';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export class NotImplementedException extends HttpException {
|
|
106
|
+
constructor(message = 'Not Implemented', response){
|
|
107
|
+
super(message, 501, response);
|
|
108
|
+
this.name = 'NotImplementedException';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export class BadGatewayException extends HttpException {
|
|
112
|
+
constructor(message = 'Bad Gateway', response){
|
|
113
|
+
super(message, 502, response);
|
|
114
|
+
this.name = 'BadGatewayException';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export class ServiceUnavailableException extends HttpException {
|
|
118
|
+
constructor(message = 'Service Unavailable', response){
|
|
119
|
+
super(message, 503, response);
|
|
120
|
+
this.name = 'ServiceUnavailableException';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export class GatewayTimeoutException extends HttpException {
|
|
124
|
+
constructor(message = 'Gateway Timeout', response){
|
|
125
|
+
super(message, 504, response);
|
|
126
|
+
this.name = 'GatewayTimeoutException';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, GoneException, PayloadTooLargeException, UnsupportedMediaTypeException, UnprocessableEntityException, TooManyRequestsException, InternalServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, GatewayTimeoutException, } from './http-exception';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, GoneException, PayloadTooLargeException, UnsupportedMediaTypeException, UnprocessableEntityException, TooManyRequestsException, InternalServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, GatewayTimeoutException } from "./http-exception.js";
|
package/dist/factory.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { VelaApplication } from "./application.js";
|
|
2
|
+
import { Container } from "./container/container.js";
|
|
3
|
+
import { RouteManager } from "./http/route.manager.js";
|
|
4
|
+
import { ModuleLoader } from "./module/module-loader.js";
|
|
5
|
+
import { ComponentManager } from "./pipeline/component.manager.js";
|
|
6
|
+
import { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/tokens.js";
|
|
7
|
+
export const VelaFactory = {
|
|
8
|
+
async create (rootModule) {
|
|
9
|
+
const container = new Container();
|
|
10
|
+
const routeManager = new RouteManager(container);
|
|
11
|
+
ComponentManager.init(container);
|
|
12
|
+
const loader = new ModuleLoader(container, routeManager);
|
|
13
|
+
loader.load(rootModule);
|
|
14
|
+
// Resolve APP_* tokens registered via module providers
|
|
15
|
+
if (container.has(APP_GUARD)) {
|
|
16
|
+
routeManager.useGlobalGuards(container.resolve(APP_GUARD));
|
|
17
|
+
}
|
|
18
|
+
if (container.has(APP_PIPE)) {
|
|
19
|
+
routeManager.useGlobalPipes(container.resolve(APP_PIPE));
|
|
20
|
+
}
|
|
21
|
+
if (container.has(APP_INTERCEPTOR)) {
|
|
22
|
+
routeManager.useGlobalInterceptors(container.resolve(APP_INTERCEPTOR));
|
|
23
|
+
}
|
|
24
|
+
if (container.has(APP_FILTER)) {
|
|
25
|
+
routeManager.useGlobalFilters(container.resolve(APP_FILTER));
|
|
26
|
+
}
|
|
27
|
+
if (container.has(APP_MIDDLEWARE)) {
|
|
28
|
+
routeManager.useGlobalMiddleware(container.resolve(APP_MIDDLEWARE));
|
|
29
|
+
}
|
|
30
|
+
const app = new VelaApplication(container, routeManager);
|
|
31
|
+
const instances = loader.resolveAllInstances();
|
|
32
|
+
app.setInstances(instances);
|
|
33
|
+
await app.callOnModuleInit();
|
|
34
|
+
await app.callOnApplicationBootstrap();
|
|
35
|
+
// Build routes (async — supports CRUD integration with dynamic imports)
|
|
36
|
+
await app.initRoutes();
|
|
37
|
+
return app;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Constructor, PipeType } from '../registry/types';
|
|
2
|
+
import type { ControllerOptions } from './types';
|
|
3
|
+
import type { ExecutionContext } from '../pipeline/types';
|
|
4
|
+
/**
|
|
5
|
+
* Marks a class as a controller.
|
|
6
|
+
* Accepts a prefix string or an options object with prefix and version.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* @Controller('/users')
|
|
11
|
+
* @Controller({ prefix: '/users', version: 1 })
|
|
12
|
+
* @Controller({ prefix: '/users', version: [1, 2] })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function Controller(prefixOrOptions?: string | ControllerOptions): ClassDecorator;
|
|
16
|
+
/**
|
|
17
|
+
* Override the version for a specific route method.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* @Controller({ prefix: '/users', version: 1 })
|
|
22
|
+
* class UserController {
|
|
23
|
+
* @Get()
|
|
24
|
+
* listV1() { ... } // GET /v1/users
|
|
25
|
+
*
|
|
26
|
+
* @Version(2)
|
|
27
|
+
* @Get()
|
|
28
|
+
* listV2() { ... } // GET /v2/users
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function Version(version: number | number[]): MethodDecorator;
|
|
33
|
+
export declare function getRouteVersion(target: Constructor, propertyKey: string | symbol): number | number[] | undefined;
|
|
34
|
+
export declare const Get: (path?: string) => MethodDecorator;
|
|
35
|
+
export declare const Post: (path?: string) => MethodDecorator;
|
|
36
|
+
export declare const Put: (path?: string) => MethodDecorator;
|
|
37
|
+
export declare const Patch: (path?: string) => MethodDecorator;
|
|
38
|
+
export declare const Delete: (path?: string) => MethodDecorator;
|
|
39
|
+
export declare const Options: (path?: string) => MethodDecorator;
|
|
40
|
+
export declare const Head: (path?: string) => MethodDecorator;
|
|
41
|
+
export declare const Param: (nameOrPipe?: string | PipeType, ...pipes: PipeType[]) => ParameterDecorator;
|
|
42
|
+
export declare const Query: (nameOrPipe?: string | PipeType, ...pipes: PipeType[]) => ParameterDecorator;
|
|
43
|
+
export declare const Body: (nameOrPipe?: string | PipeType, ...pipes: PipeType[]) => ParameterDecorator;
|
|
44
|
+
export declare const Headers: (nameOrPipe?: string | PipeType, ...pipes: PipeType[]) => ParameterDecorator;
|
|
45
|
+
export declare const Req: (nameOrPipe?: string | PipeType, ...pipes: PipeType[]) => ParameterDecorator;
|
|
46
|
+
/**
|
|
47
|
+
* Factory for creating custom parameter decorators.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const CurrentUser = createParamDecorator(
|
|
52
|
+
* (data: unknown, ctx: ExecutionContext) => {
|
|
53
|
+
* const req = ctx.getRequest();
|
|
54
|
+
* return req.headers.get('x-user-id');
|
|
55
|
+
* }
|
|
56
|
+
* );
|
|
57
|
+
*
|
|
58
|
+
* // Usage:
|
|
59
|
+
* @Get()
|
|
60
|
+
* handle(@CurrentUser() userId: string) { ... }
|
|
61
|
+
*
|
|
62
|
+
* // With data argument:
|
|
63
|
+
* const Header = createParamDecorator(
|
|
64
|
+
* (data: string, ctx: ExecutionContext) => {
|
|
65
|
+
* return ctx.getRequest().headers.get(data);
|
|
66
|
+
* }
|
|
67
|
+
* );
|
|
68
|
+
*
|
|
69
|
+
* @Get()
|
|
70
|
+
* handle(@Header('x-request-id') requestId: string) { ... }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function createParamDecorator<TData = unknown>(factory: (data: TData, ctx: ExecutionContext) => unknown): (data?: TData, ...pipes: PipeType[]) => ParameterDecorator;
|
|
74
|
+
/**
|
|
75
|
+
* Override the HTTP status code for a handler's response.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* @Post()
|
|
80
|
+
* @HttpCode(201)
|
|
81
|
+
* create(@Body() data: CreateDto) { return data; }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function HttpCode(statusCode: number): MethodDecorator;
|
|
85
|
+
/**
|
|
86
|
+
* Set a response header declaratively.
|
|
87
|
+
* Can be applied multiple times to set multiple headers.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* @Get()
|
|
92
|
+
* @Header('Cache-Control', 'no-cache')
|
|
93
|
+
* @Header('X-Custom', 'value')
|
|
94
|
+
* handle() { return { ok: true }; }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function Header(name: string, value: string): MethodDecorator;
|
|
98
|
+
/**
|
|
99
|
+
* Redirect to another URL. The handler's return value can override the URL.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* @Get('/old')
|
|
104
|
+
* @Redirect('/new', 301)
|
|
105
|
+
* handleOld() {}
|
|
106
|
+
*
|
|
107
|
+
* // Dynamic redirect — return { url, statusCode? } to override
|
|
108
|
+
* @Get('/go')
|
|
109
|
+
* @Redirect('/default')
|
|
110
|
+
* handleGo(@Query('to') to?: string) {
|
|
111
|
+
* if (to) return { url: to };
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function Redirect(url: string, statusCode?: number): MethodDecorator;
|
|
116
|
+
export declare function getHttpCode(target: Constructor, method: string | symbol): number | undefined;
|
|
117
|
+
export declare function getResponseHeaders(target: Constructor, method: string | symbol): Array<[string, string]>;
|
|
118
|
+
export declare function getRedirect(target: Constructor, method: string | symbol): {
|
|
119
|
+
url: string;
|
|
120
|
+
statusCode: number;
|
|
121
|
+
} | undefined;
|
|
122
|
+
export declare function isController(target: Constructor): boolean;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { HttpMethod, METADATA_KEYS, ParamType, Scope } from "../constants.js";
|
|
2
|
+
import { defineMetadata, getMetadata } from "../metadata.js";
|
|
3
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
4
|
+
function normalizePath(path) {
|
|
5
|
+
return path && !path.startsWith('/') ? `/${path}` : path;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Marks a class as a controller.
|
|
9
|
+
* Accepts a prefix string or an options object with prefix and version.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* @Controller('/users')
|
|
14
|
+
* @Controller({ prefix: '/users', version: 1 })
|
|
15
|
+
* @Controller({ prefix: '/users', version: [1, 2] })
|
|
16
|
+
* ```
|
|
17
|
+
*/ export function Controller(prefixOrOptions) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
19
|
+
return (target)=>{
|
|
20
|
+
let prefix;
|
|
21
|
+
let version;
|
|
22
|
+
if (typeof prefixOrOptions === 'string') {
|
|
23
|
+
prefix = prefixOrOptions;
|
|
24
|
+
} else if (prefixOrOptions) {
|
|
25
|
+
prefix = prefixOrOptions.prefix ?? '';
|
|
26
|
+
version = prefixOrOptions.version;
|
|
27
|
+
} else {
|
|
28
|
+
prefix = '';
|
|
29
|
+
}
|
|
30
|
+
MetadataRegistry.setControllerPath(target, normalizePath(prefix));
|
|
31
|
+
if (version !== undefined) {
|
|
32
|
+
MetadataRegistry.setControllerOptions(target, {
|
|
33
|
+
version
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
MetadataRegistry.markInjectable(target);
|
|
37
|
+
MetadataRegistry.setScope(target, Scope.SINGLETON);
|
|
38
|
+
// Keep WeakMap write for external package compat
|
|
39
|
+
defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
40
|
+
defineMetadata(METADATA_KEYS.SCOPE, Scope.SINGLETON, target);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Override the version for a specific route method.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* @Controller({ prefix: '/users', version: 1 })
|
|
49
|
+
* class UserController {
|
|
50
|
+
* @Get()
|
|
51
|
+
* listV1() { ... } // GET /v1/users
|
|
52
|
+
*
|
|
53
|
+
* @Version(2)
|
|
54
|
+
* @Get()
|
|
55
|
+
* listV2() { ... } // GET /v2/users
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/ export function Version(version) {
|
|
59
|
+
return (target, propertyKey, _descriptor)=>{
|
|
60
|
+
MetadataRegistry.setRouteVersion(target.constructor, propertyKey, version);
|
|
61
|
+
// Keep WeakMap write for compat
|
|
62
|
+
const versionKey = `vela:route-version:${String(propertyKey)}`;
|
|
63
|
+
defineMetadata(versionKey, version, target.constructor);
|
|
64
|
+
// If route was already registered (decorator ran after @Get), patch it
|
|
65
|
+
const routes = MetadataRegistry.getRoutes(target.constructor);
|
|
66
|
+
const route = routes.find((r)=>r.handlerName === propertyKey);
|
|
67
|
+
if (route) {
|
|
68
|
+
route.version = version;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function getRouteVersion(target, propertyKey) {
|
|
73
|
+
return MetadataRegistry.getRouteVersion(target, propertyKey) ?? getMetadata(`vela:route-version:${String(propertyKey)}`, target);
|
|
74
|
+
}
|
|
75
|
+
function createMethodDecorator(method) {
|
|
76
|
+
return (path = '')=>{
|
|
77
|
+
return (target, propertyKey, _descriptor)=>{
|
|
78
|
+
const normalizedPath = normalizePath(path);
|
|
79
|
+
// Check for @Version metadata on this method
|
|
80
|
+
const version = MetadataRegistry.getRouteVersion(target.constructor, propertyKey) ?? getMetadata(`vela:route-version:${String(propertyKey)}`, target.constructor);
|
|
81
|
+
MetadataRegistry.addRoute(target.constructor, {
|
|
82
|
+
method: method,
|
|
83
|
+
path: normalizedPath,
|
|
84
|
+
handlerName: propertyKey,
|
|
85
|
+
...version !== undefined ? {
|
|
86
|
+
version
|
|
87
|
+
} : {}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export const Get = createMethodDecorator(HttpMethod.GET);
|
|
93
|
+
export const Post = createMethodDecorator(HttpMethod.POST);
|
|
94
|
+
export const Put = createMethodDecorator(HttpMethod.PUT);
|
|
95
|
+
export const Patch = createMethodDecorator(HttpMethod.PATCH);
|
|
96
|
+
export const Delete = createMethodDecorator(HttpMethod.DELETE);
|
|
97
|
+
export const Options = createMethodDecorator(HttpMethod.OPTIONS);
|
|
98
|
+
export const Head = createMethodDecorator(HttpMethod.HEAD);
|
|
99
|
+
// Parameter decorators
|
|
100
|
+
const CUSTOM_PARAM_TYPE = 'custom';
|
|
101
|
+
function createBuiltinParamDecorator(type) {
|
|
102
|
+
return (nameOrPipe, ...pipes)=>{
|
|
103
|
+
return (target, propertyKey, parameterIndex)=>{
|
|
104
|
+
if (propertyKey === undefined) {
|
|
105
|
+
throw new Error('Parameter decorators can only be used on method parameters');
|
|
106
|
+
}
|
|
107
|
+
let name;
|
|
108
|
+
let allPipes;
|
|
109
|
+
if (typeof nameOrPipe === 'string') {
|
|
110
|
+
name = nameOrPipe;
|
|
111
|
+
allPipes = pipes;
|
|
112
|
+
} else if (nameOrPipe !== undefined) {
|
|
113
|
+
name = undefined;
|
|
114
|
+
allPipes = [
|
|
115
|
+
nameOrPipe,
|
|
116
|
+
...pipes
|
|
117
|
+
];
|
|
118
|
+
} else {
|
|
119
|
+
name = undefined;
|
|
120
|
+
allPipes = pipes;
|
|
121
|
+
}
|
|
122
|
+
MetadataRegistry.addParameter(target.constructor, propertyKey, {
|
|
123
|
+
index: parameterIndex,
|
|
124
|
+
type,
|
|
125
|
+
name,
|
|
126
|
+
...allPipes.length > 0 ? {
|
|
127
|
+
pipes: allPipes
|
|
128
|
+
} : {}
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export const Param = createBuiltinParamDecorator(ParamType.PARAM);
|
|
134
|
+
export const Query = createBuiltinParamDecorator(ParamType.QUERY);
|
|
135
|
+
export const Body = createBuiltinParamDecorator(ParamType.BODY);
|
|
136
|
+
export const Headers = createBuiltinParamDecorator(ParamType.HEADERS);
|
|
137
|
+
export const Req = createBuiltinParamDecorator(ParamType.REQUEST);
|
|
138
|
+
/**
|
|
139
|
+
* Factory for creating custom parameter decorators.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const CurrentUser = createParamDecorator(
|
|
144
|
+
* (data: unknown, ctx: ExecutionContext) => {
|
|
145
|
+
* const req = ctx.getRequest();
|
|
146
|
+
* return req.headers.get('x-user-id');
|
|
147
|
+
* }
|
|
148
|
+
* );
|
|
149
|
+
*
|
|
150
|
+
* // Usage:
|
|
151
|
+
* @Get()
|
|
152
|
+
* handle(@CurrentUser() userId: string) { ... }
|
|
153
|
+
*
|
|
154
|
+
* // With data argument:
|
|
155
|
+
* const Header = createParamDecorator(
|
|
156
|
+
* (data: string, ctx: ExecutionContext) => {
|
|
157
|
+
* return ctx.getRequest().headers.get(data);
|
|
158
|
+
* }
|
|
159
|
+
* );
|
|
160
|
+
*
|
|
161
|
+
* @Get()
|
|
162
|
+
* handle(@Header('x-request-id') requestId: string) { ... }
|
|
163
|
+
* ```
|
|
164
|
+
*/ export function createParamDecorator(factory) {
|
|
165
|
+
return (data, ...pipes)=>{
|
|
166
|
+
return (target, propertyKey, parameterIndex)=>{
|
|
167
|
+
if (propertyKey === undefined) {
|
|
168
|
+
throw new Error('Parameter decorators can only be used on method parameters');
|
|
169
|
+
}
|
|
170
|
+
MetadataRegistry.addParameter(target.constructor, propertyKey, {
|
|
171
|
+
index: parameterIndex,
|
|
172
|
+
type: CUSTOM_PARAM_TYPE,
|
|
173
|
+
name: undefined,
|
|
174
|
+
factory: (_unused, ctx)=>{
|
|
175
|
+
const honoCtx = ctx;
|
|
176
|
+
const execCtx = {
|
|
177
|
+
getClass: ()=>target.constructor,
|
|
178
|
+
getHandler: ()=>propertyKey,
|
|
179
|
+
getContext: ()=>honoCtx,
|
|
180
|
+
getRequest: ()=>honoCtx.req.raw
|
|
181
|
+
};
|
|
182
|
+
return factory(data, execCtx);
|
|
183
|
+
},
|
|
184
|
+
...pipes.length > 0 ? {
|
|
185
|
+
pipes
|
|
186
|
+
} : {}
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Response decorators
|
|
192
|
+
/**
|
|
193
|
+
* Override the HTTP status code for a handler's response.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* @Post()
|
|
198
|
+
* @HttpCode(201)
|
|
199
|
+
* create(@Body() data: CreateDto) { return data; }
|
|
200
|
+
* ```
|
|
201
|
+
*/ export function HttpCode(statusCode) {
|
|
202
|
+
return (target, propertyKey, _descriptor)=>{
|
|
203
|
+
MetadataRegistry.setHandlerHttpMeta(target.constructor, propertyKey, {
|
|
204
|
+
httpCode: statusCode
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Set a response header declaratively.
|
|
210
|
+
* Can be applied multiple times to set multiple headers.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* @Get()
|
|
215
|
+
* @Header('Cache-Control', 'no-cache')
|
|
216
|
+
* @Header('X-Custom', 'value')
|
|
217
|
+
* handle() { return { ok: true }; }
|
|
218
|
+
* ```
|
|
219
|
+
*/ export function Header(name, value) {
|
|
220
|
+
return (target, propertyKey, _descriptor)=>{
|
|
221
|
+
MetadataRegistry.setHandlerHttpMeta(target.constructor, propertyKey, {
|
|
222
|
+
responseHeaders: [
|
|
223
|
+
[
|
|
224
|
+
name,
|
|
225
|
+
value
|
|
226
|
+
]
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Redirect to another URL. The handler's return value can override the URL.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* @Get('/old')
|
|
237
|
+
* @Redirect('/new', 301)
|
|
238
|
+
* handleOld() {}
|
|
239
|
+
*
|
|
240
|
+
* // Dynamic redirect — return { url, statusCode? } to override
|
|
241
|
+
* @Get('/go')
|
|
242
|
+
* @Redirect('/default')
|
|
243
|
+
* handleGo(@Query('to') to?: string) {
|
|
244
|
+
* if (to) return { url: to };
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*/ export function Redirect(url, statusCode = 302) {
|
|
248
|
+
return (target, propertyKey, _descriptor)=>{
|
|
249
|
+
MetadataRegistry.setHandlerHttpMeta(target.constructor, propertyKey, {
|
|
250
|
+
redirect: {
|
|
251
|
+
url,
|
|
252
|
+
statusCode
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// Metadata readers (used by RouteManager)
|
|
258
|
+
export function getHttpCode(target, method) {
|
|
259
|
+
const meta = MetadataRegistry.getHandlerHttpMeta(target, method);
|
|
260
|
+
if (meta?.httpCode !== undefined) return meta.httpCode;
|
|
261
|
+
return getMetadata(METADATA_KEYS.HTTP_CODE, target, method);
|
|
262
|
+
}
|
|
263
|
+
export function getResponseHeaders(target, method) {
|
|
264
|
+
const meta = MetadataRegistry.getHandlerHttpMeta(target, method);
|
|
265
|
+
if (meta?.responseHeaders) return meta.responseHeaders;
|
|
266
|
+
return getMetadata(METADATA_KEYS.RESPONSE_HEADERS, target, method) ?? [];
|
|
267
|
+
}
|
|
268
|
+
export function getRedirect(target, method) {
|
|
269
|
+
const meta = MetadataRegistry.getHandlerHttpMeta(target, method);
|
|
270
|
+
if (meta?.redirect) return meta.redirect;
|
|
271
|
+
return getMetadata(METADATA_KEYS.REDIRECT, target, method);
|
|
272
|
+
}
|
|
273
|
+
// Helpers
|
|
274
|
+
export function isController(target) {
|
|
275
|
+
return MetadataRegistry.getControllerPath(target) !== '' || MetadataRegistry.getRoutes(target).length > 0;
|
|
276
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { RouteManager } from './route.manager';
|
|
2
|
+
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, Param, Query, Body, Headers, Req, HttpCode, Header, Redirect, createParamDecorator, isController, } from './decorators';
|
|
3
|
+
export type { RouteMetadata, ControllerMetadata, ControllerOptions, ParamMetadata, ControllerRegistration, } from './types';
|