@carno.js/core 1.1.1 → 1.2.0

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.
Files changed (124) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +188 -188
  3. package/dist/Carno.js +45 -26
  4. package/dist/Carno.mjs +45 -26
  5. package/dist/bun/index.js +4 -4
  6. package/dist/bun/index.js.map +30 -29
  7. package/dist/compression/CompressionMiddleware.js +110 -0
  8. package/dist/compression/CompressionMiddleware.mjs +90 -0
  9. package/dist/index.js +3 -1
  10. package/dist/index.mjs +2 -0
  11. package/package.json +2 -2
  12. package/src/Carno.ts +728 -673
  13. package/src/DefaultRoutes.ts +34 -34
  14. package/src/cache/CacheDriver.ts +50 -50
  15. package/src/cache/CacheService.ts +139 -139
  16. package/src/cache/MemoryDriver.ts +104 -104
  17. package/src/cache/RedisDriver.ts +116 -116
  18. package/src/compiler/JITCompiler.ts +167 -167
  19. package/src/compression/CompressionMiddleware.ts +221 -0
  20. package/src/container/Container.ts +168 -168
  21. package/src/context/Context.ts +130 -130
  22. package/src/cors/CorsHandler.ts +145 -145
  23. package/src/decorators/Controller.ts +63 -63
  24. package/src/decorators/Inject.ts +16 -16
  25. package/src/decorators/Middleware.ts +22 -22
  26. package/src/decorators/Service.ts +18 -18
  27. package/src/decorators/methods.ts +58 -58
  28. package/src/decorators/params.ts +47 -47
  29. package/src/events/Lifecycle.ts +97 -97
  30. package/src/exceptions/HttpException.ts +99 -99
  31. package/src/index.ts +99 -95
  32. package/src/metadata.ts +46 -46
  33. package/src/middleware/CarnoMiddleware.ts +20 -14
  34. package/src/router/RadixRouter.ts +225 -225
  35. package/src/testing/TestHarness.ts +185 -185
  36. package/src/utils/Metadata.ts +43 -43
  37. package/src/utils/parseQuery.ts +161 -161
  38. package/src/validation/ValibotAdapter.ts +95 -95
  39. package/src/validation/ValidatorAdapter.ts +69 -69
  40. package/src/validation/ZodAdapter.ts +102 -102
  41. package/dist/Carno.d.js +0 -14
  42. package/dist/Carno.d.mjs +0 -1
  43. package/dist/DefaultRoutes.d.js +0 -13
  44. package/dist/DefaultRoutes.d.mjs +0 -0
  45. package/dist/cache/CacheDriver.d.js +0 -13
  46. package/dist/cache/CacheDriver.d.mjs +0 -0
  47. package/dist/cache/CacheService.d.js +0 -13
  48. package/dist/cache/CacheService.d.mjs +0 -0
  49. package/dist/cache/MemoryDriver.d.js +0 -13
  50. package/dist/cache/MemoryDriver.d.mjs +0 -0
  51. package/dist/cache/RedisDriver.d.js +0 -13
  52. package/dist/cache/RedisDriver.d.mjs +0 -0
  53. package/dist/compiler/JITCompiler.d.js +0 -13
  54. package/dist/compiler/JITCompiler.d.mjs +0 -0
  55. package/dist/container/Container.d.js +0 -13
  56. package/dist/container/Container.d.mjs +0 -0
  57. package/dist/context/Context.d.js +0 -13
  58. package/dist/context/Context.d.mjs +0 -0
  59. package/dist/cors/CorsHandler.d.js +0 -13
  60. package/dist/cors/CorsHandler.d.mjs +0 -0
  61. package/dist/decorators/Controller.d.js +0 -13
  62. package/dist/decorators/Controller.d.mjs +0 -0
  63. package/dist/decorators/Inject.d.js +0 -13
  64. package/dist/decorators/Inject.d.mjs +0 -0
  65. package/dist/decorators/Middleware.d.js +0 -13
  66. package/dist/decorators/Middleware.d.mjs +0 -0
  67. package/dist/decorators/Service.d.js +0 -13
  68. package/dist/decorators/Service.d.mjs +0 -0
  69. package/dist/decorators/methods.d.js +0 -13
  70. package/dist/decorators/methods.d.mjs +0 -0
  71. package/dist/decorators/params.d.js +0 -13
  72. package/dist/decorators/params.d.mjs +0 -0
  73. package/dist/events/Lifecycle.d.js +0 -13
  74. package/dist/events/Lifecycle.d.mjs +0 -0
  75. package/dist/exceptions/HttpException.d.js +0 -13
  76. package/dist/exceptions/HttpException.d.mjs +0 -0
  77. package/dist/index.d.js +0 -130
  78. package/dist/index.d.mjs +0 -78
  79. package/dist/metadata.d.js +0 -13
  80. package/dist/metadata.d.mjs +0 -0
  81. package/dist/middleware/CarnoMiddleware.d.js +0 -13
  82. package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
  83. package/dist/router/RadixRouter.d.js +0 -13
  84. package/dist/router/RadixRouter.d.mjs +0 -0
  85. package/dist/testing/TestHarness.d.js +0 -13
  86. package/dist/testing/TestHarness.d.mjs +0 -0
  87. package/dist/utils/Metadata.d.js +0 -13
  88. package/dist/utils/Metadata.d.mjs +0 -0
  89. package/dist/utils/parseQuery.d.js +0 -13
  90. package/dist/utils/parseQuery.d.mjs +0 -0
  91. package/dist/validation/ValibotAdapter.d.js +0 -13
  92. package/dist/validation/ValibotAdapter.d.mjs +0 -0
  93. package/dist/validation/ValidatorAdapter.d.js +0 -13
  94. package/dist/validation/ValidatorAdapter.d.mjs +0 -0
  95. package/dist/validation/ZodAdapter.d.js +0 -13
  96. package/dist/validation/ZodAdapter.d.mjs +0 -0
  97. package/src/Carno.d.ts +0 -135
  98. package/src/DefaultRoutes.d.ts +0 -19
  99. package/src/cache/CacheDriver.d.ts +0 -43
  100. package/src/cache/CacheService.d.ts +0 -89
  101. package/src/cache/MemoryDriver.d.ts +0 -32
  102. package/src/cache/RedisDriver.d.ts +0 -34
  103. package/src/compiler/JITCompiler.d.ts +0 -36
  104. package/src/container/Container.d.ts +0 -38
  105. package/src/context/Context.d.ts +0 -36
  106. package/src/cors/CorsHandler.d.ts +0 -47
  107. package/src/decorators/Controller.d.ts +0 -13
  108. package/src/decorators/Inject.d.ts +0 -6
  109. package/src/decorators/Middleware.d.ts +0 -5
  110. package/src/decorators/Service.d.ts +0 -9
  111. package/src/decorators/methods.d.ts +0 -7
  112. package/src/decorators/params.d.ts +0 -13
  113. package/src/events/Lifecycle.d.ts +0 -54
  114. package/src/exceptions/HttpException.d.ts +0 -43
  115. package/src/index.d.ts +0 -42
  116. package/src/metadata.d.ts +0 -41
  117. package/src/middleware/CarnoMiddleware.d.ts +0 -12
  118. package/src/router/RadixRouter.d.ts +0 -19
  119. package/src/testing/TestHarness.d.ts +0 -71
  120. package/src/utils/Metadata.d.ts +0 -20
  121. package/src/utils/parseQuery.d.ts +0 -23
  122. package/src/validation/ValibotAdapter.d.ts +0 -30
  123. package/src/validation/ValidatorAdapter.d.ts +0 -54
  124. package/src/validation/ZodAdapter.d.ts +0 -35
@@ -1,130 +1,130 @@
1
- /**
2
- * Request Context for Turbo.
3
- *
4
- * Lazy initialization for maximum performance:
5
- * - Query parsed only when accessed
6
- * - Body parsed only when needed
7
- * - Minimal allocations in hot path
8
- */
9
-
10
- import { parseQueryFromURL } from '../utils/parseQuery';
11
-
12
- const EMPTY_PARAMS: Record<string, string> = Object.freeze({}) as Record<string, string>;
13
-
14
- export class Context {
15
- readonly req: Request;
16
- params: Record<string, string>;
17
- locals: Record<string, any> = {};
18
-
19
- // Lazy fields - only allocated when accessed
20
- private _query: Record<string, string> | null = null;
21
- private _body: any;
22
- private _bodyParsed = false;
23
- private _url: URL | null = null;
24
- private _status = 0;
25
-
26
- constructor(req: Request, params: Record<string, string> = EMPTY_PARAMS) {
27
- this.req = req;
28
- this.params = params;
29
- }
30
-
31
- get status(): number {
32
- return this._status || 200;
33
- }
34
-
35
- set status(value: number) {
36
- this._status = value;
37
- }
38
-
39
- get url(): URL {
40
- if (!this._url) {
41
- this._url = new URL(this.req.url);
42
- }
43
-
44
- return this._url;
45
- }
46
-
47
- get query(): Record<string, string> {
48
- if (!this._query) {
49
- this._query = parseQueryFromURL(this.req.url);
50
- }
51
-
52
- return this._query;
53
- }
54
-
55
- get body(): any {
56
- return this._body;
57
- }
58
-
59
- async parseBody(): Promise<any> {
60
- if (this._bodyParsed) {
61
- return this._body;
62
- }
63
-
64
- this._bodyParsed = true;
65
- const contentType = this.req.headers.get('content-type') || '';
66
-
67
- if (contentType.includes('application/json')) {
68
- this._body = await this.req.json();
69
- } else if (contentType.includes('form')) {
70
- const formData = await this.req.formData();
71
- this._body = Object.fromEntries(formData);
72
- } else if (contentType.includes('text')) {
73
- this._body = await this.req.text();
74
- } else {
75
- this._body = await this.req.arrayBuffer();
76
- }
77
-
78
- return this._body;
79
- }
80
-
81
- get method(): string {
82
- return this.req.method;
83
- }
84
-
85
- get headers(): Headers {
86
- return this.req.headers;
87
- }
88
-
89
- get path(): string {
90
- return this.url.pathname;
91
- }
92
-
93
- json(data: any, status?: number): Response {
94
- if (status) this.status = status;
95
-
96
- return Response.json(data, { status: this.status });
97
- }
98
-
99
- text(data: string, status?: number): Response {
100
- if (status) this.status = status;
101
-
102
- return new Response(data, {
103
- status: this.status,
104
- headers: { 'Content-Type': 'text/plain' }
105
- });
106
- }
107
-
108
- html(data: string, status?: number): Response {
109
- if (status) this.status = status;
110
-
111
- return new Response(data, {
112
- status: this.status,
113
- headers: { 'Content-Type': 'text/html' }
114
- });
115
- }
116
-
117
- redirect(url: string, status: number = 302): Response {
118
- return Response.redirect(url, status);
119
- }
120
-
121
- /**
122
- * Creates a Context from a job (for queue processing).
123
- */
124
- static createFromJob(job: any): Context {
125
- const fakeRequest = new Request('http://localhost/job');
126
- const ctx = new Context(fakeRequest);
127
- ctx.locals.job = job;
128
- return ctx;
129
- }
130
- }
1
+ /**
2
+ * Request Context for Turbo.
3
+ *
4
+ * Lazy initialization for maximum performance:
5
+ * - Query parsed only when accessed
6
+ * - Body parsed only when needed
7
+ * - Minimal allocations in hot path
8
+ */
9
+
10
+ import { parseQueryFromURL } from '../utils/parseQuery';
11
+
12
+ const EMPTY_PARAMS: Record<string, string> = Object.freeze({}) as Record<string, string>;
13
+
14
+ export class Context {
15
+ readonly req: Request;
16
+ params: Record<string, string>;
17
+ locals: Record<string, any> = {};
18
+
19
+ // Lazy fields - only allocated when accessed
20
+ private _query: Record<string, string> | null = null;
21
+ private _body: any;
22
+ private _bodyParsed = false;
23
+ private _url: URL | null = null;
24
+ private _status = 0;
25
+
26
+ constructor(req: Request, params: Record<string, string> = EMPTY_PARAMS) {
27
+ this.req = req;
28
+ this.params = params;
29
+ }
30
+
31
+ get status(): number {
32
+ return this._status || 200;
33
+ }
34
+
35
+ set status(value: number) {
36
+ this._status = value;
37
+ }
38
+
39
+ get url(): URL {
40
+ if (!this._url) {
41
+ this._url = new URL(this.req.url);
42
+ }
43
+
44
+ return this._url;
45
+ }
46
+
47
+ get query(): Record<string, string> {
48
+ if (!this._query) {
49
+ this._query = parseQueryFromURL(this.req.url);
50
+ }
51
+
52
+ return this._query;
53
+ }
54
+
55
+ get body(): any {
56
+ return this._body;
57
+ }
58
+
59
+ async parseBody(): Promise<any> {
60
+ if (this._bodyParsed) {
61
+ return this._body;
62
+ }
63
+
64
+ this._bodyParsed = true;
65
+ const contentType = this.req.headers.get('content-type') || '';
66
+
67
+ if (contentType.includes('application/json')) {
68
+ this._body = await this.req.json();
69
+ } else if (contentType.includes('form')) {
70
+ const formData = await this.req.formData();
71
+ this._body = Object.fromEntries(formData);
72
+ } else if (contentType.includes('text')) {
73
+ this._body = await this.req.text();
74
+ } else {
75
+ this._body = await this.req.arrayBuffer();
76
+ }
77
+
78
+ return this._body;
79
+ }
80
+
81
+ get method(): string {
82
+ return this.req.method;
83
+ }
84
+
85
+ get headers(): Headers {
86
+ return this.req.headers;
87
+ }
88
+
89
+ get path(): string {
90
+ return this.url.pathname;
91
+ }
92
+
93
+ json(data: any, status?: number): Response {
94
+ if (status) this.status = status;
95
+
96
+ return Response.json(data, { status: this.status });
97
+ }
98
+
99
+ text(data: string, status?: number): Response {
100
+ if (status) this.status = status;
101
+
102
+ return new Response(data, {
103
+ status: this.status,
104
+ headers: { 'Content-Type': 'text/plain' }
105
+ });
106
+ }
107
+
108
+ html(data: string, status?: number): Response {
109
+ if (status) this.status = status;
110
+
111
+ return new Response(data, {
112
+ status: this.status,
113
+ headers: { 'Content-Type': 'text/html' }
114
+ });
115
+ }
116
+
117
+ redirect(url: string, status: number = 302): Response {
118
+ return Response.redirect(url, status);
119
+ }
120
+
121
+ /**
122
+ * Creates a Context from a job (for queue processing).
123
+ */
124
+ static createFromJob(job: any): Context {
125
+ const fakeRequest = new Request('http://localhost/job');
126
+ const ctx = new Context(fakeRequest);
127
+ ctx.locals.job = job;
128
+ return ctx;
129
+ }
130
+ }
@@ -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
+ }