@webpieces/http-routing 0.2.91 → 0.2.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-routing",
3
- "version": "0.2.91",
3
+ "version": "0.2.93",
4
4
  "description": "Decorator-based routing with auto-wiring for WebPieces",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,8 +21,8 @@
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "@webpieces/http-api": "0.2.91",
25
- "@webpieces/http-filters": "0.2.91",
24
+ "@webpieces/http-api": "0.2.93",
25
+ "@webpieces/http-filters": "0.2.93",
26
26
  "inversify": "7.10.4",
27
27
  "minimatch": "10.0.1"
28
28
  }
@@ -0,0 +1,57 @@
1
+ import { Routes, RouteBuilder } from './WebAppMeta';
2
+ import { AuthMeta } from '@webpieces/http-api';
3
+ import 'reflect-metadata';
4
+ /**
5
+ * Type representing a class constructor (abstract or concrete).
6
+ */
7
+ export type ClassType<T = unknown> = Function & {
8
+ prototype: T;
9
+ };
10
+ /**
11
+ * ApiRoutingFactory - Automatically wire API interfaces to controllers.
12
+ * Reads @ApiPath/@Endpoint decorators from an API prototype class and
13
+ * registers POST routes for each endpoint.
14
+ *
15
+ * Replaces the old RESTApiRoutes class.
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * // In your ServerMeta:
20
+ * getRoutes(): Routes[] {
21
+ * return [
22
+ * new ApiRoutingFactory(SaveApiPrototype, SaveController),
23
+ * ];
24
+ * }
25
+ * ```
26
+ */
27
+ export declare class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {
28
+ private apiMetaClass;
29
+ private controllerClass;
30
+ /**
31
+ * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators
32
+ * @param controllerClass - The controller class that implements the API
33
+ */
34
+ constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>);
35
+ /**
36
+ * Configure routes by reading @ApiPath + @Endpoint metadata.
37
+ * Validates controller methods and auth decorators in single loop.
38
+ */
39
+ configure(routeBuilder: RouteBuilder): void;
40
+ /**
41
+ * Get the filepath of the controller source file.
42
+ * Uses a heuristic based on the controller class name.
43
+ */
44
+ private getControllerFilepath;
45
+ /**
46
+ * Get auth metadata for a specific method, falling back to class-level.
47
+ */
48
+ getAuthMetaForMethod(methodName: string): AuthMeta | undefined;
49
+ /**
50
+ * Get the API interface class.
51
+ */
52
+ getApiClass(): ClassType<TApi>;
53
+ /**
54
+ * Get the controller class.
55
+ */
56
+ getControllerClass(): ClassType<TController>;
57
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiRoutingFactory = void 0;
4
+ const WebAppMeta_1 = require("./WebAppMeta");
5
+ const http_api_1 = require("@webpieces/http-api");
6
+ require("reflect-metadata");
7
+ const decorators_1 = require("./decorators");
8
+ /**
9
+ * ApiRoutingFactory - Automatically wire API interfaces to controllers.
10
+ * Reads @ApiPath/@Endpoint decorators from an API prototype class and
11
+ * registers POST routes for each endpoint.
12
+ *
13
+ * Replaces the old RESTApiRoutes class.
14
+ *
15
+ * Usage:
16
+ * ```typescript
17
+ * // In your ServerMeta:
18
+ * getRoutes(): Routes[] {
19
+ * return [
20
+ * new ApiRoutingFactory(SaveApiPrototype, SaveController),
21
+ * ];
22
+ * }
23
+ * ```
24
+ */
25
+ // webpieces-disable no-any-unknown -- generic class requires unconstrained default type params
26
+ class ApiRoutingFactory {
27
+ /**
28
+ * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators
29
+ * @param controllerClass - The controller class that implements the API
30
+ */
31
+ constructor(apiMetaClass, controllerClass) {
32
+ this.apiMetaClass = apiMetaClass;
33
+ this.controllerClass = controllerClass;
34
+ // Validate that apiMetaClass is marked with @ApiPath
35
+ if (!(0, http_api_1.isApiPath)(apiMetaClass)) {
36
+ const className = apiMetaClass.name || 'Unknown';
37
+ throw new Error(`Class ${className} must be decorated with @ApiPath()`);
38
+ }
39
+ }
40
+ /**
41
+ * Configure routes by reading @ApiPath + @Endpoint metadata.
42
+ * Validates controller methods and auth decorators in single loop.
43
+ */
44
+ configure(routeBuilder) {
45
+ const basePath = (0, http_api_1.getApiPath)(this.apiMetaClass);
46
+ const endpoints = (0, http_api_1.getEndpoints)(this.apiMetaClass) || {};
47
+ const controllerFilepath = this.getControllerFilepath();
48
+ const apiName = this.apiMetaClass.name || 'Unknown';
49
+ const controllerName = this.controllerClass.name || 'Unknown';
50
+ for (const [methodName, endpointPath] of Object.entries(endpoints)) {
51
+ // Validate controller implements this method
52
+ if (typeof this.controllerClass.prototype[methodName] !== 'function') {
53
+ throw new Error(`Controller ${controllerName} must implement method ${methodName} from API ${apiName}`);
54
+ }
55
+ // Validate auth decorator exists (class-level or method-level)
56
+ const authMeta = (0, http_api_1.getAuthMeta)(this.apiMetaClass, methodName);
57
+ if (!authMeta) {
58
+ throw new Error(`Endpoint '${methodName}' in ${apiName} has no @Authentication decorator. ` +
59
+ `Add @Authentication(new AuthenticationConfig(...)) to the class or method.`);
60
+ }
61
+ const fullPath = basePath + endpointPath;
62
+ const routeMeta = new http_api_1.RouteMetadata('POST', fullPath, methodName, controllerName, authMeta);
63
+ routeBuilder.addRoute(new WebAppMeta_1.RouteDefinition(routeMeta, this.controllerClass, controllerFilepath));
64
+ }
65
+ }
66
+ /**
67
+ * Get the filepath of the controller source file.
68
+ * Uses a heuristic based on the controller class name.
69
+ */
70
+ getControllerFilepath() {
71
+ // Check for explicit @SourceFile decorator metadata
72
+ const filepath = Reflect.getMetadata(decorators_1.ROUTING_METADATA_KEYS.SOURCE_FILEPATH, this.controllerClass);
73
+ if (filepath) {
74
+ return filepath;
75
+ }
76
+ // Fallback to class name pattern
77
+ const className = this.controllerClass.name;
78
+ return className ? `**/${className}.ts` : undefined;
79
+ }
80
+ /**
81
+ * Get auth metadata for a specific method, falling back to class-level.
82
+ */
83
+ getAuthMetaForMethod(methodName) {
84
+ return (0, http_api_1.getAuthMeta)(this.apiMetaClass, methodName);
85
+ }
86
+ /**
87
+ * Get the API interface class.
88
+ */
89
+ getApiClass() {
90
+ return this.apiMetaClass;
91
+ }
92
+ /**
93
+ * Get the controller class.
94
+ */
95
+ getControllerClass() {
96
+ return this.controllerClass;
97
+ }
98
+ }
99
+ exports.ApiRoutingFactory = ApiRoutingFactory;
100
+ //# sourceMappingURL=ApiRoutingFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiRoutingFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiRoutingFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport { isApiPath, getApiPath, getEndpoints, getAuthMeta, RouteMetadata, AuthMeta } from '@webpieces/http-api';\nimport 'reflect-metadata';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\n/**\n * Type representing a class constructor (abstract or concrete).\n */\n// webpieces-disable no-any-unknown -- generic type alias requires unconstrained default\nexport type ClassType<T = unknown> = Function & { prototype: T };\n\n/**\n * ApiRoutingFactory - Automatically wire API interfaces to controllers.\n * Reads @ApiPath/@Endpoint decorators from an API prototype class and\n * registers POST routes for each endpoint.\n *\n * Replaces the old RESTApiRoutes class.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new ApiRoutingFactory(SaveApiPrototype, SaveController),\n * ];\n * }\n * ```\n */\n// webpieces-disable no-any-unknown -- generic class requires unconstrained default type params\nexport class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController>;\n\n /**\n * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators\n * @param controllerClass - The controller class that implements the API\n */\n constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>) {\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked with @ApiPath\n if (!isApiPath(apiMetaClass)) {\n const className = apiMetaClass.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n }\n\n /**\n * Configure routes by reading @ApiPath + @Endpoint metadata.\n * Validates controller methods and auth decorators in single loop.\n */\n configure(routeBuilder: RouteBuilder): void {\n const basePath = getApiPath(this.apiMetaClass)!;\n const endpoints = getEndpoints(this.apiMetaClass) || {};\n const controllerFilepath = this.getControllerFilepath();\n const apiName = this.apiMetaClass.name || 'Unknown';\n const controllerName = this.controllerClass.name || 'Unknown';\n\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n // Validate controller implements this method\n if (typeof this.controllerClass.prototype[methodName] !== 'function') {\n throw new Error(\n `Controller ${controllerName} must implement method ${methodName} from API ${apiName}`,\n );\n }\n\n // Validate auth decorator exists (class-level or method-level)\n const authMeta = getAuthMeta(this.apiMetaClass, methodName);\n if (!authMeta) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no @Authentication decorator. ` +\n `Add @Authentication(new AuthenticationConfig(...)) to the class or method.`,\n );\n }\n\n const fullPath = basePath + endpointPath;\n const routeMeta = new RouteMetadata(\n 'POST',\n fullPath,\n methodName,\n controllerName,\n authMeta,\n );\n\n routeBuilder.addRoute(new RouteDefinition(routeMeta, this.controllerClass, controllerFilepath));\n }\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n */\n private getControllerFilepath(): string | undefined {\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = this.controllerClass.name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get auth metadata for a specific method, falling back to class-level.\n */\n getAuthMetaForMethod(methodName: string): AuthMeta | undefined {\n return getAuthMeta(this.apiMetaClass, methodName);\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): ClassType<TApi> {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerClass(): ClassType<TController> {\n return this.controllerClass;\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { RouteMetadata } from '@webpieces/http-api';
1
+ import { RouteMetadata, AuthMeta } from '@webpieces/http-api';
2
2
  /**
3
3
  * Metadata about the method being invoked.
4
4
  * Passed to filters and contains request information.
@@ -42,12 +42,17 @@ export declare class MethodMeta {
42
42
  * The deserialized request DTO.
43
43
  */
44
44
  requestDto?: unknown;
45
+ /**
46
+ * Auth metadata from @Public/@Authenticated/@Roles decorators.
47
+ * Populated by ApiRoutingFactory so filters can read auth requirements.
48
+ */
49
+ authMeta?: AuthMeta;
45
50
  /**
46
51
  * Additional metadata for storing request-scoped data.
47
52
  * Used by filters to pass data to other filters/controllers.
48
53
  */
49
54
  metadata: Map<string, unknown>;
50
- constructor(routeMeta: RouteMetadata, requestHeaders?: Map<string, string[]>, requestDto?: unknown, metadata?: Map<string, unknown>);
55
+ constructor(routeMeta: RouteMetadata, requestHeaders?: Map<string, string[]>, requestDto?: unknown, metadata?: Map<string, unknown>, authMeta?: AuthMeta);
51
56
  /**
52
57
  * Get the HTTP method (convenience accessor).
53
58
  */
package/src/MethodMeta.js CHANGED
@@ -14,11 +14,12 @@ exports.MethodMeta = void 0;
14
14
  * - metadata: Request-scoped data for filters to communicate
15
15
  */
16
16
  class MethodMeta {
17
- constructor(routeMeta, requestHeaders, requestDto, metadata) {
17
+ constructor(routeMeta, requestHeaders, requestDto, metadata, authMeta) {
18
18
  this.routeMeta = routeMeta;
19
19
  this.requestHeaders = requestHeaders;
20
20
  this.requestDto = requestDto;
21
21
  this.metadata = metadata ?? new Map();
22
+ this.authMeta = authMeta ?? routeMeta.authMeta;
22
23
  }
23
24
  /**
24
25
  * Get the HTTP method (convenience accessor).
@@ -1 +1 @@
1
- {"version":3,"file":"MethodMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/MethodMeta.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;GAWG;AACH,MAAa,UAAU;IAwCnB,YACI,SAAwB,EACxB,cAAsC,EACtC,UAAoB,EACpB,QAA+B;QAE/B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,GAAG,EAAE,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;CACJ;AAxED,gCAwEC","sourcesContent":["import { RouteMetadata } from '@webpieces/http-api';\n\n/**\n * Metadata about the method being invoked.\n * Passed to filters and contains request information.\n *\n * MethodMeta is DTO-only - it does NOT contain Express req/res directly.\n *\n * Fields:\n * - routeMeta: Static route information (httpMethod, path, methodName)\n * - requestHeaders: HTTP headers from the request (NEW)\n * - requestDto: The deserialized request body\n * - metadata: Request-scoped data for filters to communicate\n */\nexport class MethodMeta {\n /**\n * Route metadata (httpMethod, path, methodName, parameterTypes)\n */\n routeMeta: RouteMetadata;\n\n /**\n * HTTP headers from the request.\n * Map of header name (lowercase) -> array of values.\n *\n * HTTP spec allows multiple values for same header name,\n * so we store as string[] (even though most headers have single value).\n *\n * LIFECYCLE:\n * 1. Set by ExpressWrapper BEFORE filter chain executes\n * 2. ContextFilter (priority 2000) transfers headers to RequestContext\n * 3. ContextFilter CLEARS this field (sets to undefined) after transfer\n * 4. ALL FILTERS AFTER ContextFilter will see this as UNDEFINED\n *\n * IMPORTANT: Downstream filters should NOT read from requestHeaders!\n * Instead, use RequestContext.getHeader() to read headers after ContextFilter.\n *\n * Example (correct usage in downstream filters):\n * ```typescript\n * const requestId = RequestContext.getHeader(WebpiecesCoreHeaders.REQUEST_ID);\n * ```\n */\n public requestHeaders?: Map<string, string[]>;\n\n /**\n * The deserialized request DTO.\n */\n requestDto?: unknown;\n\n /**\n * Additional metadata for storing request-scoped data.\n * Used by filters to pass data to other filters/controllers.\n */\n metadata: Map<string, unknown>;\n\n constructor(\n routeMeta: RouteMetadata,\n requestHeaders?: Map<string, string[]>,\n requestDto?: unknown,\n metadata?: Map<string, unknown>,\n ) {\n this.routeMeta = routeMeta;\n this.requestHeaders = requestHeaders;\n this.requestDto = requestDto;\n this.metadata = metadata ?? new Map();\n }\n\n /**\n * Get the HTTP method (convenience accessor).\n */\n get httpMethod(): string {\n return this.routeMeta.httpMethod;\n }\n\n /**\n * Get the request path (convenience accessor).\n */\n get path(): string {\n return this.routeMeta.path;\n }\n\n /**\n * Get the method name (convenience accessor).\n */\n get methodName(): string {\n return this.routeMeta.methodName;\n }\n}\n"]}
1
+ {"version":3,"file":"MethodMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/MethodMeta.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;GAWG;AACH,MAAa,UAAU;IA8CnB,YACI,SAAwB,EACxB,cAAsC,EACtC,UAAoB,EACpB,QAA+B,EAC/B,QAAmB;QAEnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,GAAG,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;CACJ;AAhFD,gCAgFC","sourcesContent":["import { RouteMetadata, AuthMeta } from '@webpieces/http-api';\n\n/**\n * Metadata about the method being invoked.\n * Passed to filters and contains request information.\n *\n * MethodMeta is DTO-only - it does NOT contain Express req/res directly.\n *\n * Fields:\n * - routeMeta: Static route information (httpMethod, path, methodName)\n * - requestHeaders: HTTP headers from the request (NEW)\n * - requestDto: The deserialized request body\n * - metadata: Request-scoped data for filters to communicate\n */\nexport class MethodMeta {\n /**\n * Route metadata (httpMethod, path, methodName, parameterTypes)\n */\n routeMeta: RouteMetadata;\n\n /**\n * HTTP headers from the request.\n * Map of header name (lowercase) -> array of values.\n *\n * HTTP spec allows multiple values for same header name,\n * so we store as string[] (even though most headers have single value).\n *\n * LIFECYCLE:\n * 1. Set by ExpressWrapper BEFORE filter chain executes\n * 2. ContextFilter (priority 2000) transfers headers to RequestContext\n * 3. ContextFilter CLEARS this field (sets to undefined) after transfer\n * 4. ALL FILTERS AFTER ContextFilter will see this as UNDEFINED\n *\n * IMPORTANT: Downstream filters should NOT read from requestHeaders!\n * Instead, use RequestContext.getHeader() to read headers after ContextFilter.\n *\n * Example (correct usage in downstream filters):\n * ```typescript\n * const requestId = RequestContext.getHeader(WebpiecesCoreHeaders.REQUEST_ID);\n * ```\n */\n public requestHeaders?: Map<string, string[]>;\n\n /**\n * The deserialized request DTO.\n */\n requestDto?: unknown;\n\n /**\n * Auth metadata from @Public/@Authenticated/@Roles decorators.\n * Populated by ApiRoutingFactory so filters can read auth requirements.\n */\n authMeta?: AuthMeta;\n\n /**\n * Additional metadata for storing request-scoped data.\n * Used by filters to pass data to other filters/controllers.\n */\n metadata: Map<string, unknown>;\n\n constructor(\n routeMeta: RouteMetadata,\n requestHeaders?: Map<string, string[]>,\n requestDto?: unknown,\n metadata?: Map<string, unknown>,\n authMeta?: AuthMeta,\n ) {\n this.routeMeta = routeMeta;\n this.requestHeaders = requestHeaders;\n this.requestDto = requestDto;\n this.metadata = metadata ?? new Map();\n this.authMeta = authMeta ?? routeMeta.authMeta;\n }\n\n /**\n * Get the HTTP method (convenience accessor).\n */\n get httpMethod(): string {\n return this.routeMeta.httpMethod;\n }\n\n /**\n * Get the request path (convenience accessor).\n */\n get path(): string {\n return this.routeMeta.path;\n }\n\n /**\n * Get the method name (convenience accessor).\n */\n get methodName(): string {\n return this.routeMeta.methodName;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { ApiInterface, Get, Post, Put, Delete, Patch, Path, getRoutes, isApiInterface, RouteMetadata, METADATA_KEYS, ValidateImplementation, } from '@webpieces/http-api';
1
+ export { ApiPath, Endpoint, Authentication, AuthenticationConfig, getApiPath, getEndpoints, isApiPath, getAuthMeta, AuthMeta, RouteMetadata, METADATA_KEYS, ValidateImplementation, } from '@webpieces/http-api';
2
2
  export { Controller, isController, provideSingleton, provideTransient, ROUTING_METADATA_KEYS, } from './decorators';
3
- export { RESTApiRoutes, ClassType } from './RESTApiRoutes';
3
+ export { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';
4
4
  export { WebAppMeta, WEBAPP_META_TOKEN, Routes, RouteBuilder, RouteDefinition, FilterDefinition, } from './WebAppMeta';
5
5
  export { MethodMeta } from './MethodMeta';
6
6
  export { RouteHandler } from './RouteHandler';
package/src/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.RouteHandler = exports.MethodMeta = exports.FilterDefinition = exports.RouteDefinition = exports.WEBAPP_META_TOKEN = exports.RESTApiRoutes = exports.ROUTING_METADATA_KEYS = exports.provideTransient = exports.provideSingleton = exports.isController = exports.Controller = exports.METADATA_KEYS = exports.RouteMetadata = exports.isApiInterface = exports.getRoutes = exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = void 0;
3
+ exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.RouteHandler = exports.MethodMeta = exports.FilterDefinition = exports.RouteDefinition = exports.WEBAPP_META_TOKEN = exports.ApiRoutingFactory = exports.ROUTING_METADATA_KEYS = exports.provideTransient = exports.provideSingleton = exports.isController = exports.Controller = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = void 0;
4
4
  // Re-export API decorators from http-api for convenience
5
5
  var http_api_1 = require("@webpieces/http-api");
6
- Object.defineProperty(exports, "ApiInterface", { enumerable: true, get: function () { return http_api_1.ApiInterface; } });
7
- Object.defineProperty(exports, "Get", { enumerable: true, get: function () { return http_api_1.Get; } });
8
- Object.defineProperty(exports, "Post", { enumerable: true, get: function () { return http_api_1.Post; } });
9
- Object.defineProperty(exports, "Put", { enumerable: true, get: function () { return http_api_1.Put; } });
10
- Object.defineProperty(exports, "Delete", { enumerable: true, get: function () { return http_api_1.Delete; } });
11
- Object.defineProperty(exports, "Patch", { enumerable: true, get: function () { return http_api_1.Patch; } });
12
- Object.defineProperty(exports, "Path", { enumerable: true, get: function () { return http_api_1.Path; } });
13
- Object.defineProperty(exports, "getRoutes", { enumerable: true, get: function () { return http_api_1.getRoutes; } });
14
- Object.defineProperty(exports, "isApiInterface", { enumerable: true, get: function () { return http_api_1.isApiInterface; } });
6
+ Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return http_api_1.ApiPath; } });
7
+ Object.defineProperty(exports, "Endpoint", { enumerable: true, get: function () { return http_api_1.Endpoint; } });
8
+ Object.defineProperty(exports, "Authentication", { enumerable: true, get: function () { return http_api_1.Authentication; } });
9
+ Object.defineProperty(exports, "AuthenticationConfig", { enumerable: true, get: function () { return http_api_1.AuthenticationConfig; } });
10
+ Object.defineProperty(exports, "getApiPath", { enumerable: true, get: function () { return http_api_1.getApiPath; } });
11
+ Object.defineProperty(exports, "getEndpoints", { enumerable: true, get: function () { return http_api_1.getEndpoints; } });
12
+ Object.defineProperty(exports, "isApiPath", { enumerable: true, get: function () { return http_api_1.isApiPath; } });
13
+ Object.defineProperty(exports, "getAuthMeta", { enumerable: true, get: function () { return http_api_1.getAuthMeta; } });
14
+ Object.defineProperty(exports, "AuthMeta", { enumerable: true, get: function () { return http_api_1.AuthMeta; } });
15
15
  Object.defineProperty(exports, "RouteMetadata", { enumerable: true, get: function () { return http_api_1.RouteMetadata; } });
16
16
  Object.defineProperty(exports, "METADATA_KEYS", { enumerable: true, get: function () { return http_api_1.METADATA_KEYS; } });
17
17
  // Server-side routing decorators and utilities
@@ -21,8 +21,8 @@ Object.defineProperty(exports, "isController", { enumerable: true, get: function
21
21
  Object.defineProperty(exports, "provideSingleton", { enumerable: true, get: function () { return decorators_1.provideSingleton; } });
22
22
  Object.defineProperty(exports, "provideTransient", { enumerable: true, get: function () { return decorators_1.provideTransient; } });
23
23
  Object.defineProperty(exports, "ROUTING_METADATA_KEYS", { enumerable: true, get: function () { return decorators_1.ROUTING_METADATA_KEYS; } });
24
- var RESTApiRoutes_1 = require("./RESTApiRoutes");
25
- Object.defineProperty(exports, "RESTApiRoutes", { enumerable: true, get: function () { return RESTApiRoutes_1.RESTApiRoutes; } });
24
+ var ApiRoutingFactory_1 = require("./ApiRoutingFactory");
25
+ Object.defineProperty(exports, "ApiRoutingFactory", { enumerable: true, get: function () { return ApiRoutingFactory_1.ApiRoutingFactory; } });
26
26
  // Core routing types (moved from core-meta)
27
27
  var WebAppMeta_1 = require("./WebAppMeta");
28
28
  Object.defineProperty(exports, "WEBAPP_META_TOKEN", { enumerable: true, get: function () { return WebAppMeta_1.WEBAPP_META_TOKEN; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAyD;AACzD,gDAa6B;AAZzB,wGAAA,YAAY,OAAA;AACZ,+FAAA,GAAG,OAAA;AACH,gGAAA,IAAI,OAAA;AACJ,+FAAA,GAAG,OAAA;AACH,kGAAA,MAAM,OAAA;AACN,iGAAA,KAAK,OAAA;AACL,gGAAA,IAAI,OAAA;AACJ,qGAAA,SAAS,OAAA;AACT,0GAAA,cAAc,OAAA;AACd,yGAAA,aAAa,OAAA;AACb,yGAAA,aAAa,OAAA;AAIjB,+CAA+C;AAC/C,2CAMsB;AALlB,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,8GAAA,gBAAgB,OAAA;AAChB,8GAAA,gBAAgB,OAAA;AAChB,mHAAA,qBAAqB,OAAA;AAGzB,iDAA2D;AAAlD,8GAAA,aAAa,OAAA;AAEtB,4CAA4C;AAC5C,2CAOsB;AALlB,+GAAA,iBAAiB,OAAA;AAGjB,6GAAA,eAAe,OAAA;AACf,8GAAA,gBAAgB,OAAA;AAGpB,oCAAoC;AACpC,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+BAA+B;AAC/B,uDAK4B;AAJxB,oHAAA,gBAAgB,OAAA;AAChB,wHAAA,oBAAoB,OAAA;AACpB,kHAAA,cAAc,OAAA;AAIlB,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAEtB,iCAAiC;AACjC,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAE7B,uBAAuB;AACvB,qDAA4E;AAAnE,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA","sourcesContent":["// Re-export API decorators from http-api for convenience\nexport {\n ApiInterface,\n Get,\n Post,\n Put,\n Delete,\n Patch,\n Path,\n getRoutes,\n isApiInterface,\n RouteMetadata,\n METADATA_KEYS,\n ValidateImplementation,\n} from '@webpieces/http-api';\n\n// Server-side routing decorators and utilities\nexport {\n Controller,\n isController,\n provideSingleton,\n provideTransient,\n ROUTING_METADATA_KEYS,\n} from './decorators';\n\nexport { RESTApiRoutes, ClassType } from './RESTApiRoutes';\n\n// Core routing types (moved from core-meta)\nexport {\n WebAppMeta,\n WEBAPP_META_TOKEN,\n Routes,\n RouteBuilder,\n RouteDefinition,\n FilterDefinition,\n} from './WebAppMeta';\n\n// Method metadata and route handler\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// Route builder implementation\nexport {\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n ExpressRouteHandler,\n} from './RouteBuilderImpl';\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// Context readers (Node.js only)\nexport { RequestContextReader } from './RequestContextReader';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAyD;AACzD,gDAa6B;AAZzB,mGAAA,OAAO,OAAA;AACP,oGAAA,QAAQ,OAAA;AACR,0GAAA,cAAc,OAAA;AACd,gHAAA,oBAAoB,OAAA;AACpB,sGAAA,UAAU,OAAA;AACV,wGAAA,YAAY,OAAA;AACZ,qGAAA,SAAS,OAAA;AACT,uGAAA,WAAW,OAAA;AACX,oGAAA,QAAQ,OAAA;AACR,yGAAA,aAAa,OAAA;AACb,yGAAA,aAAa,OAAA;AAIjB,+CAA+C;AAC/C,2CAMsB;AALlB,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,8GAAA,gBAAgB,OAAA;AAChB,8GAAA,gBAAgB,OAAA;AAChB,mHAAA,qBAAqB,OAAA;AAGzB,yDAAmE;AAA1D,sHAAA,iBAAiB,OAAA;AAE1B,4CAA4C;AAC5C,2CAOsB;AALlB,+GAAA,iBAAiB,OAAA;AAGjB,6GAAA,eAAe,OAAA;AACf,8GAAA,gBAAgB,OAAA;AAGpB,oCAAoC;AACpC,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+BAA+B;AAC/B,uDAK4B;AAJxB,oHAAA,gBAAgB,OAAA;AAChB,wHAAA,oBAAoB,OAAA;AACpB,kHAAA,cAAc,OAAA;AAIlB,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAEtB,iCAAiC;AACjC,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA;AAE7B,uBAAuB;AACvB,qDAA4E;AAAnE,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA","sourcesContent":["// Re-export API decorators from http-api for convenience\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n ValidateImplementation,\n} from '@webpieces/http-api';\n\n// Server-side routing decorators and utilities\nexport {\n Controller,\n isController,\n provideSingleton,\n provideTransient,\n ROUTING_METADATA_KEYS,\n} from './decorators';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types (moved from core-meta)\nexport {\n WebAppMeta,\n WEBAPP_META_TOKEN,\n Routes,\n RouteBuilder,\n RouteDefinition,\n FilterDefinition,\n} from './WebAppMeta';\n\n// Method metadata and route handler\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// Route builder implementation\nexport {\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n ExpressRouteHandler,\n} from './RouteBuilderImpl';\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// Context readers (Node.js only)\nexport { RequestContextReader } from './RequestContextReader';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n"]}
@@ -1,77 +0,0 @@
1
- import { Routes, RouteBuilder } from './WebAppMeta';
2
- /**
3
- * Type representing a class constructor (abstract or concrete).
4
- */
5
- export type ClassType<T = any> = Function & {
6
- prototype: T;
7
- };
8
- /**
9
- * RESTApiRoutes - Automatically wire API interfaces to controllers.
10
- * Similar to Java WebPieces RESTApiRoutes.
11
- *
12
- * This class uses reflection (reflect-metadata) to read decorators from
13
- * an API interface class and automatically register routes that dispatch
14
- * to the corresponding controller methods.
15
- *
16
- * Usage:
17
- * ```typescript
18
- * // In your ServerMeta:
19
- * getRoutes(): Routes[] {
20
- * return [
21
- * new RESTApiRoutes(SaveApiPrototype, SaveController),
22
- * // ... more routes
23
- * ];
24
- * }
25
- * ```
26
- *
27
- * The API interface and controller must follow this pattern:
28
- * - API interface class has @ApiInterface() decorator
29
- * - Methods have @Post()/@Get()/etc and @Path() decorators
30
- * - Controller class implements the same interface
31
- * - Controller class has @Controller() decorator
32
- *
33
- * Type Parameters:
34
- * - TApi: The API prototype class type (abstract class with decorators)
35
- * - TController: The controller class type (must extend TApi)
36
- */
37
- export declare class RESTApiRoutes<TApi = any, TController extends TApi = any> implements Routes {
38
- private apiMetaClass;
39
- private controllerClass;
40
- /**
41
- * Create a new RESTApiRoutes.
42
- *
43
- * @param apiMetaClass - The API interface class with decorators (e.g., SaveApiPrototype)
44
- * @param controllerClass - The controller class that implements the API (e.g., SaveController)
45
- */
46
- constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>);
47
- /**
48
- * Validate that the controller implements all methods from the API interface.
49
- */
50
- private validateControllerImplementsApi;
51
- /**
52
- * Configure routes by reading metadata from the API interface.
53
- */
54
- configure(routeBuilder: RouteBuilder): void;
55
- /**
56
- * Register a single route with the route builder.
57
- */
58
- private registerRoute;
59
- /**
60
- * Get the filepath of the controller source file.
61
- * Uses a heuristic based on the controller class name.
62
- *
63
- * Since TypeScript doesn't provide source file paths at runtime,
64
- * we use the class name to create a pattern that filters can match against.
65
- *
66
- * @returns Filepath pattern or undefined
67
- */
68
- private getControllerFilepath;
69
- /**
70
- * Get the API interface class.
71
- */
72
- getApiClass(): any;
73
- /**
74
- * Get the controller class.
75
- */
76
- getControllerClass(): any;
77
- }
@@ -1,126 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RESTApiRoutes = void 0;
4
- const WebAppMeta_1 = require("./WebAppMeta");
5
- const http_api_1 = require("@webpieces/http-api");
6
- const decorators_1 = require("./decorators");
7
- /**
8
- * RESTApiRoutes - Automatically wire API interfaces to controllers.
9
- * Similar to Java WebPieces RESTApiRoutes.
10
- *
11
- * This class uses reflection (reflect-metadata) to read decorators from
12
- * an API interface class and automatically register routes that dispatch
13
- * to the corresponding controller methods.
14
- *
15
- * Usage:
16
- * ```typescript
17
- * // In your ServerMeta:
18
- * getRoutes(): Routes[] {
19
- * return [
20
- * new RESTApiRoutes(SaveApiPrototype, SaveController),
21
- * // ... more routes
22
- * ];
23
- * }
24
- * ```
25
- *
26
- * The API interface and controller must follow this pattern:
27
- * - API interface class has @ApiInterface() decorator
28
- * - Methods have @Post()/@Get()/etc and @Path() decorators
29
- * - Controller class implements the same interface
30
- * - Controller class has @Controller() decorator
31
- *
32
- * Type Parameters:
33
- * - TApi: The API prototype class type (abstract class with decorators)
34
- * - TController: The controller class type (must extend TApi)
35
- */
36
- class RESTApiRoutes {
37
- /**
38
- * Create a new RESTApiRoutes.
39
- *
40
- * @param apiMetaClass - The API interface class with decorators (e.g., SaveApiPrototype)
41
- * @param controllerClass - The controller class that implements the API (e.g., SaveController)
42
- */
43
- constructor(apiMetaClass, controllerClass) {
44
- this.apiMetaClass = apiMetaClass;
45
- this.controllerClass = controllerClass;
46
- // Validate that apiMetaClass is marked as @ApiInterface
47
- if (!(0, http_api_1.isApiInterface)(apiMetaClass)) {
48
- const className = apiMetaClass.name || 'Unknown';
49
- throw new Error(`Class ${className} must be decorated with @ApiInterface()`);
50
- }
51
- // Validate that controllerClass implements the methods from apiMetaClass
52
- this.validateControllerImplementsApi();
53
- }
54
- /**
55
- * Validate that the controller implements all methods from the API interface.
56
- */
57
- validateControllerImplementsApi() {
58
- const routes = (0, http_api_1.getRoutes)(this.apiMetaClass);
59
- for (const route of routes) {
60
- const controllerPrototype = this.controllerClass.prototype;
61
- if (typeof controllerPrototype[route.methodName] !== 'function') {
62
- const controllerName = this.controllerClass.name || 'Unknown';
63
- const apiName = this.apiMetaClass.name || 'Unknown';
64
- throw new Error(`Controller ${controllerName} must implement method ${route.methodName} from API ${apiName}`);
65
- }
66
- }
67
- }
68
- /**
69
- * Configure routes by reading metadata from the API interface.
70
- */
71
- configure(routeBuilder) {
72
- const routes = (0, http_api_1.getRoutes)(this.apiMetaClass);
73
- for (const route of routes) {
74
- this.registerRoute(routeBuilder, route);
75
- }
76
- }
77
- /**
78
- * Register a single route with the route builder.
79
- */
80
- registerRoute(routeBuilder, route) {
81
- if (!route.httpMethod || !route.path) {
82
- const apiName = this.apiMetaClass.name || 'Unknown';
83
- throw new Error(`Method ${route.methodName} in ${apiName} must have both @HttpMethod and @Path decorators`);
84
- }
85
- // Set controller class name for logging
86
- route.controllerClassName = this.controllerClass.name;
87
- // Extract controller filepath for filter matching
88
- const controllerFilepath = this.getControllerFilepath();
89
- // Pass controller class and method name to RouteBuilder
90
- // RouteBuilder will resolve the controller from DI and create the handler
91
- routeBuilder.addRoute(new WebAppMeta_1.RouteDefinition(route, this.controllerClass, controllerFilepath));
92
- }
93
- /**
94
- * Get the filepath of the controller source file.
95
- * Uses a heuristic based on the controller class name.
96
- *
97
- * Since TypeScript doesn't provide source file paths at runtime,
98
- * we use the class name to create a pattern that filters can match against.
99
- *
100
- * @returns Filepath pattern or undefined
101
- */
102
- getControllerFilepath() {
103
- // Check for explicit @SourceFile decorator metadata
104
- const filepath = Reflect.getMetadata(decorators_1.ROUTING_METADATA_KEYS.SOURCE_FILEPATH, this.controllerClass);
105
- if (filepath) {
106
- return filepath;
107
- }
108
- // Fallback to class name pattern
109
- const className = this.controllerClass.name;
110
- return className ? `**/${className}.ts` : undefined;
111
- }
112
- /**
113
- * Get the API interface class.
114
- */
115
- getApiClass() {
116
- return this.apiMetaClass;
117
- }
118
- /**
119
- * Get the controller class.
120
- */
121
- getControllerClass() {
122
- return this.controllerClass;
123
- }
124
- }
125
- exports.RESTApiRoutes = RESTApiRoutes;
126
- //# sourceMappingURL=RESTApiRoutes.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RESTApiRoutes.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RESTApiRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["import { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport { getRoutes, isApiInterface, RouteMetadata } from '@webpieces/http-api';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\n/**\n * Type representing a class constructor (abstract or concrete).\n */\nexport type ClassType<T = any> = Function & { prototype: T };\n\n/**\n * RESTApiRoutes - Automatically wire API interfaces to controllers.\n * Similar to Java WebPieces RESTApiRoutes.\n *\n * This class uses reflection (reflect-metadata) to read decorators from\n * an API interface class and automatically register routes that dispatch\n * to the corresponding controller methods.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new RESTApiRoutes(SaveApiPrototype, SaveController),\n * // ... more routes\n * ];\n * }\n * ```\n *\n * The API interface and controller must follow this pattern:\n * - API interface class has @ApiInterface() decorator\n * - Methods have @Post()/@Get()/etc and @Path() decorators\n * - Controller class implements the same interface\n * - Controller class has @Controller() decorator\n *\n * Type Parameters:\n * - TApi: The API prototype class type (abstract class with decorators)\n * - TController: The controller class type (must extend TApi)\n */\nexport class RESTApiRoutes<TApi = any, TController extends TApi = any> implements Routes {\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController>;\n\n /**\n * Create a new RESTApiRoutes.\n *\n * @param apiMetaClass - The API interface class with decorators (e.g., SaveApiPrototype)\n * @param controllerClass - The controller class that implements the API (e.g., SaveController)\n */\n constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>) {\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked as @ApiInterface\n if (!isApiInterface(apiMetaClass)) {\n const className = (apiMetaClass as any).name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiInterface()`);\n }\n\n // Validate that controllerClass implements the methods from apiMetaClass\n this.validateControllerImplementsApi();\n }\n\n /**\n * Validate that the controller implements all methods from the API interface.\n */\n private validateControllerImplementsApi(): void {\n const routes = getRoutes(this.apiMetaClass);\n\n for (const route of routes) {\n const controllerPrototype = this.controllerClass.prototype;\n\n if (typeof controllerPrototype[route.methodName] !== 'function') {\n const controllerName = (this.controllerClass as any).name || 'Unknown';\n const apiName = (this.apiMetaClass as any).name || 'Unknown';\n throw new Error(\n `Controller ${controllerName} must implement method ${route.methodName} from API ${apiName}`,\n );\n }\n }\n }\n\n /**\n * Configure routes by reading metadata from the API interface.\n */\n configure(routeBuilder: RouteBuilder): void {\n const routes = getRoutes(this.apiMetaClass);\n\n for (const route of routes) {\n this.registerRoute(routeBuilder, route);\n }\n }\n\n /**\n * Register a single route with the route builder.\n */\n private registerRoute(routeBuilder: RouteBuilder, route: RouteMetadata): void {\n if (!route.httpMethod || !route.path) {\n const apiName = (this.apiMetaClass as any).name || 'Unknown';\n throw new Error(\n `Method ${route.methodName} in ${apiName} must have both @HttpMethod and @Path decorators`,\n );\n }\n\n // Set controller class name for logging\n route.controllerClassName = (this.controllerClass as any).name;\n\n // Extract controller filepath for filter matching\n const controllerFilepath = this.getControllerFilepath();\n\n // Pass controller class and method name to RouteBuilder\n // RouteBuilder will resolve the controller from DI and create the handler\n routeBuilder.addRoute(new RouteDefinition(route, this.controllerClass, controllerFilepath));\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n *\n * Since TypeScript doesn't provide source file paths at runtime,\n * we use the class name to create a pattern that filters can match against.\n *\n * @returns Filepath pattern or undefined\n */\n private getControllerFilepath(): string | undefined {\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = (this.controllerClass as any).name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): any {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerClass(): any {\n return this.controllerClass;\n }\n}\n"]}