@webpieces/http-server 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-server",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "WebPieces server with filter chain and dependency injection",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,8 +22,8 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-meta": "0.2.8",
26
- "@webpieces/http-routing": "0.2.8",
27
- "@webpieces/http-filters": "0.2.8"
25
+ "@webpieces/core-meta": "0.2.10",
26
+ "@webpieces/http-routing": "0.2.10",
27
+ "@webpieces/http-filters": "0.2.10"
28
28
  }
29
29
  }
@@ -0,0 +1,37 @@
1
+ import { Filter } from '@webpieces/http-filters';
2
+ import { FilterDefinition } from '@webpieces/core-meta';
3
+ /**
4
+ * FilterMatcher - Matches filters to routes based on filepath patterns.
5
+ * Similar to Java SharedMatchUtil.findMatchingFilters().
6
+ *
7
+ * Responsibilities:
8
+ * 1. Filter based on filepath glob pattern matching
9
+ * 2. Sort matching filters by priority (higher first)
10
+ *
11
+ * Differences from Java:
12
+ * - Uses glob patterns instead of regex
13
+ * - Only matches filepaths (no URL path or HTTPS filtering yet)
14
+ * - Simpler API focused on one responsibility
15
+ */
16
+ export declare class FilterMatcher {
17
+ /**
18
+ * Find filters that match the given controller filepath.
19
+ *
20
+ * @param controllerFilepath - The filepath of the controller source file
21
+ * @param allFilters - All registered filters with their definitions
22
+ * @returns Array of matching filters, sorted by priority (highest first)
23
+ */
24
+ static findMatchingFilters(controllerFilepath: string | undefined, allFilters: Array<{
25
+ filter: Filter;
26
+ definition: FilterDefinition;
27
+ }>): Filter[];
28
+ /**
29
+ * Normalize a controller filepath for consistent matching.
30
+ * - Converts backslashes to forward slashes (Windows compatibility)
31
+ * - Removes leading './'
32
+ *
33
+ * @param filepath - Raw filepath
34
+ * @returns Normalized filepath
35
+ */
36
+ static normalizeFilepath(filepath: string): string;
37
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterMatcher = void 0;
4
+ const minimatch_1 = require("minimatch");
5
+ /**
6
+ * FilterMatcher - Matches filters to routes based on filepath patterns.
7
+ * Similar to Java SharedMatchUtil.findMatchingFilters().
8
+ *
9
+ * Responsibilities:
10
+ * 1. Filter based on filepath glob pattern matching
11
+ * 2. Sort matching filters by priority (higher first)
12
+ *
13
+ * Differences from Java:
14
+ * - Uses glob patterns instead of regex
15
+ * - Only matches filepaths (no URL path or HTTPS filtering yet)
16
+ * - Simpler API focused on one responsibility
17
+ */
18
+ class FilterMatcher {
19
+ /**
20
+ * Find filters that match the given controller filepath.
21
+ *
22
+ * @param controllerFilepath - The filepath of the controller source file
23
+ * @param allFilters - All registered filters with their definitions
24
+ * @returns Array of matching filters, sorted by priority (highest first)
25
+ */
26
+ static findMatchingFilters(controllerFilepath, allFilters) {
27
+ const matchingFilters = [];
28
+ for (const { filter, definition } of allFilters) {
29
+ const pattern = definition.filepathPattern;
30
+ // Special case: '*' matches all controllers (global filter)
31
+ if (pattern === '*') {
32
+ matchingFilters.push({ filter, priority: filter.priority });
33
+ continue;
34
+ }
35
+ // If no filepath available, only match wildcard patterns
36
+ if (!controllerFilepath) {
37
+ if (pattern === '**/*') {
38
+ matchingFilters.push({ filter, priority: filter.priority });
39
+ }
40
+ continue;
41
+ }
42
+ // Normalize filepath for consistent matching
43
+ const normalizedPath = FilterMatcher.normalizeFilepath(controllerFilepath);
44
+ // Match using minimatch
45
+ if ((0, minimatch_1.minimatch)(normalizedPath, pattern)) {
46
+ matchingFilters.push({ filter, priority: filter.priority });
47
+ }
48
+ }
49
+ // Sort by priority (highest first)
50
+ matchingFilters.sort((a, b) => b.priority - a.priority);
51
+ return matchingFilters.map((item) => item.filter);
52
+ }
53
+ /**
54
+ * Normalize a controller filepath for consistent matching.
55
+ * - Converts backslashes to forward slashes (Windows compatibility)
56
+ * - Removes leading './'
57
+ *
58
+ * @param filepath - Raw filepath
59
+ * @returns Normalized filepath
60
+ */
61
+ static normalizeFilepath(filepath) {
62
+ return filepath
63
+ .replace(/\\/g, '/') // Windows backslashes to forward slashes
64
+ .replace(/^\.\//, ''); // Remove leading './'
65
+ }
66
+ }
67
+ exports.FilterMatcher = FilterMatcher;
68
+ //# sourceMappingURL=FilterMatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterMatcher.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/FilterMatcher.ts"],"names":[],"mappings":";;;AAEA,yCAAsC;AAEtC;;;;;;;;;;;;GAYG;AACH,MAAa,aAAa;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CACxB,kBAAsC,EACtC,UAAmE;QAEnE,MAAM,eAAe,GAAgD,EAAE,CAAC;QAExE,KAAK,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,UAAU,EAAE,CAAC;YAChD,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC;YAE3C,4DAA4D;YAC5D,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;gBACpB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC5D,SAAS;YACX,CAAC;YAED,yDAAyD;YACzD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACxB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;oBACvB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC9D,CAAC;gBACD,SAAS;YACX,CAAC;YAED,6CAA6C;YAC7C,MAAM,cAAc,GAAG,aAAa,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;YAE3E,wBAAwB;YACxB,IAAI,IAAA,qBAAS,EAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC;gBACvC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QAExD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAgB;QACvC,OAAO,QAAQ;aACZ,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,yCAAyC;aAC7D,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB;IACjD,CAAC;CACF;AA3DD,sCA2DC","sourcesContent":["import { Filter } from '@webpieces/http-filters';\nimport { FilterDefinition } from '@webpieces/core-meta';\nimport { minimatch } from 'minimatch';\n\n/**\n * FilterMatcher - Matches filters to routes based on filepath patterns.\n * Similar to Java SharedMatchUtil.findMatchingFilters().\n *\n * Responsibilities:\n * 1. Filter based on filepath glob pattern matching\n * 2. Sort matching filters by priority (higher first)\n *\n * Differences from Java:\n * - Uses glob patterns instead of regex\n * - Only matches filepaths (no URL path or HTTPS filtering yet)\n * - Simpler API focused on one responsibility\n */\nexport class FilterMatcher {\n /**\n * Find filters that match the given controller filepath.\n *\n * @param controllerFilepath - The filepath of the controller source file\n * @param allFilters - All registered filters with their definitions\n * @returns Array of matching filters, sorted by priority (highest first)\n */\n static findMatchingFilters(\n controllerFilepath: string | undefined,\n allFilters: Array<{ filter: Filter; definition: FilterDefinition }>\n ): Filter[] {\n const matchingFilters: Array<{ filter: Filter; priority: number }> = [];\n\n for (const { filter, definition } of allFilters) {\n const pattern = definition.filepathPattern;\n\n // Special case: '*' matches all controllers (global filter)\n if (pattern === '*') {\n matchingFilters.push({ filter, priority: filter.priority });\n continue;\n }\n\n // If no filepath available, only match wildcard patterns\n if (!controllerFilepath) {\n if (pattern === '**/*') {\n matchingFilters.push({ filter, priority: filter.priority });\n }\n continue;\n }\n\n // Normalize filepath for consistent matching\n const normalizedPath = FilterMatcher.normalizeFilepath(controllerFilepath);\n\n // Match using minimatch\n if (minimatch(normalizedPath, pattern)) {\n matchingFilters.push({ filter, priority: filter.priority });\n }\n }\n\n // Sort by priority (highest first)\n matchingFilters.sort((a, b) => b.priority - a.priority);\n\n return matchingFilters.map((item) => item.filter);\n }\n\n /**\n * Normalize a controller filepath for consistent matching.\n * - Converts backslashes to forward slashes (Windows compatibility)\n * - Removes leading './'\n *\n * @param filepath - Raw filepath\n * @returns Normalized filepath\n */\n static normalizeFilepath(filepath: string): string {\n return filepath\n .replace(/\\\\/g, '/') // Windows backslashes to forward slashes\n .replace(/^\\.\\//, ''); // Remove leading './'\n }\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { Container } from 'inversify';
2
- import { RouteBuilder, RouteDefinition, FilterDefinition } from '@webpieces/core-meta';
2
+ import { RouteBuilder, RouteDefinition, FilterDefinition, RouteHandler } from '@webpieces/core-meta';
3
3
  import { Filter } from '@webpieces/http-filters';
4
- import { RouteMetadata } from "@webpieces/http-api";
4
+ import { RouteMetadata } from '@webpieces/http-api';
5
5
  /**
6
6
  * Registered route entry in the route registry.
7
7
  *
@@ -12,9 +12,10 @@ import { RouteMetadata } from "@webpieces/http-api";
12
12
  * Each route has its own TResult type, but we can't store different
13
13
  * generic types in the same Map, so we use unknown as a type-safe escape hatch.
14
14
  */
15
- export interface RegisteredRoute<TResult = unknown> extends RouteDefinition<TResult> {
15
+ export declare class RegisteredRoute<TResult = unknown> extends RouteDefinition<TResult> {
16
16
  routeMetadata?: RouteMetadata;
17
17
  controllerClass?: any;
18
+ constructor(method: string, path: string, handler: RouteHandler<TResult>, controllerFilepath?: string, routeMetadata?: RouteMetadata, controllerClass?: any);
18
19
  }
19
20
  /**
20
21
  * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.
@@ -30,16 +31,19 @@ export interface RegisteredRoute<TResult = unknown> extends RouteDefinition<TRes
30
31
  */
31
32
  export declare class RouteBuilderImpl implements RouteBuilder {
32
33
  private routes;
33
- private filters;
34
+ private filterRegistry;
34
35
  private container;
35
36
  /**
36
37
  * Create a new RouteBuilder.
37
38
  *
38
39
  * @param routes - Map to store registered routes (keyed by "METHOD:path")
39
- * @param filters - Array to store registered filters
40
+ * @param filterRegistry - Array to store registered filters with their definitions
40
41
  * @param container - DI container for resolving filter instances
41
42
  */
42
- constructor(routes: Map<string, RegisteredRoute<unknown>>, filters: Filter[], container: Container);
43
+ constructor(routes: Map<string, RegisteredRoute<unknown>>, filterRegistry: Array<{
44
+ filter: Filter;
45
+ definition: FilterDefinition;
46
+ }>, container: Container);
43
47
  /**
44
48
  * Register a route with the router.
45
49
  *
@@ -52,10 +56,11 @@ export declare class RouteBuilderImpl implements RouteBuilder {
52
56
  /**
53
57
  * Register a filter with the filter chain.
54
58
  *
55
- * Filters are resolved from the DI container and added to the filter array.
56
- * They will be executed in priority order (higher priority first).
59
+ * Filters are resolved from the DI container and stored with their definitions.
60
+ * The definition includes pattern information used for route-specific filtering.
61
+ * Filters will be matched and executed in priority order (higher priority first).
57
62
  *
58
- * @param filterDef - Filter definition with priority and filter class
63
+ * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern
59
64
  */
60
65
  addFilter(filterDef: FilterDefinition): void;
61
66
  }
@@ -1,6 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RouteBuilderImpl = void 0;
3
+ exports.RouteBuilderImpl = exports.RegisteredRoute = void 0;
4
+ const core_meta_1 = require("@webpieces/core-meta");
5
+ /**
6
+ * Registered route entry in the route registry.
7
+ *
8
+ * We use unknown instead of any for better type safety:
9
+ * - unknown forces type checking at usage points
10
+ * - any allows unsafe operations without checks
11
+ *
12
+ * Each route has its own TResult type, but we can't store different
13
+ * generic types in the same Map, so we use unknown as a type-safe escape hatch.
14
+ */
15
+ class RegisteredRoute extends core_meta_1.RouteDefinition {
16
+ constructor(method, path, handler, controllerFilepath, routeMetadata, controllerClass) {
17
+ super(method, path, handler, controllerFilepath);
18
+ this.routeMetadata = routeMetadata;
19
+ this.controllerClass = controllerClass;
20
+ }
21
+ }
22
+ exports.RegisteredRoute = RegisteredRoute;
4
23
  /**
5
24
  * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.
6
25
  *
@@ -18,12 +37,12 @@ class RouteBuilderImpl {
18
37
  * Create a new RouteBuilder.
19
38
  *
20
39
  * @param routes - Map to store registered routes (keyed by "METHOD:path")
21
- * @param filters - Array to store registered filters
40
+ * @param filterRegistry - Array to store registered filters with their definitions
22
41
  * @param container - DI container for resolving filter instances
23
42
  */
24
- constructor(routes, filters, container) {
43
+ constructor(routes, filterRegistry, container) {
25
44
  this.routes = routes;
26
- this.filters = filters;
45
+ this.filterRegistry = filterRegistry;
27
46
  this.container = container;
28
47
  }
29
48
  /**
@@ -43,18 +62,19 @@ class RouteBuilderImpl {
43
62
  /**
44
63
  * Register a filter with the filter chain.
45
64
  *
46
- * Filters are resolved from the DI container and added to the filter array.
47
- * They will be executed in priority order (higher priority first).
65
+ * Filters are resolved from the DI container and stored with their definitions.
66
+ * The definition includes pattern information used for route-specific filtering.
67
+ * Filters will be matched and executed in priority order (higher priority first).
48
68
  *
49
- * @param filterDef - Filter definition with priority and filter class
69
+ * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern
50
70
  */
51
71
  addFilter(filterDef) {
52
72
  // Resolve filter instance from DI container
53
73
  const filter = this.container.get(filterDef.filterClass);
54
74
  // Set priority on the filter instance
55
75
  filter.priority = filterDef.priority;
56
- // Add to filters array (will be sorted by priority later)
57
- this.filters.push(filter);
76
+ // Store both filter instance and definition for pattern matching
77
+ this.filterRegistry.push({ filter, definition: filterDef });
58
78
  }
59
79
  }
60
80
  exports.RouteBuilderImpl = RouteBuilderImpl;
@@ -1 +1 @@
1
- {"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/RouteBuilderImpl.ts"],"names":[],"mappings":";;;AAoBA;;;;;;;;;;;GAWG;AACH,MAAa,gBAAgB;IAK3B;;;;;;OAMG;IACH,YACE,MAA6C,EAC7C,OAAiB,EACjB,SAAoB;QAEpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CAAoB,KAA+B;QACzD,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAE5C,+CAA+C;QAC/C,mEAAmE;QACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAiC,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,SAA2B;QACnC,4CAA4C;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,SAAS,CAAC,WAAW,CAAC,CAAC;QAEjE,sCAAsC;QACtC,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;QAErC,0DAA0D;QAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;CACF;AAxDD,4CAwDC","sourcesContent":["import { Container } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from '@webpieces/core-meta';\nimport { Filter } from '@webpieces/http-filters';\nimport {RouteMetadata} from \"@webpieces/http-api\";\n\n/**\n * Registered route entry in the route registry.\n *\n * We use unknown instead of any for better type safety:\n * - unknown forces type checking at usage points\n * - any allows unsafe operations without checks\n *\n * Each route has its own TResult type, but we can't store different\n * generic types in the same Map, so we use unknown as a type-safe escape hatch.\n */\nexport interface RegisteredRoute<TResult = unknown> extends RouteDefinition<TResult> {\n routeMetadata?: RouteMetadata;\n controllerClass?: any;\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n */\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: Map<string, RegisteredRoute<unknown>>;\n private filters: Filter[];\n private container: Container;\n\n /**\n * Create a new RouteBuilder.\n *\n * @param routes - Map to store registered routes (keyed by \"METHOD:path\")\n * @param filters - Array to store registered filters\n * @param container - DI container for resolving filter instances\n */\n constructor(\n routes: Map<string, RegisteredRoute<unknown>>,\n filters: Filter[],\n container: Container\n ) {\n this.routes = routes;\n this.filters = filters;\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * The route is stored with a key of \"METHOD:path\" (e.g., \"POST:/search/item\").\n * The TResult generic ensures type safety for the route's return type.\n *\n * @param route - Route definition with method, path, and handler\n */\n addRoute<TResult = unknown>(route: RouteDefinition<TResult>): void {\n const key = `${route.method}:${route.path}`;\n\n // Store as RegisteredRoute<unknown> in the map\n // Type safety is maintained through the generic on RouteDefinition\n this.routes.set(key, route as RegisteredRoute<unknown>);\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Filters are resolved from the DI container and added to the filter array.\n * They will be executed in priority order (higher priority first).\n *\n * @param filterDef - Filter definition with priority and filter class\n */\n addFilter(filterDef: FilterDefinition): void {\n // Resolve filter instance from DI container\n const filter = this.container.get<Filter>(filterDef.filterClass);\n\n // Set priority on the filter instance\n filter.priority = filterDef.priority;\n\n // Add to filters array (will be sorted by priority later)\n this.filters.push(filter);\n }\n}\n"]}
1
+ {"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/RouteBuilderImpl.ts"],"names":[],"mappings":";;;AACA,oDAAqG;AAIrG;;;;;;;;;GASG;AACH,MAAa,eAAmC,SAAQ,2BAAwB;IAI9E,YACE,MAAc,EACd,IAAY,EACZ,OAA8B,EAC9B,kBAA2B,EAC3B,aAA6B,EAC7B,eAAqB;QAErB,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;QACjD,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;CACF;AAhBD,0CAgBC;AAED;;;;;;;;;;;GAWG;AACH,MAAa,gBAAgB;IAK3B;;;;;;OAMG;IACH,YACE,MAA6C,EAC7C,cAAuE,EACvE,SAAoB;QAEpB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CAAoB,KAA+B;QACzD,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAE5C,+CAA+C;QAC/C,mEAAmE;QACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAiC,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,SAA2B;QACnC,4CAA4C;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAS,SAAS,CAAC,WAAW,CAAC,CAAC;QAEjE,sCAAsC;QACtC,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC;QAErC,iEAAiE;QACjE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF;AAzDD,4CAyDC","sourcesContent":["import { Container } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition, RouteHandler } from '@webpieces/core-meta';\nimport { Filter } from '@webpieces/http-filters';\nimport { RouteMetadata } from '@webpieces/http-api';\n\n/**\n * Registered route entry in the route registry.\n *\n * We use unknown instead of any for better type safety:\n * - unknown forces type checking at usage points\n * - any allows unsafe operations without checks\n *\n * Each route has its own TResult type, but we can't store different\n * generic types in the same Map, so we use unknown as a type-safe escape hatch.\n */\nexport class RegisteredRoute<TResult = unknown> extends RouteDefinition<TResult> {\n routeMetadata?: RouteMetadata;\n controllerClass?: any;\n\n constructor(\n method: string,\n path: string,\n handler: RouteHandler<TResult>,\n controllerFilepath?: string,\n routeMetadata?: RouteMetadata,\n controllerClass?: any\n ) {\n super(method, path, handler, controllerFilepath);\n this.routeMetadata = routeMetadata;\n this.controllerClass = controllerClass;\n }\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n */\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: Map<string, RegisteredRoute<unknown>>;\n private filterRegistry: Array<{ filter: Filter; definition: FilterDefinition }>;\n private container: Container;\n\n /**\n * Create a new RouteBuilder.\n *\n * @param routes - Map to store registered routes (keyed by \"METHOD:path\")\n * @param filterRegistry - Array to store registered filters with their definitions\n * @param container - DI container for resolving filter instances\n */\n constructor(\n routes: Map<string, RegisteredRoute<unknown>>,\n filterRegistry: Array<{ filter: Filter; definition: FilterDefinition }>,\n container: Container\n ) {\n this.routes = routes;\n this.filterRegistry = filterRegistry;\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * The route is stored with a key of \"METHOD:path\" (e.g., \"POST:/search/item\").\n * The TResult generic ensures type safety for the route's return type.\n *\n * @param route - Route definition with method, path, and handler\n */\n addRoute<TResult = unknown>(route: RouteDefinition<TResult>): void {\n const key = `${route.method}:${route.path}`;\n\n // Store as RegisteredRoute<unknown> in the map\n // Type safety is maintained through the generic on RouteDefinition\n this.routes.set(key, route as RegisteredRoute<unknown>);\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Filters are resolved from the DI container and stored with their definitions.\n * The definition includes pattern information used for route-specific filtering.\n * Filters will be matched and executed in priority order (higher priority first).\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n // Resolve filter instance from DI container\n const filter = this.container.get<Filter>(filterDef.filterClass);\n\n // Set priority on the filter instance\n filter.priority = filterDef.priority;\n\n // Store both filter instance and definition for pattern matching\n this.filterRegistry.push({ filter, definition: filterDef });\n }\n}\n"]}
@@ -55,9 +55,10 @@ export declare class WebpiecesServer {
55
55
  */
56
56
  private routes;
57
57
  /**
58
- * Registered filters, sorted by priority (higher priority first).
58
+ * Registered filters with their definitions.
59
+ * Used by FilterMatcher to match filters to routes based on filepath patterns.
59
60
  */
60
- private filters;
61
+ private filterRegistry;
61
62
  private initialized;
62
63
  private app?;
63
64
  private server?;
@@ -4,9 +4,11 @@ exports.WebpiecesServer = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const express_1 = tslib_1.__importDefault(require("express"));
6
6
  const inversify_1 = require("inversify");
7
+ const core_meta_1 = require("@webpieces/core-meta");
7
8
  const http_filters_1 = require("@webpieces/http-filters");
8
9
  const http_routing_1 = require("@webpieces/http-routing");
9
10
  const RouteBuilderImpl_1 = require("./RouteBuilderImpl");
11
+ const FilterMatcher_1 = require("./FilterMatcher");
10
12
  /**
11
13
  * WebpiecesServer - Main bootstrap class for WebPieces applications.
12
14
  *
@@ -50,9 +52,10 @@ class WebpiecesServer {
50
52
  */
51
53
  this.routes = new Map();
52
54
  /**
53
- * Registered filters, sorted by priority (higher priority first).
55
+ * Registered filters with their definitions.
56
+ * Used by FilterMatcher to match filters to routes based on filepath patterns.
54
57
  */
55
- this.filters = [];
58
+ this.filterRegistry = [];
56
59
  this.initialized = false;
57
60
  this.port = 8080;
58
61
  this.meta = meta;
@@ -107,7 +110,7 @@ class WebpiecesServer {
107
110
  const routeConfigs = this.meta.getRoutes();
108
111
  // Create explicit RouteBuilder implementation
109
112
  // Filters are resolved from appContainer (which has access to platformContainer too)
110
- const routeBuilder = new RouteBuilderImpl_1.RouteBuilderImpl(this.routes, this.filters, this.appContainer);
113
+ const routeBuilder = new RouteBuilderImpl_1.RouteBuilderImpl(this.routes, this.filterRegistry, this.appContainer);
111
114
  // Configure routes using the explicit RouteBuilder
112
115
  for (const routeConfig of routeConfigs) {
113
116
  routeConfig.configure(routeBuilder);
@@ -130,7 +133,7 @@ class WebpiecesServer {
130
133
  this.server = this.app.listen(this.port, () => {
131
134
  console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
132
135
  console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);
133
- console.log(`[WebpiecesServer] Registered ${this.filters.length} filters`);
136
+ console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);
134
137
  });
135
138
  }
136
139
  /**
@@ -144,34 +147,20 @@ class WebpiecesServer {
144
147
  const method = route.method.toLowerCase();
145
148
  const path = route.path;
146
149
  console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);
150
+ // Find matching filters for this route
151
+ const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, this.filterRegistry);
147
152
  // Create Express route handler
148
153
  const handler = async (req, res, next) => {
149
154
  try {
150
155
  // Create method metadata
151
- const meta = {
152
- httpMethod: route.method,
153
- path: route.path,
154
- methodName: key,
155
- params: [req.body],
156
- request: {
157
- body: req.body,
158
- query: req.query,
159
- params: req.params,
160
- headers: req.headers,
161
- },
162
- metadata: new Map(),
163
- };
164
- // Create filter chain
165
- const filterChain = new http_filters_1.FilterChain(this.filters);
156
+ const meta = new http_filters_1.MethodMeta(route.method, route.path, key, [req.body], new core_meta_1.RouteRequest(req.body, req.query, req.params, req.headers), undefined, new Map());
157
+ // Create filter chain with matched filters
158
+ const filterChain = new http_filters_1.FilterChain(matchingFilters);
166
159
  // Execute the filter chain
167
160
  const action = await filterChain.execute(meta, async () => {
168
161
  // Create typed route context
169
162
  // Use appContainer which has access to both app and framework bindings
170
- const routeContext = {
171
- container: this.appContainer,
172
- params: [req.body],
173
- request: meta.request,
174
- };
163
+ const routeContext = new core_meta_1.RouteContext(this.appContainer, [req.body], meta.request);
175
164
  // Final handler: invoke the controller method via route handler
176
165
  const result = await route.handler.execute(routeContext);
177
166
  // Wrap result in a JSON action
@@ -256,28 +245,18 @@ class WebpiecesServer {
256
245
  if (!registeredRoute) {
257
246
  throw new Error(`Route not found: ${key}`);
258
247
  }
248
+ // Find matching filters for this route
249
+ const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(registeredRoute.controllerFilepath, this.filterRegistry);
259
250
  // Create method metadata
260
- const meta = {
261
- httpMethod: route.httpMethod,
262
- path: route.path,
263
- methodName: route.methodName,
264
- params: [...args],
265
- request: {
266
- body: args[0], // Assume first arg is the request body
267
- },
268
- metadata: new Map(),
269
- };
270
- // Create filter chain
271
- const filterChain = new http_filters_1.FilterChain(this.filters);
251
+ const meta = new http_filters_1.MethodMeta(route.httpMethod, route.path, route.methodName, [...args], new core_meta_1.RouteRequest(args[0]), // Assume first arg is the request body
252
+ undefined, new Map());
253
+ // Create filter chain with matched filters
254
+ const filterChain = new http_filters_1.FilterChain(matchingFilters);
272
255
  // Execute the filter chain
273
256
  const action = await filterChain.execute(meta, async () => {
274
257
  // Create typed route context
275
258
  // Use appContainer which has access to both app and framework bindings
276
- const routeContext = {
277
- container: this.appContainer,
278
- params: meta.params,
279
- request: meta.request,
280
- };
259
+ const routeContext = new core_meta_1.RouteContext(this.appContainer, meta.params, meta.request);
281
260
  // Final handler: invoke the controller method via route handler
282
261
  const result = await registeredRoute.handler.execute(routeContext);
283
262
  // Wrap result in a JSON action
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Express, Request, Response, NextFunction } from 'express';\nimport { Container } from 'inversify';\nimport { WebAppMeta, RouteContext } from '@webpieces/core-meta';\nimport { FilterChain, Filter, MethodMeta, jsonAction } from '@webpieces/http-filters';\nimport { getRoutes, RouteMetadata } from '@webpieces/http-routing';\nimport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\n\n/**\n * WebpiecesServer - Main bootstrap class for WebPieces applications.\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * Usage for testing (no HTTP):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.initialize();\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n *\n * Usage for production (HTTP server):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.start(); // Starts Express server\n * ```\n */\nexport class WebpiecesServer {\n private meta: WebAppMeta;\n\n /**\n * WebPieces container: Core WebPieces framework bindings.\n * This includes framework-level services like filters, routing infrastructure,\n * logging, metrics, etc. Similar to Java WebPieces platform container.\n */\n private webpiecesContainer: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer: Container;\n\n /**\n * Routes registry: Maps \"METHOD:path\" -> RegisteredRoute\n * Example: \"POST:/search/item\" -> { method: \"POST\", path: \"/search/item\", handler: ... }\n *\n * We use unknown instead of any for type safety - each route has its own return type,\n * but we can't have different generic types in the same Map.\n */\n private routes: Map<string, RegisteredRoute<unknown>> = new Map();\n\n /**\n * Registered filters, sorted by priority (higher priority first).\n */\n private filters: Filter[] = [];\n\n private initialized = false;\n private app?: Express;\n private server?: any;\n private port: number = 8080;\n\n constructor(meta: WebAppMeta) {\n this.meta = meta;\n\n // Create WebPieces container for framework-level bindings\n this.webpiecesContainer = new Container();\n\n // Create application container as a child of WebPieces container\n // This allows app container to access framework bindings\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called automatically by start() or can be called manually for testing.\n */\n initialize(): void {\n if (this.initialized) {\n return;\n }\n\n // 1. Load DI modules\n this.loadDIModules();\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n */\n private loadDIModules(): void {\n const modules = this.meta.getDIModules();\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n this.appContainer.load(module);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Create explicit RouteBuilder implementation\n // Filters are resolved from appContainer (which has access to platformContainer too)\n const routeBuilder = new RouteBuilderImpl(\n this.routes,\n this.filters,\n this.appContainer\n );\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n */\n start(port: number = 8080): void {\n this.port = port;\n this.initialize();\n\n // Create Express app\n this.app = express();\n\n // Middleware\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // Register routes\n this.registerExpressRoutes();\n\n // Start listening\n this.server = this.app.listen(this.port, () => {\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);\n console.log(`[WebpiecesServer] Registered ${this.filters.length} filters`);\n });\n }\n\n /**\n * Register all routes with Express.\n */\n private registerExpressRoutes(): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n for (const [key, route] of this.routes.entries()) {\n const method = route.method.toLowerCase();\n const path = route.path;\n\n console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);\n\n // Create Express route handler\n const handler = async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Create method metadata\n const meta: MethodMeta = {\n httpMethod: route.method,\n path: route.path,\n methodName: key,\n params: [req.body],\n request: {\n body: req.body,\n query: req.query,\n params: req.params,\n headers: req.headers,\n },\n metadata: new Map(),\n };\n\n // Create filter chain\n const filterChain = new FilterChain(this.filters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext: RouteContext = {\n container: this.appContainer,\n params: [req.body],\n request: meta.request,\n };\n\n // Final handler: invoke the controller method via route handler\n const result = await route.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Send response\n if (action.type === 'json') {\n res.json(action.data);\n } else if (action.type === 'error') {\n res.status(500).json({ error: action.data });\n }\n } catch (error: any) {\n console.error('[WebpiecesServer] Error handling request:', error);\n res.status(500).json({ error: error.message });\n }\n };\n\n // Register with Express\n switch (method) {\n case 'get':\n this.app.get(path, handler);\n break;\n case 'post':\n this.app.post(path, handler);\n break;\n case 'put':\n this.app.put(path, handler);\n break;\n case 'delete':\n this.app.delete(path, handler);\n break;\n case 'patch':\n this.app.patch(path, handler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);\n }\n }\n }\n\n /**\n * Stop the HTTP server.\n */\n stop(): void {\n if (this.server) {\n this.server.close(() => {\n console.log('[WebpiecesServer] Server stopped');\n });\n }\n }\n\n /**\n * Create an API client proxy for testing (no HTTP).\n *\n * This creates a proxy object that implements the API interface\n * and routes method calls through the full filter chain to the controller.\n *\n * @param apiMetaClass - The API interface class with decorators\n * @returns Proxy object implementing the API interface\n */\n createApiClient<T>(apiMetaClass: any): T {\n this.initialize();\n\n // Get routes from the API metadata\n const routes = getRoutes(apiMetaClass);\n\n // Create a proxy object\n const proxy: any = {};\n\n for (const route of routes) {\n const methodName = route.methodName;\n\n // Create a function that routes through the filter chain\n proxy[methodName] = async (...args: any[]) => {\n return this.invokeRoute(route, args);\n };\n }\n\n return proxy as T;\n }\n\n /**\n * Invoke a route through the filter chain.\n */\n private async invokeRoute(route: RouteMetadata, args: any[]): Promise<any> {\n // Find the registered route\n const key = `${route.httpMethod}:${route.path}`;\n const registeredRoute = this.routes.get(key);\n\n if (!registeredRoute) {\n throw new Error(`Route not found: ${key}`);\n }\n\n // Create method metadata\n const meta: MethodMeta = {\n httpMethod: route.httpMethod,\n path: route.path,\n methodName: route.methodName,\n params: [...args],\n request: {\n body: args[0], // Assume first arg is the request body\n },\n metadata: new Map(),\n };\n\n // Create filter chain\n const filterChain = new FilterChain(this.filters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext: RouteContext = {\n container: this.appContainer,\n params: meta.params,\n request: meta.request,\n };\n\n // Final handler: invoke the controller method via route handler\n const result = await registeredRoute.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Return the data from the action\n if (action.type === 'error') {\n throw new Error(JSON.stringify(action.data));\n }\n\n return action.data;\n }\n\n /**\n * Get the application DI container (for testing).\n * Returns appContainer which has access to both app and framework bindings.\n */\n getContainer(): Container {\n this.initialize();\n return this.appContainer;\n }\n\n /**\n * Get the WebPieces framework container (for advanced testing/debugging).\n */\n getWebpiecesContainer(): Container {\n this.initialize();\n return this.webpiecesContainer;\n }\n\n\n}\n"]}
1
+ {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Express, Request, Response, NextFunction } from 'express';\nimport { Container } from 'inversify';\nimport { WebAppMeta, RouteContext, RouteRequest, FilterDefinition } from '@webpieces/core-meta';\nimport { FilterChain, Filter, MethodMeta, jsonAction } from '@webpieces/http-filters';\nimport { getRoutes, RouteMetadata } from '@webpieces/http-routing';\nimport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\nimport { FilterMatcher } from './FilterMatcher';\n\n/**\n * WebpiecesServer - Main bootstrap class for WebPieces applications.\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * Usage for testing (no HTTP):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.initialize();\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n *\n * Usage for production (HTTP server):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.start(); // Starts Express server\n * ```\n */\nexport class WebpiecesServer {\n private meta: WebAppMeta;\n\n /**\n * WebPieces container: Core WebPieces framework bindings.\n * This includes framework-level services like filters, routing infrastructure,\n * logging, metrics, etc. Similar to Java WebPieces platform container.\n */\n private webpiecesContainer: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer: Container;\n\n /**\n * Routes registry: Maps \"METHOD:path\" -> RegisteredRoute\n * Example: \"POST:/search/item\" -> { method: \"POST\", path: \"/search/item\", handler: ... }\n *\n * We use unknown instead of any for type safety - each route has its own return type,\n * but we can't have different generic types in the same Map.\n */\n private routes: Map<string, RegisteredRoute<unknown>> = new Map();\n\n /**\n * Registered filters with their definitions.\n * Used by FilterMatcher to match filters to routes based on filepath patterns.\n */\n private filterRegistry: Array<{ filter: Filter; definition: FilterDefinition }> = [];\n\n private initialized = false;\n private app?: Express;\n private server?: any;\n private port: number = 8080;\n\n constructor(meta: WebAppMeta) {\n this.meta = meta;\n\n // Create WebPieces container for framework-level bindings\n this.webpiecesContainer = new Container();\n\n // Create application container as a child of WebPieces container\n // This allows app container to access framework bindings\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called automatically by start() or can be called manually for testing.\n */\n initialize(): void {\n if (this.initialized) {\n return;\n }\n\n // 1. Load DI modules\n this.loadDIModules();\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n */\n private loadDIModules(): void {\n const modules = this.meta.getDIModules();\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n this.appContainer.load(module);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Create explicit RouteBuilder implementation\n // Filters are resolved from appContainer (which has access to platformContainer too)\n const routeBuilder = new RouteBuilderImpl(\n this.routes,\n this.filterRegistry,\n this.appContainer\n );\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n */\n start(port: number = 8080): void {\n this.port = port;\n this.initialize();\n\n // Create Express app\n this.app = express();\n\n // Middleware\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // Register routes\n this.registerExpressRoutes();\n\n // Start listening\n this.server = this.app.listen(this.port, () => {\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);\n console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);\n });\n }\n\n /**\n * Register all routes with Express.\n */\n private registerExpressRoutes(): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n for (const [key, route] of this.routes.entries()) {\n const method = route.method.toLowerCase();\n const path = route.path;\n\n console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n this.filterRegistry\n );\n\n // Create Express route handler\n const handler = async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Create method metadata\n const meta = new MethodMeta(\n route.method,\n route.path,\n key,\n [req.body],\n new RouteRequest(req.body, req.query, req.params, req.headers),\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n [req.body],\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await route.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Send response\n if (action.type === 'json') {\n res.json(action.data);\n } else if (action.type === 'error') {\n res.status(500).json({ error: action.data });\n }\n } catch (error: any) {\n console.error('[WebpiecesServer] Error handling request:', error);\n res.status(500).json({ error: error.message });\n }\n };\n\n // Register with Express\n switch (method) {\n case 'get':\n this.app.get(path, handler);\n break;\n case 'post':\n this.app.post(path, handler);\n break;\n case 'put':\n this.app.put(path, handler);\n break;\n case 'delete':\n this.app.delete(path, handler);\n break;\n case 'patch':\n this.app.patch(path, handler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);\n }\n }\n }\n\n /**\n * Stop the HTTP server.\n */\n stop(): void {\n if (this.server) {\n this.server.close(() => {\n console.log('[WebpiecesServer] Server stopped');\n });\n }\n }\n\n /**\n * Create an API client proxy for testing (no HTTP).\n *\n * This creates a proxy object that implements the API interface\n * and routes method calls through the full filter chain to the controller.\n *\n * @param apiMetaClass - The API interface class with decorators\n * @returns Proxy object implementing the API interface\n */\n createApiClient<T>(apiMetaClass: any): T {\n this.initialize();\n\n // Get routes from the API metadata\n const routes = getRoutes(apiMetaClass);\n\n // Create a proxy object\n const proxy: any = {};\n\n for (const route of routes) {\n const methodName = route.methodName;\n\n // Create a function that routes through the filter chain\n proxy[methodName] = async (...args: any[]) => {\n return this.invokeRoute(route, args);\n };\n }\n\n return proxy as T;\n }\n\n /**\n * Invoke a route through the filter chain.\n */\n private async invokeRoute(route: RouteMetadata, args: any[]): Promise<any> {\n // Find the registered route\n const key = `${route.httpMethod}:${route.path}`;\n const registeredRoute = this.routes.get(key);\n\n if (!registeredRoute) {\n throw new Error(`Route not found: ${key}`);\n }\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n registeredRoute.controllerFilepath,\n this.filterRegistry\n );\n\n // Create method metadata\n const meta = new MethodMeta(\n route.httpMethod,\n route.path,\n route.methodName,\n [...args],\n new RouteRequest(args[0]), // Assume first arg is the request body\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n meta.params,\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await registeredRoute.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Return the data from the action\n if (action.type === 'error') {\n throw new Error(JSON.stringify(action.data));\n }\n\n return action.data;\n }\n\n /**\n * Get the application DI container (for testing).\n * Returns appContainer which has access to both app and framework bindings.\n */\n getContainer(): Container {\n this.initialize();\n return this.appContainer;\n }\n\n /**\n * Get the WebPieces framework container (for advanced testing/debugging).\n */\n getWebpiecesContainer(): Container {\n this.initialize();\n return this.webpiecesContainer;\n }\n\n\n}\n"]}
package/src/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RouteBuilderImpl = exports.WebpiecesServer = void 0;
3
+ exports.RegisteredRoute = exports.RouteBuilderImpl = exports.WebpiecesServer = void 0;
4
4
  var WebpiecesServer_1 = require("./WebpiecesServer");
5
5
  Object.defineProperty(exports, "WebpiecesServer", { enumerable: true, get: function () { return WebpiecesServer_1.WebpiecesServer; } });
6
6
  var RouteBuilderImpl_1 = require("./RouteBuilderImpl");
7
7
  Object.defineProperty(exports, "RouteBuilderImpl", { enumerable: true, get: function () { return RouteBuilderImpl_1.RouteBuilderImpl; } });
8
+ Object.defineProperty(exports, "RegisteredRoute", { enumerable: true, get: function () { return RouteBuilderImpl_1.RegisteredRoute; } });
8
9
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,uDAAuE;AAA9D,oHAAA,gBAAgB,OAAA","sourcesContent":["export { WebpiecesServer } from './WebpiecesServer';\nexport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AACxB,uDAAuE;AAA9D,oHAAA,gBAAgB,OAAA;AAAE,mHAAA,eAAe,OAAA","sourcesContent":["export { WebpiecesServer } from './WebpiecesServer';\nexport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\n"]}