@webpieces/http-routing 0.2.8 → 0.2.10

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.8",
3
+ "version": "0.2.10",
4
4
  "description": "Decorator-based routing with auto-wiring for WebPieces",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -21,7 +21,7 @@
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "@webpieces/core-meta": "0.2.8",
25
- "@webpieces/http-api": "0.2.8"
24
+ "@webpieces/core-meta": "0.2.10",
25
+ "@webpieces/http-api": "0.2.10"
26
26
  }
27
27
  }
@@ -69,6 +69,16 @@ export declare class RESTApiRoutes<TApi = any, TController extends TApi = any> i
69
69
  * from the method signature on the API interface.
70
70
  */
71
71
  private createRouteHandler;
72
+ /**
73
+ * Get the filepath of the controller source file.
74
+ * Uses a heuristic based on the controller class name.
75
+ *
76
+ * Since TypeScript doesn't provide source file paths at runtime,
77
+ * we use the class name to create a pattern that filters can match against.
78
+ *
79
+ * @returns Filepath pattern or undefined
80
+ */
81
+ private getControllerFilepath;
72
82
  /**
73
83
  * Get the API interface class.
74
84
  */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RESTApiRoutes = void 0;
4
4
  const core_meta_1 = require("@webpieces/core-meta");
5
5
  const http_api_1 = require("@webpieces/http-api");
6
+ const decorators_1 = require("./decorators");
6
7
  /**
7
8
  * RESTApiRoutes - Automatically wire API interfaces to controllers.
8
9
  * Similar to Java WebPieces RESTApiRoutes.
@@ -81,14 +82,12 @@ class RESTApiRoutes {
81
82
  const apiName = this.apiMetaClass.name || 'Unknown';
82
83
  throw new Error(`Method ${route.methodName} in ${apiName} must have both @HttpMethod and @Path decorators`);
83
84
  }
85
+ // Extract controller filepath for filter matching
86
+ const controllerFilepath = this.getControllerFilepath();
84
87
  // Create typed route handler
85
88
  // The handler's return type is inferred from the controller method's return type
86
89
  const routeHandler = this.createRouteHandler(route);
87
- routeBuilder.addRoute({
88
- method: route.httpMethod,
89
- path: route.path,
90
- handler: routeHandler,
91
- });
90
+ routeBuilder.addRoute(new core_meta_1.RouteDefinition(route.httpMethod, route.path, routeHandler, controllerFilepath));
92
91
  }
93
92
  /**
94
93
  * Create a typed route handler for a specific route.
@@ -123,6 +122,25 @@ class RESTApiRoutes {
123
122
  }
124
123
  };
125
124
  }
125
+ /**
126
+ * Get the filepath of the controller source file.
127
+ * Uses a heuristic based on the controller class name.
128
+ *
129
+ * Since TypeScript doesn't provide source file paths at runtime,
130
+ * we use the class name to create a pattern that filters can match against.
131
+ *
132
+ * @returns Filepath pattern or undefined
133
+ */
134
+ getControllerFilepath() {
135
+ // Check for explicit @SourceFile decorator metadata
136
+ const filepath = Reflect.getMetadata(decorators_1.ROUTING_METADATA_KEYS.SOURCE_FILEPATH, this.controllerClass);
137
+ if (filepath) {
138
+ return filepath;
139
+ }
140
+ // Fallback to class name pattern
141
+ const className = this.controllerClass.name;
142
+ return className ? `**/${className}.ts` : undefined;
143
+ }
126
144
  /**
127
145
  * Get the API interface class.
128
146
  */
@@ -1 +1 @@
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, RouteHandler, RouteContext } from '@webpieces/core-meta';\nimport { getRoutes, isApiInterface, RouteMetadata } from '@webpieces/http-api';\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(\n `Class ${className} must be decorated with @ApiInterface()`\n );\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 // Create typed route handler\n // The handler's return type is inferred from the controller method's return type\n const routeHandler: RouteHandler<unknown> = this.createRouteHandler(route);\n\n routeBuilder.addRoute({\n method: route.httpMethod,\n path: route.path,\n handler: routeHandler,\n });\n }\n\n /**\n * Create a typed route handler for a specific route.\n *\n * The handler:\n * 1. Resolves the controller from the DI container\n * 2. Invokes the controller method with extracted parameters\n * 3. Returns the controller method result\n *\n * Type parameter TResult represents the return type of the controller method.\n * At runtime, we can't enforce this statically, but TypeScript will infer it\n * from the method signature on the API interface.\n */\n private createRouteHandler<TResult = unknown>(route: RouteMetadata): RouteHandler<TResult> {\n const controllerClass = this.controllerClass;\n\n return new class extends RouteHandler<TResult> {\n async execute(context: RouteContext): Promise<TResult> {\n const { container, params } = context;\n\n // Resolve controller instance from DI container\n const controller = container.get(controllerClass) as TController;\n\n // Get the controller method\n const method = (controller as any)[route.methodName];\n if (typeof method !== 'function') {\n const controllerName = (controllerClass as any).name || 'Unknown';\n throw new Error(\n `Method ${route.methodName} not found on controller ${controllerName}`\n );\n }\n\n // Invoke the method with parameters and return the result\n // TypeScript trusts that the method returns Promise<TResult> based on\n // the interface definition (e.g., SaveApi.save returns Promise<SaveResponse>)\n const result: TResult = await method.apply(controller, params);\n\n return result;\n }\n };\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"]}
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, RouteHandler, RouteContext, RouteDefinition } from '@webpieces/core-meta';\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(\n `Class ${className} must be decorated with @ApiInterface()`\n );\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 // Extract controller filepath for filter matching\n const controllerFilepath = this.getControllerFilepath();\n\n // Create typed route handler\n // The handler's return type is inferred from the controller method's return type\n const routeHandler: RouteHandler<unknown> = this.createRouteHandler(route);\n\n routeBuilder.addRoute(\n new RouteDefinition(\n route.httpMethod,\n route.path,\n routeHandler,\n controllerFilepath\n )\n );\n }\n\n /**\n * Create a typed route handler for a specific route.\n *\n * The handler:\n * 1. Resolves the controller from the DI container\n * 2. Invokes the controller method with extracted parameters\n * 3. Returns the controller method result\n *\n * Type parameter TResult represents the return type of the controller method.\n * At runtime, we can't enforce this statically, but TypeScript will infer it\n * from the method signature on the API interface.\n */\n private createRouteHandler<TResult = unknown>(route: RouteMetadata): RouteHandler<TResult> {\n const controllerClass = this.controllerClass;\n\n return new class extends RouteHandler<TResult> {\n async execute(context: RouteContext): Promise<TResult> {\n const { container, params } = context;\n\n // Resolve controller instance from DI container\n const controller = container.get(controllerClass) as TController;\n\n // Get the controller method\n const method = (controller as any)[route.methodName];\n if (typeof method !== 'function') {\n const controllerName = (controllerClass as any).name || 'Unknown';\n throw new Error(\n `Method ${route.methodName} not found on controller ${controllerName}`\n );\n }\n\n // Invoke the method with parameters and return the result\n // TypeScript trusts that the method returns Promise<TResult> based on\n // the interface definition (e.g., SaveApi.save returns Promise<SaveResponse>)\n const result: TResult = await method.apply(controller, params);\n\n return result;\n }\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 * 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"]}
@@ -5,6 +5,7 @@ import 'reflect-metadata';
5
5
  */
6
6
  export declare const ROUTING_METADATA_KEYS: {
7
7
  CONTROLLER: string;
8
+ SOURCE_FILEPATH: string;
8
9
  };
9
10
  /**
10
11
  * @Controller decorator to mark a class as a controller.
@@ -24,6 +25,20 @@ export declare function Controller(): ClassDecorator;
24
25
  * Server-side only.
25
26
  */
26
27
  export declare function isController(controllerClass: any): boolean;
28
+ /**
29
+ * SourceFile decorator to explicitly set the source filepath for a controller.
30
+ * This is used by filter matching to determine which filters apply to the controller.
31
+ *
32
+ * If not specified, the system will use a heuristic based on class name.
33
+ *
34
+ * Usage:
35
+ * @SourceFile('src/controllers/admin/UserController.ts')
36
+ * @Controller()
37
+ * export class UserController implements UserApi
38
+ *
39
+ * @param filepath - The source filepath of the controller
40
+ */
41
+ export declare function SourceFile(filepath: string): ClassDecorator;
27
42
  /**
28
43
  * Provides a singleton-scoped dependency.
29
44
  * When called without arguments, the decorated class binds to itself.
package/src/decorators.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ROUTING_METADATA_KEYS = void 0;
4
4
  exports.Controller = Controller;
5
5
  exports.isController = isController;
6
+ exports.SourceFile = SourceFile;
6
7
  exports.provideSingleton = provideSingleton;
7
8
  exports.provideTransient = provideTransient;
8
9
  require("reflect-metadata");
@@ -13,6 +14,7 @@ const binding_decorators_1 = require("@inversifyjs/binding-decorators");
13
14
  */
14
15
  exports.ROUTING_METADATA_KEYS = {
15
16
  CONTROLLER: 'webpieces:controller',
17
+ SOURCE_FILEPATH: 'webpieces:source-filepath',
16
18
  };
17
19
  /**
18
20
  * @Controller decorator to mark a class as a controller.
@@ -38,6 +40,24 @@ function Controller() {
38
40
  function isController(controllerClass) {
39
41
  return Reflect.getMetadata(exports.ROUTING_METADATA_KEYS.CONTROLLER, controllerClass) === true;
40
42
  }
43
+ /**
44
+ * SourceFile decorator to explicitly set the source filepath for a controller.
45
+ * This is used by filter matching to determine which filters apply to the controller.
46
+ *
47
+ * If not specified, the system will use a heuristic based on class name.
48
+ *
49
+ * Usage:
50
+ * @SourceFile('src/controllers/admin/UserController.ts')
51
+ * @Controller()
52
+ * export class UserController implements UserApi
53
+ *
54
+ * @param filepath - The source filepath of the controller
55
+ */
56
+ function SourceFile(filepath) {
57
+ return (target) => {
58
+ Reflect.defineMetadata(exports.ROUTING_METADATA_KEYS.SOURCE_FILEPATH, filepath, target);
59
+ };
60
+ }
41
61
  /**
42
62
  * Provides a singleton-scoped dependency.
43
63
  * When called without arguments, the decorated class binds to itself.
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/decorators.ts"],"names":[],"mappings":";;;AAuBA,gCAIC;AAMD,oCAEC;AAiBD,4CAIC;AAiBD,4CAIC;AA7ED,4BAA0B;AAC1B,wEAA0D;AAE1D;;;GAGG;AACU,QAAA,qBAAqB,GAAG;IACnC,UAAU,EAAE,sBAAsB;CACnC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,SAAgB,UAAU;IACxB,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,CAAC,cAAc,CAAC,6BAAqB,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAAC,eAAoB;IAC/C,OAAO,OAAO,CAAC,WAAW,CAAC,6BAAqB,CAAC,UAAU,EAAE,eAAe,CAAC,KAAK,IAAI,CAAC;AACzF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,IAAA,4BAAO,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,IAAA,4BAAO,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import 'reflect-metadata';\nimport { provide } from '@inversifyjs/binding-decorators';\n\n/**\n * Metadata keys for server-side routing.\n * These are specific to the routing package (server-side only).\n */\nexport const ROUTING_METADATA_KEYS = {\n CONTROLLER: 'webpieces:controller',\n};\n\n/**\n * @Controller decorator to mark a class as a controller.\n * This is a server-side only decorator.\n *\n * Usage:\n * ```typescript\n * @Controller()\n * export class SaveController implements SaveApi {\n * // ...\n * }\n * ```\n */\nexport function Controller(): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(ROUTING_METADATA_KEYS.CONTROLLER, true, target);\n };\n}\n\n/**\n * Helper function to check if a class is a controller.\n * Server-side only.\n */\nexport function isController(controllerClass: any): boolean {\n return Reflect.getMetadata(ROUTING_METADATA_KEYS.CONTROLLER, controllerClass) === true;\n}\n\n/**\n * Provides a singleton-scoped dependency.\n * When called without arguments, the decorated class binds to itself.\n *\n * Server-side only - registers classes in the DI container.\n *\n * Usage:\n * ```typescript\n * @provideSingleton()\n * @Controller()\n * export class SaveController {\n * // ...\n * }\n * ```\n */\nexport function provideSingleton() {\n return (target: any) => {\n return provide(target, (bind) => bind.inSingletonScope())(target);\n };\n}\n\n/**\n * Provides a transient-scoped dependency (new instance every time).\n * When called without arguments, the decorated class binds to itself.\n *\n * Server-side only - registers classes in the DI container.\n *\n * Usage:\n * ```typescript\n * @provideTransient()\n * @Controller()\n * export class TransientController {\n * // ...\n * }\n * ```\n */\nexport function provideTransient() {\n return (target: any) => {\n return provide(target)(target);\n };\n}\n"]}
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/decorators.ts"],"names":[],"mappings":";;;AAwBA,gCAIC;AAMD,oCAEC;AAeD,gCAQC;AAiBD,4CAIC;AAiBD,4CAIC;AArGD,4BAA0B;AAC1B,wEAA0D;AAE1D;;;GAGG;AACU,QAAA,qBAAqB,GAAG;IACnC,UAAU,EAAE,sBAAsB;IAClC,eAAe,EAAE,2BAA2B;CAC7C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,SAAgB,UAAU;IACxB,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,CAAC,cAAc,CAAC,6BAAqB,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAAC,eAAoB;IAC/C,OAAO,OAAO,CAAC,WAAW,CAAC,6BAAqB,CAAC,UAAU,EAAE,eAAe,CAAC,KAAK,IAAI,CAAC;AACzF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,UAAU,CAAC,QAAgB;IACzC,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,CAAC,cAAc,CACpB,6BAAqB,CAAC,eAAe,EACrC,QAAQ,EACR,MAAM,CACP,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,IAAA,4BAAO,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACpE,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,OAAO,IAAA,4BAAO,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import 'reflect-metadata';\nimport { provide } from '@inversifyjs/binding-decorators';\n\n/**\n * Metadata keys for server-side routing.\n * These are specific to the routing package (server-side only).\n */\nexport const ROUTING_METADATA_KEYS = {\n CONTROLLER: 'webpieces:controller',\n SOURCE_FILEPATH: 'webpieces:source-filepath',\n};\n\n/**\n * @Controller decorator to mark a class as a controller.\n * This is a server-side only decorator.\n *\n * Usage:\n * ```typescript\n * @Controller()\n * export class SaveController implements SaveApi {\n * // ...\n * }\n * ```\n */\nexport function Controller(): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(ROUTING_METADATA_KEYS.CONTROLLER, true, target);\n };\n}\n\n/**\n * Helper function to check if a class is a controller.\n * Server-side only.\n */\nexport function isController(controllerClass: any): boolean {\n return Reflect.getMetadata(ROUTING_METADATA_KEYS.CONTROLLER, controllerClass) === true;\n}\n\n/**\n * SourceFile decorator to explicitly set the source filepath for a controller.\n * This is used by filter matching to determine which filters apply to the controller.\n *\n * If not specified, the system will use a heuristic based on class name.\n *\n * Usage:\n * @SourceFile('src/controllers/admin/UserController.ts')\n * @Controller()\n * export class UserController implements UserApi\n *\n * @param filepath - The source filepath of the controller\n */\nexport function SourceFile(filepath: string): ClassDecorator {\n return (target: any) => {\n Reflect.defineMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n filepath,\n target\n );\n };\n}\n\n/**\n * Provides a singleton-scoped dependency.\n * When called without arguments, the decorated class binds to itself.\n *\n * Server-side only - registers classes in the DI container.\n *\n * Usage:\n * ```typescript\n * @provideSingleton()\n * @Controller()\n * export class SaveController {\n * // ...\n * }\n * ```\n */\nexport function provideSingleton() {\n return (target: any) => {\n return provide(target, (bind) => bind.inSingletonScope())(target);\n };\n}\n\n/**\n * Provides a transient-scoped dependency (new instance every time).\n * When called without arguments, the decorated class binds to itself.\n *\n * Server-side only - registers classes in the DI container.\n *\n * Usage:\n * ```typescript\n * @provideTransient()\n * @Controller()\n * export class TransientController {\n * // ...\n * }\n * ```\n */\nexport function provideTransient() {\n return (target: any) => {\n return provide(target)(target);\n };\n}\n"]}
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RESTApiRoutes = exports.ROUTING_METADATA_KEYS = exports.provideTransient = exports.provideSingleton = exports.isController = exports.Controller = exports.METADATA_KEYS = exports.isApiInterface = exports.getRoutes = exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = void 0;
3
+ 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;
4
4
  // Re-export API decorators from http-api for convenience
5
5
  var http_api_1 = require("@webpieces/http-api");
6
6
  Object.defineProperty(exports, "ApiInterface", { enumerable: true, get: function () { return http_api_1.ApiInterface; } });
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "Patch", { enumerable: true, get: function () { r
12
12
  Object.defineProperty(exports, "Path", { enumerable: true, get: function () { return http_api_1.Path; } });
13
13
  Object.defineProperty(exports, "getRoutes", { enumerable: true, get: function () { return http_api_1.getRoutes; } });
14
14
  Object.defineProperty(exports, "isApiInterface", { enumerable: true, get: function () { return http_api_1.isApiInterface; } });
15
+ Object.defineProperty(exports, "RouteMetadata", { enumerable: true, get: function () { return http_api_1.RouteMetadata; } });
15
16
  Object.defineProperty(exports, "METADATA_KEYS", { enumerable: true, get: function () { return http_api_1.METADATA_KEYS; } });
16
17
  // Server-side routing decorators and utilities
17
18
  var decorators_1 = require("./decorators");
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;AAZ3B,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;AAEd,yGAAA,aAAa,OAAA;AAIf,+CAA+C;AAC/C,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,8GAAA,gBAAgB,OAAA;AAChB,8GAAA,gBAAgB,OAAA;AAChB,mHAAA,qBAAqB,OAAA;AAGvB,iDAA2D;AAAlD,8GAAA,aAAa,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"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAyD;AACzD,gDAa6B;AAZ3B,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;AAIf,+CAA+C;AAC/C,2CAMsB;AALpB,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,8GAAA,gBAAgB,OAAA;AAChB,8GAAA,gBAAgB,OAAA;AAChB,mHAAA,qBAAqB,OAAA;AAGvB,iDAA2D;AAAlD,8GAAA,aAAa,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"]}