@webpieces/http-routing 0.2.92 → 0.2.94
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 +3 -3
- package/src/ApiRoutingFactory.d.ts +57 -0
- package/src/ApiRoutingFactory.js +100 -0
- package/src/ApiRoutingFactory.js.map +1 -0
- package/src/MethodMeta.d.ts +7 -2
- package/src/MethodMeta.js +2 -1
- package/src/MethodMeta.js.map +1 -1
- package/src/index.d.ts +2 -2
- package/src/index.js +12 -12
- package/src/index.js.map +1 -1
- package/src/RESTApiRoutes.d.ts +0 -77
- package/src/RESTApiRoutes.js +0 -126
- package/src/RESTApiRoutes.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-routing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.94",
|
|
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.
|
|
25
|
-
"@webpieces/http-filters": "0.2.
|
|
24
|
+
"@webpieces/http-api": "0.2.94",
|
|
25
|
+
"@webpieces/http-filters": "0.2.94",
|
|
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(SaveApi, 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(SaveApi, 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(SaveApi, 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"]}
|
package/src/MethodMeta.d.ts
CHANGED
|
@@ -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).
|
package/src/MethodMeta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MethodMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/MethodMeta.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;GAWG;AACH,MAAa,UAAU;
|
|
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 {
|
|
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 {
|
|
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.
|
|
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, "
|
|
7
|
-
Object.defineProperty(exports, "
|
|
8
|
-
Object.defineProperty(exports, "
|
|
9
|
-
Object.defineProperty(exports, "
|
|
10
|
-
Object.defineProperty(exports, "
|
|
11
|
-
Object.defineProperty(exports, "
|
|
12
|
-
Object.defineProperty(exports, "
|
|
13
|
-
Object.defineProperty(exports, "
|
|
14
|
-
Object.defineProperty(exports, "
|
|
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
|
|
25
|
-
Object.defineProperty(exports, "
|
|
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,
|
|
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"]}
|
package/src/RESTApiRoutes.d.ts
DELETED
|
@@ -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
|
-
}
|
package/src/RESTApiRoutes.js
DELETED
|
@@ -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
|
package/src/RESTApiRoutes.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RESTApiRoutes.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RESTApiRoutes.ts"],"names":[],"mappings":";;;AAAA,6CAAqE;AACrE,kDAA+E;AAC/E,6CAAqD;AAOrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAa,aAAa;IAItB;;;;;OAKG;IACH,YAAY,YAA6B,EAAE,eAAuC;QAC9E,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,wDAAwD;QACxD,IAAI,CAAC,IAAA,yBAAc,EAAC,YAAY,CAAC,EAAE,CAAC;YAChC,MAAM,SAAS,GAAI,YAAoB,CAAC,IAAI,IAAI,SAAS,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,SAAS,SAAS,yCAAyC,CAAC,CAAC;QACjF,CAAC;QAED,yEAAyE;QACzE,IAAI,CAAC,+BAA+B,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACK,+BAA+B;QACnC,MAAM,MAAM,GAAG,IAAA,oBAAS,EAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC;YAE3D,IAAI,OAAO,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,UAAU,EAAE,CAAC;gBAC9D,MAAM,cAAc,GAAI,IAAI,CAAC,eAAuB,CAAC,IAAI,IAAI,SAAS,CAAC;gBACvE,MAAM,OAAO,GAAI,IAAI,CAAC,YAAoB,CAAC,IAAI,IAAI,SAAS,CAAC;gBAC7D,MAAM,IAAI,KAAK,CACX,cAAc,cAAc,0BAA0B,KAAK,CAAC,UAAU,aAAa,OAAO,EAAE,CAC/F,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,YAA0B;QAChC,MAAM,MAAM,GAAG,IAAA,oBAAS,EAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE5C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,YAA0B,EAAE,KAAoB;QAClE,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,OAAO,GAAI,IAAI,CAAC,YAAoB,CAAC,IAAI,IAAI,SAAS,CAAC;YAC7D,MAAM,IAAI,KAAK,CACX,UAAU,KAAK,CAAC,UAAU,OAAO,OAAO,kDAAkD,CAC7F,CAAC;QACN,CAAC;QAED,wCAAwC;QACxC,KAAK,CAAC,mBAAmB,GAAI,IAAI,CAAC,eAAuB,CAAC,IAAI,CAAC;QAE/D,kDAAkD;QAClD,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAExD,wDAAwD;QACxD,0EAA0E;QAC1E,YAAY,CAAC,QAAQ,CAAC,IAAI,4BAAe,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAChG,CAAC;IAED;;;;;;;;OAQG;IACK,qBAAqB;QACzB,oDAAoD;QACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAChC,kCAAqB,CAAC,eAAe,EACrC,IAAI,CAAC,eAAe,CACvB,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACX,OAAO,QAAQ,CAAC;QACpB,CAAC;QAED,iCAAiC;QACjC,MAAM,SAAS,GAAI,IAAI,CAAC,eAAuB,CAAC,IAAI,CAAC;QACrD,OAAO,SAAS,CAAC,CAAC,CAAC,MAAM,SAAS,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,CAAC;IAED;;OAEG;IACH,WAAW;QACP,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,kBAAkB;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;CACJ;AAjHD,sCAiHC","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"]}
|