@carno.js/core 1.1.1 → 1.1.2
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/LICENSE +21 -21
- package/README.md +188 -188
- package/dist/Carno.js +46 -26
- package/dist/Carno.mjs +46 -26
- package/dist/bun/index.js +4 -4
- package/dist/bun/index.js.map +29 -29
- package/package.json +2 -2
- package/src/Carno.ts +718 -673
- package/src/DefaultRoutes.ts +34 -34
- package/src/cache/CacheDriver.ts +50 -50
- package/src/cache/CacheService.ts +139 -139
- package/src/cache/MemoryDriver.ts +104 -104
- package/src/cache/RedisDriver.ts +116 -116
- package/src/compiler/JITCompiler.ts +167 -167
- package/src/container/Container.ts +168 -168
- package/src/context/Context.ts +130 -130
- package/src/cors/CorsHandler.ts +145 -145
- package/src/decorators/Controller.ts +63 -63
- package/src/decorators/Inject.ts +16 -16
- package/src/decorators/Middleware.ts +22 -22
- package/src/decorators/Service.ts +18 -18
- package/src/decorators/methods.ts +58 -58
- package/src/decorators/params.ts +47 -47
- package/src/events/Lifecycle.ts +97 -97
- package/src/exceptions/HttpException.ts +99 -99
- package/src/index.ts +95 -95
- package/src/metadata.ts +46 -46
- package/src/middleware/CarnoMiddleware.ts +14 -14
- package/src/router/RadixRouter.ts +225 -225
- package/src/testing/TestHarness.ts +185 -185
- package/src/utils/Metadata.ts +43 -43
- package/src/utils/parseQuery.ts +161 -161
- package/src/validation/ValibotAdapter.ts +95 -95
- package/src/validation/ValidatorAdapter.ts +69 -69
- package/src/validation/ZodAdapter.ts +102 -102
- package/dist/Carno.d.js +0 -14
- package/dist/Carno.d.mjs +0 -1
- package/dist/DefaultRoutes.d.js +0 -13
- package/dist/DefaultRoutes.d.mjs +0 -0
- package/dist/cache/CacheDriver.d.js +0 -13
- package/dist/cache/CacheDriver.d.mjs +0 -0
- package/dist/cache/CacheService.d.js +0 -13
- package/dist/cache/CacheService.d.mjs +0 -0
- package/dist/cache/MemoryDriver.d.js +0 -13
- package/dist/cache/MemoryDriver.d.mjs +0 -0
- package/dist/cache/RedisDriver.d.js +0 -13
- package/dist/cache/RedisDriver.d.mjs +0 -0
- package/dist/compiler/JITCompiler.d.js +0 -13
- package/dist/compiler/JITCompiler.d.mjs +0 -0
- package/dist/container/Container.d.js +0 -13
- package/dist/container/Container.d.mjs +0 -0
- package/dist/context/Context.d.js +0 -13
- package/dist/context/Context.d.mjs +0 -0
- package/dist/cors/CorsHandler.d.js +0 -13
- package/dist/cors/CorsHandler.d.mjs +0 -0
- package/dist/decorators/Controller.d.js +0 -13
- package/dist/decorators/Controller.d.mjs +0 -0
- package/dist/decorators/Inject.d.js +0 -13
- package/dist/decorators/Inject.d.mjs +0 -0
- package/dist/decorators/Middleware.d.js +0 -13
- package/dist/decorators/Middleware.d.mjs +0 -0
- package/dist/decorators/Service.d.js +0 -13
- package/dist/decorators/Service.d.mjs +0 -0
- package/dist/decorators/methods.d.js +0 -13
- package/dist/decorators/methods.d.mjs +0 -0
- package/dist/decorators/params.d.js +0 -13
- package/dist/decorators/params.d.mjs +0 -0
- package/dist/events/Lifecycle.d.js +0 -13
- package/dist/events/Lifecycle.d.mjs +0 -0
- package/dist/exceptions/HttpException.d.js +0 -13
- package/dist/exceptions/HttpException.d.mjs +0 -0
- package/dist/index.d.js +0 -130
- package/dist/index.d.mjs +0 -78
- package/dist/metadata.d.js +0 -13
- package/dist/metadata.d.mjs +0 -0
- package/dist/middleware/CarnoMiddleware.d.js +0 -13
- package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
- package/dist/router/RadixRouter.d.js +0 -13
- package/dist/router/RadixRouter.d.mjs +0 -0
- package/dist/testing/TestHarness.d.js +0 -13
- package/dist/testing/TestHarness.d.mjs +0 -0
- package/dist/utils/Metadata.d.js +0 -13
- package/dist/utils/Metadata.d.mjs +0 -0
- package/dist/utils/parseQuery.d.js +0 -13
- package/dist/utils/parseQuery.d.mjs +0 -0
- package/dist/validation/ValibotAdapter.d.js +0 -13
- package/dist/validation/ValibotAdapter.d.mjs +0 -0
- package/dist/validation/ValidatorAdapter.d.js +0 -13
- package/dist/validation/ValidatorAdapter.d.mjs +0 -0
- package/dist/validation/ZodAdapter.d.js +0 -13
- package/dist/validation/ZodAdapter.d.mjs +0 -0
- package/src/Carno.d.ts +0 -135
- package/src/DefaultRoutes.d.ts +0 -19
- package/src/cache/CacheDriver.d.ts +0 -43
- package/src/cache/CacheService.d.ts +0 -89
- package/src/cache/MemoryDriver.d.ts +0 -32
- package/src/cache/RedisDriver.d.ts +0 -34
- package/src/compiler/JITCompiler.d.ts +0 -36
- package/src/container/Container.d.ts +0 -38
- package/src/context/Context.d.ts +0 -36
- package/src/cors/CorsHandler.d.ts +0 -47
- package/src/decorators/Controller.d.ts +0 -13
- package/src/decorators/Inject.d.ts +0 -6
- package/src/decorators/Middleware.d.ts +0 -5
- package/src/decorators/Service.d.ts +0 -9
- package/src/decorators/methods.d.ts +0 -7
- package/src/decorators/params.d.ts +0 -13
- package/src/events/Lifecycle.d.ts +0 -54
- package/src/exceptions/HttpException.d.ts +0 -43
- package/src/index.d.ts +0 -42
- package/src/metadata.d.ts +0 -41
- package/src/middleware/CarnoMiddleware.d.ts +0 -12
- package/src/router/RadixRouter.d.ts +0 -19
- package/src/testing/TestHarness.d.ts +0 -71
- package/src/utils/Metadata.d.ts +0 -20
- package/src/utils/parseQuery.d.ts +0 -23
- package/src/validation/ValibotAdapter.d.ts +0 -30
- package/src/validation/ValidatorAdapter.d.ts +0 -54
- package/src/validation/ZodAdapter.d.ts +0 -35
package/src/cors/CorsHandler.ts
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CORS Configuration types.
|
|
3
|
-
*/
|
|
4
|
-
export type CorsOrigin =
|
|
5
|
-
| string
|
|
6
|
-
| string[]
|
|
7
|
-
| RegExp
|
|
8
|
-
| ((origin: string) => boolean);
|
|
9
|
-
|
|
10
|
-
export interface CorsConfig {
|
|
11
|
-
origins: CorsOrigin;
|
|
12
|
-
methods?: string[];
|
|
13
|
-
allowedHeaders?: string[];
|
|
14
|
-
exposedHeaders?: string[];
|
|
15
|
-
credentials?: boolean;
|
|
16
|
-
maxAge?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_CORS_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
|
|
20
|
-
export const DEFAULT_CORS_HEADERS = ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'];
|
|
21
|
-
|
|
22
|
-
type OriginMatcher = (origin: string) => boolean;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* CORS Handler - Pre-computes headers at startup for maximum performance.
|
|
26
|
-
*/
|
|
27
|
-
export class CorsHandler {
|
|
28
|
-
private readonly cache = new Map<string, Record<string, string>>();
|
|
29
|
-
private readonly methodsStr: string;
|
|
30
|
-
private readonly headersStr: string;
|
|
31
|
-
private readonly exposedStr: string | null;
|
|
32
|
-
private readonly maxAgeStr: string | null;
|
|
33
|
-
private readonly hasCredentials: boolean;
|
|
34
|
-
private readonly isWildcard: boolean;
|
|
35
|
-
private readonly matcher: OriginMatcher;
|
|
36
|
-
|
|
37
|
-
// Pre-created preflight response for wildcard CORS
|
|
38
|
-
private readonly preflightResponse: Response | null = null;
|
|
39
|
-
|
|
40
|
-
constructor(config: CorsConfig) {
|
|
41
|
-
this.methodsStr = (config.methods || DEFAULT_CORS_METHODS).join(', ');
|
|
42
|
-
this.headersStr = (config.allowedHeaders || DEFAULT_CORS_HEADERS).join(', ');
|
|
43
|
-
this.exposedStr = config.exposedHeaders?.join(', ') || null;
|
|
44
|
-
this.maxAgeStr = config.maxAge?.toString() || null;
|
|
45
|
-
this.hasCredentials = !!config.credentials;
|
|
46
|
-
this.isWildcard = config.origins === '*';
|
|
47
|
-
this.matcher = this.buildMatcher(config.origins);
|
|
48
|
-
|
|
49
|
-
// Pre-create preflight response for wildcard
|
|
50
|
-
if (this.isWildcard) {
|
|
51
|
-
this.preflightResponse = new Response(null, {
|
|
52
|
-
status: 204,
|
|
53
|
-
headers: this.buildHeaders('*')
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Handle preflight (OPTIONS) request.
|
|
60
|
-
*/
|
|
61
|
-
preflight(origin: string): Response {
|
|
62
|
-
if (this.isWildcard && this.preflightResponse) {
|
|
63
|
-
return this.preflightResponse.clone();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!this.isAllowed(origin)) {
|
|
67
|
-
return new Response(null, { status: 403 });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return new Response(null, {
|
|
71
|
-
status: 204,
|
|
72
|
-
headers: this.getHeaders(origin)
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Apply CORS headers to a response.
|
|
78
|
-
*/
|
|
79
|
-
apply(response: Response, origin: string): Response {
|
|
80
|
-
if (!this.isAllowed(origin)) {
|
|
81
|
-
return response;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const headers = this.getHeaders(origin);
|
|
85
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
86
|
-
response.headers.set(key, value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return response;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Check if origin is allowed.
|
|
94
|
-
*/
|
|
95
|
-
isAllowed(origin: string): boolean {
|
|
96
|
-
return this.matcher(origin);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Get cached CORS headers for origin.
|
|
101
|
-
*/
|
|
102
|
-
private getHeaders(origin: string): Record<string, string> {
|
|
103
|
-
const key = this.isWildcard ? '*' : origin;
|
|
104
|
-
let headers = this.cache.get(key);
|
|
105
|
-
|
|
106
|
-
if (!headers) {
|
|
107
|
-
headers = this.buildHeaders(origin);
|
|
108
|
-
this.cache.set(key, headers);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return headers;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private buildHeaders(origin: string): Record<string, string> {
|
|
115
|
-
const headers: Record<string, string> = {
|
|
116
|
-
'Access-Control-Allow-Origin': this.isWildcard ? '*' : origin,
|
|
117
|
-
'Access-Control-Allow-Methods': this.methodsStr,
|
|
118
|
-
'Access-Control-Allow-Headers': this.headersStr
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
if (this.hasCredentials) {
|
|
122
|
-
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
123
|
-
}
|
|
124
|
-
if (this.exposedStr) {
|
|
125
|
-
headers['Access-Control-Expose-Headers'] = this.exposedStr;
|
|
126
|
-
}
|
|
127
|
-
if (this.maxAgeStr) {
|
|
128
|
-
headers['Access-Control-Max-Age'] = this.maxAgeStr;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return headers;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private buildMatcher(origins: CorsOrigin): OriginMatcher {
|
|
135
|
-
if (origins === '*') return () => true;
|
|
136
|
-
if (typeof origins === 'string') return (o) => o === origins;
|
|
137
|
-
if (Array.isArray(origins)) {
|
|
138
|
-
const set = new Set(origins);
|
|
139
|
-
return (o) => set.has(o);
|
|
140
|
-
}
|
|
141
|
-
if (origins instanceof RegExp) return (o) => origins.test(o);
|
|
142
|
-
if (typeof origins === 'function') return origins;
|
|
143
|
-
return () => false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* CORS Configuration types.
|
|
3
|
+
*/
|
|
4
|
+
export type CorsOrigin =
|
|
5
|
+
| string
|
|
6
|
+
| string[]
|
|
7
|
+
| RegExp
|
|
8
|
+
| ((origin: string) => boolean);
|
|
9
|
+
|
|
10
|
+
export interface CorsConfig {
|
|
11
|
+
origins: CorsOrigin;
|
|
12
|
+
methods?: string[];
|
|
13
|
+
allowedHeaders?: string[];
|
|
14
|
+
exposedHeaders?: string[];
|
|
15
|
+
credentials?: boolean;
|
|
16
|
+
maxAge?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_CORS_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
|
|
20
|
+
export const DEFAULT_CORS_HEADERS = ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'];
|
|
21
|
+
|
|
22
|
+
type OriginMatcher = (origin: string) => boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* CORS Handler - Pre-computes headers at startup for maximum performance.
|
|
26
|
+
*/
|
|
27
|
+
export class CorsHandler {
|
|
28
|
+
private readonly cache = new Map<string, Record<string, string>>();
|
|
29
|
+
private readonly methodsStr: string;
|
|
30
|
+
private readonly headersStr: string;
|
|
31
|
+
private readonly exposedStr: string | null;
|
|
32
|
+
private readonly maxAgeStr: string | null;
|
|
33
|
+
private readonly hasCredentials: boolean;
|
|
34
|
+
private readonly isWildcard: boolean;
|
|
35
|
+
private readonly matcher: OriginMatcher;
|
|
36
|
+
|
|
37
|
+
// Pre-created preflight response for wildcard CORS
|
|
38
|
+
private readonly preflightResponse: Response | null = null;
|
|
39
|
+
|
|
40
|
+
constructor(config: CorsConfig) {
|
|
41
|
+
this.methodsStr = (config.methods || DEFAULT_CORS_METHODS).join(', ');
|
|
42
|
+
this.headersStr = (config.allowedHeaders || DEFAULT_CORS_HEADERS).join(', ');
|
|
43
|
+
this.exposedStr = config.exposedHeaders?.join(', ') || null;
|
|
44
|
+
this.maxAgeStr = config.maxAge?.toString() || null;
|
|
45
|
+
this.hasCredentials = !!config.credentials;
|
|
46
|
+
this.isWildcard = config.origins === '*';
|
|
47
|
+
this.matcher = this.buildMatcher(config.origins);
|
|
48
|
+
|
|
49
|
+
// Pre-create preflight response for wildcard
|
|
50
|
+
if (this.isWildcard) {
|
|
51
|
+
this.preflightResponse = new Response(null, {
|
|
52
|
+
status: 204,
|
|
53
|
+
headers: this.buildHeaders('*')
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle preflight (OPTIONS) request.
|
|
60
|
+
*/
|
|
61
|
+
preflight(origin: string): Response {
|
|
62
|
+
if (this.isWildcard && this.preflightResponse) {
|
|
63
|
+
return this.preflightResponse.clone();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!this.isAllowed(origin)) {
|
|
67
|
+
return new Response(null, { status: 403 });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Response(null, {
|
|
71
|
+
status: 204,
|
|
72
|
+
headers: this.getHeaders(origin)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Apply CORS headers to a response.
|
|
78
|
+
*/
|
|
79
|
+
apply(response: Response, origin: string): Response {
|
|
80
|
+
if (!this.isAllowed(origin)) {
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const headers = this.getHeaders(origin);
|
|
85
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
86
|
+
response.headers.set(key, value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if origin is allowed.
|
|
94
|
+
*/
|
|
95
|
+
isAllowed(origin: string): boolean {
|
|
96
|
+
return this.matcher(origin);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get cached CORS headers for origin.
|
|
101
|
+
*/
|
|
102
|
+
private getHeaders(origin: string): Record<string, string> {
|
|
103
|
+
const key = this.isWildcard ? '*' : origin;
|
|
104
|
+
let headers = this.cache.get(key);
|
|
105
|
+
|
|
106
|
+
if (!headers) {
|
|
107
|
+
headers = this.buildHeaders(origin);
|
|
108
|
+
this.cache.set(key, headers);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return headers;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private buildHeaders(origin: string): Record<string, string> {
|
|
115
|
+
const headers: Record<string, string> = {
|
|
116
|
+
'Access-Control-Allow-Origin': this.isWildcard ? '*' : origin,
|
|
117
|
+
'Access-Control-Allow-Methods': this.methodsStr,
|
|
118
|
+
'Access-Control-Allow-Headers': this.headersStr
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (this.hasCredentials) {
|
|
122
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
123
|
+
}
|
|
124
|
+
if (this.exposedStr) {
|
|
125
|
+
headers['Access-Control-Expose-Headers'] = this.exposedStr;
|
|
126
|
+
}
|
|
127
|
+
if (this.maxAgeStr) {
|
|
128
|
+
headers['Access-Control-Max-Age'] = this.maxAgeStr;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return headers;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private buildMatcher(origins: CorsOrigin): OriginMatcher {
|
|
135
|
+
if (origins === '*') return () => true;
|
|
136
|
+
if (typeof origins === 'string') return (o) => o === origins;
|
|
137
|
+
if (Array.isArray(origins)) {
|
|
138
|
+
const set = new Set(origins);
|
|
139
|
+
return (o) => set.has(o);
|
|
140
|
+
}
|
|
141
|
+
if (origins instanceof RegExp) return (o) => origins.test(o);
|
|
142
|
+
if (typeof origins === 'function') return origins;
|
|
143
|
+
return () => false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import { CONTROLLER_META, ROUTES_META } from '../metadata';
|
|
2
|
-
import type { ControllerOptions, ControllerMeta } from '../metadata';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Normalizes path or options to ControllerOptions.
|
|
6
|
-
*/
|
|
7
|
-
function normalizeOptions(pathOrOptions?: string | ControllerOptions): ControllerOptions {
|
|
8
|
-
if (!pathOrOptions) {
|
|
9
|
-
return {};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (typeof pathOrOptions === 'string') {
|
|
13
|
-
return { path: pathOrOptions };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return pathOrOptions;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalizes a path to start with / and not end with /.
|
|
21
|
-
*/
|
|
22
|
-
function normalizePath(path: string): string {
|
|
23
|
-
if (!path) return '';
|
|
24
|
-
|
|
25
|
-
let normalized = path.startsWith('/') ? path : '/' + path;
|
|
26
|
-
|
|
27
|
-
if (normalized !== '/' && normalized.endsWith('/')) {
|
|
28
|
-
normalized = normalized.slice(0, -1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return normalized;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Marks a class as a controller with a base path.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* // Simple path
|
|
39
|
-
* @Controller('/users')
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* // With options
|
|
43
|
-
* @Controller({ path: '/users', children: [ProfileController] })
|
|
44
|
-
*/
|
|
45
|
-
export function Controller(pathOrOptions?: string | ControllerOptions): ClassDecorator {
|
|
46
|
-
return (target) => {
|
|
47
|
-
const options = normalizeOptions(pathOrOptions);
|
|
48
|
-
const path = normalizePath(options.path || '');
|
|
49
|
-
|
|
50
|
-
const meta: ControllerMeta = {
|
|
51
|
-
path,
|
|
52
|
-
scope: options.scope,
|
|
53
|
-
children: options.children
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
Reflect.defineMetadata(CONTROLLER_META, meta, target);
|
|
57
|
-
|
|
58
|
-
// Ensure routes array exists
|
|
59
|
-
if (!Reflect.hasMetadata(ROUTES_META, target)) {
|
|
60
|
-
Reflect.defineMetadata(ROUTES_META, [], target);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
1
|
+
import { CONTROLLER_META, ROUTES_META } from '../metadata';
|
|
2
|
+
import type { ControllerOptions, ControllerMeta } from '../metadata';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes path or options to ControllerOptions.
|
|
6
|
+
*/
|
|
7
|
+
function normalizeOptions(pathOrOptions?: string | ControllerOptions): ControllerOptions {
|
|
8
|
+
if (!pathOrOptions) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof pathOrOptions === 'string') {
|
|
13
|
+
return { path: pathOrOptions };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return pathOrOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes a path to start with / and not end with /.
|
|
21
|
+
*/
|
|
22
|
+
function normalizePath(path: string): string {
|
|
23
|
+
if (!path) return '';
|
|
24
|
+
|
|
25
|
+
let normalized = path.startsWith('/') ? path : '/' + path;
|
|
26
|
+
|
|
27
|
+
if (normalized !== '/' && normalized.endsWith('/')) {
|
|
28
|
+
normalized = normalized.slice(0, -1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Marks a class as a controller with a base path.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Simple path
|
|
39
|
+
* @Controller('/users')
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // With options
|
|
43
|
+
* @Controller({ path: '/users', children: [ProfileController] })
|
|
44
|
+
*/
|
|
45
|
+
export function Controller(pathOrOptions?: string | ControllerOptions): ClassDecorator {
|
|
46
|
+
return (target) => {
|
|
47
|
+
const options = normalizeOptions(pathOrOptions);
|
|
48
|
+
const path = normalizePath(options.path || '');
|
|
49
|
+
|
|
50
|
+
const meta: ControllerMeta = {
|
|
51
|
+
path,
|
|
52
|
+
scope: options.scope,
|
|
53
|
+
children: options.children
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Reflect.defineMetadata(CONTROLLER_META, meta, target);
|
|
57
|
+
|
|
58
|
+
// Ensure routes array exists
|
|
59
|
+
if (!Reflect.hasMetadata(ROUTES_META, target)) {
|
|
60
|
+
Reflect.defineMetadata(ROUTES_META, [], target);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/decorators/Inject.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { INJECT_META } from '../metadata';
|
|
2
|
-
import type { Token } from '../container/Container';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Explicitly specifies which token to inject.
|
|
6
|
-
* Useful for interfaces or when automatic detection fails.
|
|
7
|
-
*/
|
|
8
|
-
export function Inject(token: Token): ParameterDecorator {
|
|
9
|
-
return (target, propertyKey, parameterIndex) => {
|
|
10
|
-
const existing: Map<number, Token> = Reflect.getMetadata(INJECT_META, target) || new Map();
|
|
11
|
-
|
|
12
|
-
existing.set(parameterIndex, token);
|
|
13
|
-
|
|
14
|
-
Reflect.defineMetadata(INJECT_META, existing, target);
|
|
15
|
-
};
|
|
16
|
-
}
|
|
1
|
+
import { INJECT_META } from '../metadata';
|
|
2
|
+
import type { Token } from '../container/Container';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Explicitly specifies which token to inject.
|
|
6
|
+
* Useful for interfaces or when automatic detection fails.
|
|
7
|
+
*/
|
|
8
|
+
export function Inject(token: Token): ParameterDecorator {
|
|
9
|
+
return (target, propertyKey, parameterIndex) => {
|
|
10
|
+
const existing: Map<number, Token> = Reflect.getMetadata(INJECT_META, target) || new Map();
|
|
11
|
+
|
|
12
|
+
existing.set(parameterIndex, token);
|
|
13
|
+
|
|
14
|
+
Reflect.defineMetadata(INJECT_META, existing, target);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { MIDDLEWARE_META, type MiddlewareInfo } from '../metadata';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Middleware decorator.
|
|
5
|
-
* Can be applied to controllers or individual methods.
|
|
6
|
-
*/
|
|
7
|
-
export function Use(...middlewares: Function[]): ClassDecorator & MethodDecorator {
|
|
8
|
-
return function (target: any, propertyKey?: string) {
|
|
9
|
-
const isMethod = propertyKey !== undefined;
|
|
10
|
-
const metaTarget = isMethod ? target.constructor : target;
|
|
11
|
-
const existing: MiddlewareInfo[] = Reflect.getMetadata(MIDDLEWARE_META, metaTarget) || [];
|
|
12
|
-
|
|
13
|
-
for (const handler of middlewares) {
|
|
14
|
-
existing.push({
|
|
15
|
-
handler,
|
|
16
|
-
target: isMethod ? propertyKey : undefined
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
Reflect.defineMetadata(MIDDLEWARE_META, existing, metaTarget);
|
|
21
|
-
} as ClassDecorator & MethodDecorator;
|
|
22
|
-
}
|
|
1
|
+
import { MIDDLEWARE_META, type MiddlewareInfo } from '../metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Middleware decorator.
|
|
5
|
+
* Can be applied to controllers or individual methods.
|
|
6
|
+
*/
|
|
7
|
+
export function Use(...middlewares: Function[]): ClassDecorator & MethodDecorator {
|
|
8
|
+
return function (target: any, propertyKey?: string) {
|
|
9
|
+
const isMethod = propertyKey !== undefined;
|
|
10
|
+
const metaTarget = isMethod ? target.constructor : target;
|
|
11
|
+
const existing: MiddlewareInfo[] = Reflect.getMetadata(MIDDLEWARE_META, metaTarget) || [];
|
|
12
|
+
|
|
13
|
+
for (const handler of middlewares) {
|
|
14
|
+
existing.push({
|
|
15
|
+
handler,
|
|
16
|
+
target: isMethod ? propertyKey : undefined
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Reflect.defineMetadata(MIDDLEWARE_META, existing, metaTarget);
|
|
21
|
+
} as ClassDecorator & MethodDecorator;
|
|
22
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { SERVICE_META } from '../metadata';
|
|
2
|
-
import { Scope } from '../container/Container';
|
|
3
|
-
|
|
4
|
-
export interface ServiceOptions {
|
|
5
|
-
scope?: Scope;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Marks a class as an injectable service.
|
|
10
|
-
* Services are singleton by default.
|
|
11
|
-
*/
|
|
12
|
-
export function Service(options: ServiceOptions = {}): ClassDecorator {
|
|
13
|
-
return (target) => {
|
|
14
|
-
Reflect.defineMetadata(SERVICE_META, {
|
|
15
|
-
scope: options.scope ?? Scope.SINGLETON
|
|
16
|
-
}, target);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
1
|
+
import { SERVICE_META } from '../metadata';
|
|
2
|
+
import { Scope } from '../container/Container';
|
|
3
|
+
|
|
4
|
+
export interface ServiceOptions {
|
|
5
|
+
scope?: Scope;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Marks a class as an injectable service.
|
|
10
|
+
* Services are singleton by default.
|
|
11
|
+
*/
|
|
12
|
+
export function Service(options: ServiceOptions = {}): ClassDecorator {
|
|
13
|
+
return (target) => {
|
|
14
|
+
Reflect.defineMetadata(SERVICE_META, {
|
|
15
|
+
scope: options.scope ?? Scope.SINGLETON
|
|
16
|
+
}, target);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { ROUTES_META, type RouteInfo } from '../metadata';
|
|
2
|
-
|
|
3
|
-
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a method decorator for HTTP methods.
|
|
7
|
-
* Supports both legacy decorators (experimentalDecorators) and TS5 stage 3 decorators.
|
|
8
|
-
*/
|
|
9
|
-
function createMethodDecorator(method: HttpMethod) {
|
|
10
|
-
return function (path: string = ''): any {
|
|
11
|
-
return function (
|
|
12
|
-
targetOrMethod: any,
|
|
13
|
-
contextOrPropertyKey?: string | symbol | ClassMethodDecoratorContext,
|
|
14
|
-
descriptor?: PropertyDescriptor
|
|
15
|
-
): any {
|
|
16
|
-
// TS5 Stage 3 decorators: context is ClassMethodDecoratorContext
|
|
17
|
-
if (contextOrPropertyKey && typeof contextOrPropertyKey === 'object' && 'kind' in contextOrPropertyKey) {
|
|
18
|
-
const context = contextOrPropertyKey as ClassMethodDecoratorContext;
|
|
19
|
-
|
|
20
|
-
context.addInitializer(function (this: any) {
|
|
21
|
-
const constructor = this.constructor;
|
|
22
|
-
const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, constructor) || [];
|
|
23
|
-
|
|
24
|
-
routes.push({
|
|
25
|
-
method,
|
|
26
|
-
path: path.startsWith('/') ? path : '/' + path,
|
|
27
|
-
handlerName: String(context.name)
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
Reflect.defineMetadata(ROUTES_META, routes, constructor);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return targetOrMethod;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Legacy decorators (experimentalDecorators: true)
|
|
37
|
-
const constructor = targetOrMethod.constructor;
|
|
38
|
-
const propertyKey = contextOrPropertyKey as string | symbol;
|
|
39
|
-
const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, constructor) || [];
|
|
40
|
-
|
|
41
|
-
routes.push({
|
|
42
|
-
method,
|
|
43
|
-
path: path.startsWith('/') ? path : '/' + path,
|
|
44
|
-
handlerName: String(propertyKey)
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
Reflect.defineMetadata(ROUTES_META, routes, constructor);
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const Get = createMethodDecorator('get');
|
|
53
|
-
export const Post = createMethodDecorator('post');
|
|
54
|
-
export const Put = createMethodDecorator('put');
|
|
55
|
-
export const Delete = createMethodDecorator('delete');
|
|
56
|
-
export const Patch = createMethodDecorator('patch');
|
|
57
|
-
export const Head = createMethodDecorator('head');
|
|
58
|
-
export const Options = createMethodDecorator('options');
|
|
1
|
+
import { ROUTES_META, type RouteInfo } from '../metadata';
|
|
2
|
+
|
|
3
|
+
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a method decorator for HTTP methods.
|
|
7
|
+
* Supports both legacy decorators (experimentalDecorators) and TS5 stage 3 decorators.
|
|
8
|
+
*/
|
|
9
|
+
function createMethodDecorator(method: HttpMethod) {
|
|
10
|
+
return function (path: string = ''): any {
|
|
11
|
+
return function (
|
|
12
|
+
targetOrMethod: any,
|
|
13
|
+
contextOrPropertyKey?: string | symbol | ClassMethodDecoratorContext,
|
|
14
|
+
descriptor?: PropertyDescriptor
|
|
15
|
+
): any {
|
|
16
|
+
// TS5 Stage 3 decorators: context is ClassMethodDecoratorContext
|
|
17
|
+
if (contextOrPropertyKey && typeof contextOrPropertyKey === 'object' && 'kind' in contextOrPropertyKey) {
|
|
18
|
+
const context = contextOrPropertyKey as ClassMethodDecoratorContext;
|
|
19
|
+
|
|
20
|
+
context.addInitializer(function (this: any) {
|
|
21
|
+
const constructor = this.constructor;
|
|
22
|
+
const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, constructor) || [];
|
|
23
|
+
|
|
24
|
+
routes.push({
|
|
25
|
+
method,
|
|
26
|
+
path: path.startsWith('/') ? path : '/' + path,
|
|
27
|
+
handlerName: String(context.name)
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
Reflect.defineMetadata(ROUTES_META, routes, constructor);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return targetOrMethod;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Legacy decorators (experimentalDecorators: true)
|
|
37
|
+
const constructor = targetOrMethod.constructor;
|
|
38
|
+
const propertyKey = contextOrPropertyKey as string | symbol;
|
|
39
|
+
const routes: RouteInfo[] = Reflect.getMetadata(ROUTES_META, constructor) || [];
|
|
40
|
+
|
|
41
|
+
routes.push({
|
|
42
|
+
method,
|
|
43
|
+
path: path.startsWith('/') ? path : '/' + path,
|
|
44
|
+
handlerName: String(propertyKey)
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
Reflect.defineMetadata(ROUTES_META, routes, constructor);
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const Get = createMethodDecorator('get');
|
|
53
|
+
export const Post = createMethodDecorator('post');
|
|
54
|
+
export const Put = createMethodDecorator('put');
|
|
55
|
+
export const Delete = createMethodDecorator('delete');
|
|
56
|
+
export const Patch = createMethodDecorator('patch');
|
|
57
|
+
export const Head = createMethodDecorator('head');
|
|
58
|
+
export const Options = createMethodDecorator('options');
|