@webpieces/http-routing 0.3.293 → 0.3.298

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +3 -4
  2. package/src/ApiClient.d.ts +19 -0
  3. package/src/ApiClient.js +26 -0
  4. package/src/ApiClient.js.map +1 -0
  5. package/src/ApiFactory.d.ts +25 -0
  6. package/src/ApiFactory.js +3 -0
  7. package/src/ApiFactory.js.map +1 -0
  8. package/src/ApiRoutingFactory.js +1 -1
  9. package/src/ApiRoutingFactory.js.map +1 -1
  10. package/src/AuthConfig.d.ts +30 -0
  11. package/src/AuthConfig.js +35 -0
  12. package/src/AuthConfig.js.map +1 -0
  13. package/src/Filter.d.ts +76 -0
  14. package/src/Filter.js +75 -0
  15. package/src/Filter.js.map +1 -0
  16. package/src/FilterChain.d.ts +30 -0
  17. package/src/FilterChain.js +63 -0
  18. package/src/FilterChain.js.map +1 -0
  19. package/src/FilterMatcher.d.ts +2 -2
  20. package/src/FilterMatcher.js.map +1 -1
  21. package/src/InProcessApiClientFactory.d.ts +1 -0
  22. package/src/InProcessApiClientFactory.js +15 -3
  23. package/src/InProcessApiClientFactory.js.map +1 -1
  24. package/src/MethodMeta.d.ts +53 -0
  25. package/src/MethodMeta.js +74 -0
  26. package/src/MethodMeta.js.map +1 -0
  27. package/src/RouteBuilderImpl.d.ts +11 -3
  28. package/src/RouteBuilderImpl.js +20 -11
  29. package/src/RouteBuilderImpl.js.map +1 -1
  30. package/src/RouteHandler.d.ts +1 -1
  31. package/src/RouteHandler.js.map +1 -1
  32. package/src/WebAppMeta.d.ts +6 -15
  33. package/src/WebAppMeta.js +11 -7
  34. package/src/WebAppMeta.js.map +1 -1
  35. package/src/WebpiecesRouter.d.ts +21 -10
  36. package/src/WebpiecesRouter.js +27 -13
  37. package/src/WebpiecesRouter.js.map +1 -1
  38. package/src/fillContext.d.ts +11 -0
  39. package/src/fillContext.js +35 -0
  40. package/src/fillContext.js.map +1 -0
  41. package/src/filters/AuthFilter.d.ts +24 -0
  42. package/src/filters/AuthFilter.js +102 -0
  43. package/src/filters/AuthFilter.js.map +1 -0
  44. package/src/filters/ErrorLogFilter.d.ts +15 -0
  45. package/src/filters/ErrorLogFilter.js +40 -0
  46. package/src/filters/ErrorLogFilter.js.map +1 -0
  47. package/src/index.d.ts +9 -4
  48. package/src/index.js +27 -14
  49. package/src/index.js.map +1 -1
@@ -0,0 +1,53 @@
1
+ import { RouteMetadata, AuthMeta } from '@webpieces/core-util';
2
+ /**
3
+ * Metadata about the method being invoked.
4
+ * Passed to filters and contains request information.
5
+ *
6
+ * MethodMeta is DTO-only - it does NOT contain Express req/res, nor the raw headers. The raw
7
+ * inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in
8
+ * RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed
9
+ * body + route/auth metadata that flow as the chain's call argument.
10
+ *
11
+ * It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in
12
+ * @webpieces/http-routing and is express-free, so filter authors reference it without pulling
13
+ * in any express dependency.
14
+ *
15
+ * Fields:
16
+ * - routeMeta: Static route information (httpMethod, path, methodName)
17
+ * - requestDto: The deserialized request body
18
+ * - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators
19
+ * - metadata: Request-scoped data for filters to communicate
20
+ */
21
+ export declare class MethodMeta {
22
+ /**
23
+ * Route metadata (httpMethod, path, methodName, parameterTypes)
24
+ */
25
+ routeMeta: RouteMetadata;
26
+ /**
27
+ * The deserialized request DTO.
28
+ */
29
+ requestDto?: unknown;
30
+ /**
31
+ * Auth metadata from @Public/@Authenticated/@Roles decorators.
32
+ * Populated by ApiRoutingFactory so filters can read auth requirements.
33
+ */
34
+ authMeta?: AuthMeta;
35
+ /**
36
+ * Additional metadata for storing request-scoped data.
37
+ * Used by filters to pass data to other filters/controllers.
38
+ */
39
+ metadata: Map<string, unknown>;
40
+ constructor(routeMeta: RouteMetadata, requestDto?: unknown, metadata?: Map<string, unknown>, authMeta?: AuthMeta);
41
+ /**
42
+ * Get the HTTP method (convenience accessor).
43
+ */
44
+ get httpMethod(): string;
45
+ /**
46
+ * Get the request path (convenience accessor).
47
+ */
48
+ get path(): string;
49
+ /**
50
+ * Get the method name (convenience accessor).
51
+ */
52
+ get methodName(): string;
53
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MethodMeta = void 0;
4
+ /**
5
+ * Metadata about the method being invoked.
6
+ * Passed to filters and contains request information.
7
+ *
8
+ * MethodMeta is DTO-only - it does NOT contain Express req/res, nor the raw headers. The raw
9
+ * inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in
10
+ * RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed
11
+ * body + route/auth metadata that flow as the chain's call argument.
12
+ *
13
+ * It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in
14
+ * @webpieces/http-routing and is express-free, so filter authors reference it without pulling
15
+ * in any express dependency.
16
+ *
17
+ * Fields:
18
+ * - routeMeta: Static route information (httpMethod, path, methodName)
19
+ * - requestDto: The deserialized request body
20
+ * - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators
21
+ * - metadata: Request-scoped data for filters to communicate
22
+ */
23
+ class MethodMeta {
24
+ /**
25
+ * Route metadata (httpMethod, path, methodName, parameterTypes)
26
+ */
27
+ routeMeta;
28
+ /**
29
+ * The deserialized request DTO.
30
+ */
31
+ // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary
32
+ requestDto;
33
+ /**
34
+ * Auth metadata from @Public/@Authenticated/@Roles decorators.
35
+ * Populated by ApiRoutingFactory so filters can read auth requirements.
36
+ */
37
+ authMeta;
38
+ /**
39
+ * Additional metadata for storing request-scoped data.
40
+ * Used by filters to pass data to other filters/controllers.
41
+ */
42
+ // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data
43
+ metadata;
44
+ constructor(routeMeta,
45
+ // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary
46
+ requestDto,
47
+ // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data
48
+ metadata, authMeta) {
49
+ this.routeMeta = routeMeta;
50
+ this.requestDto = requestDto;
51
+ this.metadata = metadata ?? new Map();
52
+ this.authMeta = authMeta ?? routeMeta.authMeta;
53
+ }
54
+ /**
55
+ * Get the HTTP method (convenience accessor).
56
+ */
57
+ get httpMethod() {
58
+ return this.routeMeta.httpMethod;
59
+ }
60
+ /**
61
+ * Get the request path (convenience accessor).
62
+ */
63
+ get path() {
64
+ return this.routeMeta.path;
65
+ }
66
+ /**
67
+ * Get the method name (convenience accessor).
68
+ */
69
+ get methodName() {
70
+ return this.routeMeta.methodName;
71
+ }
72
+ }
73
+ exports.MethodMeta = MethodMeta;
74
+ //# sourceMappingURL=MethodMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MethodMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/MethodMeta.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,UAAU;IACnB;;OAEG;IACH,SAAS,CAAgB;IAEzB;;OAEG;IACH,wFAAwF;IACxF,UAAU,CAAW;IAErB;;;OAGG;IACH,QAAQ,CAAY;IAEpB;;;OAGG;IACH,yFAAyF;IACzF,QAAQ,CAAuB;IAE/B,YACI,SAAwB;IACxB,wFAAwF;IACxF,UAAoB;IACpB,yFAAyF;IACzF,QAA+B,EAC/B,QAAmB;QAEnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,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;AA3DD,gCA2DC","sourcesContent":["import { RouteMetadata, AuthMeta } from '@webpieces/core-util';\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, nor the raw headers. The raw\n * inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in\n * RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed\n * body + route/auth metadata that flow as the chain's call argument.\n *\n * It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in\n * @webpieces/http-routing and is express-free, so filter authors reference it without pulling\n * in any express dependency.\n *\n * Fields:\n * - routeMeta: Static route information (httpMethod, path, methodName)\n * - requestDto: The deserialized request body\n * - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators\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 * The deserialized request DTO.\n */\n // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary\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 // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data\n metadata: Map<string, unknown>;\n\n constructor(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary\n requestDto?: unknown,\n // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data\n metadata?: Map<string, unknown>,\n authMeta?: AuthMeta,\n ) {\n this.routeMeta = routeMeta;\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"]}
@@ -1,10 +1,11 @@
1
1
  import { Container } from 'inversify';
2
2
  import { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';
3
3
  import { RouteHandler } from './RouteHandler';
4
- import { MethodMeta } from '@webpieces/http-filters';
4
+ import { MethodMeta } from './MethodMeta';
5
5
  import { RouteMetadata } from '@webpieces/core-util';
6
- import { WpResponse, Service } from '@webpieces/http-filters';
6
+ import { WpResponse, Service } from './Filter';
7
7
  import { HttpFilter } from './FilterMatcher';
8
+ import { ApiClient } from './ApiClient';
8
9
  /**
9
10
  * FilterWithMeta - Pairs a resolved filter instance with its definition.
10
11
  * Stores both the DI-resolved filter and the metadata needed for matching.
@@ -111,6 +112,13 @@ export declare class RouteBuilderImpl implements RouteBuilder {
111
112
  * @returns Map of routes with handlers and definitions, keyed by "METHOD:path"
112
113
  */
113
114
  getRoutes(): RouteHandlerWithMeta[];
115
+ /**
116
+ * Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +
117
+ * the composed express-tier impl (filter chain → controller). This is what
118
+ * {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl
119
+ * WITHOUT ever seeing this RouteBuilder.
120
+ */
121
+ apiClients(): ApiClient[];
114
122
  /**
115
123
  * Get all filters sorted by priority (highest priority first).
116
124
  *
@@ -136,7 +144,7 @@ export declare class RouteBuilderImpl implements RouteBuilder {
136
144
  * @param routeWithMeta - Route handler with metadata
137
145
  * @returns The service for this route
138
146
  */
139
- createRouteHandler(routeWithMeta: RouteHandlerWithMeta, includeExpressTier?: boolean): Service<MethodMeta, WpResponse<unknown>>;
147
+ createRouteHandler(routeWithMeta: RouteHandlerWithMeta): Service<MethodMeta, WpResponse<unknown>>;
140
148
  /**
141
149
  * Create an invoker function for a route (for testing via createApiClient).
142
150
  * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,
@@ -4,8 +4,9 @@ exports.RouteBuilderImpl = exports.RouteHandlerWithMeta = exports.RouteHandlerIm
4
4
  const tslib_1 = require("tslib");
5
5
  const inversify_1 = require("inversify");
6
6
  const core_context_1 = require("@webpieces/core-context");
7
- const http_filters_1 = require("@webpieces/http-filters");
7
+ const Filter_1 = require("./Filter");
8
8
  const FilterMatcher_1 = require("./FilterMatcher");
9
+ const ApiClient_1 = require("./ApiClient");
9
10
  const core_util_1 = require("@webpieces/core-util");
10
11
  const log = core_util_1.LogManager.getLogger('RouteBuilder');
11
12
  /**
@@ -168,6 +169,15 @@ let RouteBuilderImpl = class RouteBuilderImpl {
168
169
  getRoutes() {
169
170
  return this.routes;
170
171
  }
172
+ /**
173
+ * Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +
174
+ * the composed express-tier impl (filter chain → controller). This is what
175
+ * {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl
176
+ * WITHOUT ever seeing this RouteBuilder.
177
+ */
178
+ apiClients() {
179
+ return this.routes.map((routeWithMeta) => new ApiClient_1.ApiClient(routeWithMeta.definition.apiClass, routeWithMeta.definition.routeMeta, this.createRouteHandler(routeWithMeta)));
180
+ }
171
181
  /**
172
182
  * Get all filters sorted by priority (highest priority first).
173
183
  *
@@ -205,14 +215,14 @@ let RouteBuilderImpl = class RouteBuilderImpl {
205
215
  * @param routeWithMeta - Route handler with metadata
206
216
  * @returns The service for this route
207
217
  */
208
- createRouteHandler(routeWithMeta, includeExpressTier = true) {
218
+ createRouteHandler(routeWithMeta) {
209
219
  const route = routeWithMeta.definition;
210
220
  const routeMeta = route.routeMeta;
211
221
  log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);
212
- // Get cached filter definitions, then drop express-tier filters when composing an
213
- // in-process (createApiClient) chain those need the raw HTTP request (e.g. auth
214
- // reading the Authorization header) and would wrongly reject a headerless in-process call.
215
- const filterDefinitions = this.getFilterDefinitions().filter((def) => includeExpressTier || def.tier !== 'express');
222
+ // ONE chain for both HTTP and in-process — no transport tier. The fixed framework
223
+ // filters (ErrorLogFilter, AuthFilter) are auto-installed and read the transport-neutral
224
+ // HttpRequest, so they run identically in both.
225
+ const filterDefinitions = this.getFilterDefinitions();
216
226
  // Find matching filters for this route
217
227
  const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, filterDefinitions);
218
228
  // Create service that wraps the controller execution
@@ -222,11 +232,11 @@ let RouteBuilderImpl = class RouteBuilderImpl {
222
232
  // A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)
223
233
  // yields undefined; coerce to {} so the response is a non-null JSON body
224
234
  // (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.
225
- return new http_filters_1.WpResponse(result ?? {});
235
+ return new Filter_1.WpResponse(result ?? {});
226
236
  },
227
237
  };
228
238
  if (matchingFilters.length === 0) {
229
- throw new Error("No filters found for route. Check filter definitions as you must have at least ContextFilter");
239
+ throw new Error("No filters found for route the framework auto-installs ErrorLogFilter + AuthFilter, so this indicates a wiring problem.");
230
240
  }
231
241
  // Chain filters: highest priority (first in array) should run first (be outermost)
232
242
  // Build from innermost (lowest priority) to outermost (highest priority)
@@ -257,9 +267,8 @@ let RouteBuilderImpl = class RouteBuilderImpl {
257
267
  if (!routeWithMeta) {
258
268
  throw new Error(`Route not found: ${method} ${path}`);
259
269
  }
260
- // Setup filter chain ONCE (not on every invocation!).
261
- // In-process client → api-tier filters only (skip express-tier like ServiceAuthFilter).
262
- return this.createRouteHandler(routeWithMeta, false);
270
+ // Setup filter chain ONCE (not on every invocation!). Same chain as HTTP — auth included.
271
+ return this.createRouteHandler(routeWithMeta);
263
272
  }
264
273
  /**
265
274
  * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.
@@ -1 +1 @@
1
- {"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, injectable } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from '@webpieces/http-filters';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { WpResponse, Service } from '@webpieces/http-filters';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n private method: (this: unknown, requestDto?: unknown) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.call(this.controller, meta.requestDto);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\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 *\n * DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(\n route.routeMeta.httpMethod,\n route.routeMeta.path\n );\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as { name?: string }).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n method as (this: unknown, requestDto?: unknown) => Promise<TResult>\n );\n\n // Return handler with route definition\n return new RouteHandlerWithMeta(\n handler as RouteHandler<unknown>,\n route,\n );\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n includeExpressTier: boolean = true,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // Get cached filter definitions, then drop express-tier filters when composing an\n // in-process (createApiClient) chain — those need the raw HTTP request (e.g. auth\n // reading the Authorization header) and would wrongly reject a headerless in-process call.\n const filterDefinitions = this.getFilterDefinitions().filter(\n (def: FilterDefinition) => includeExpressTier || def.tier !== 'express',\n );\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)\n // yields undefined; coerce to {} so the response is a non-null JSON body\n // (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\"No filters found for route. Check filter definitions as you must have at least ContextFilter\");\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!).\n // In-process client → api-tier filters only (skip express-tier like ServiceAuthFilter).\n return this.createRouteHandler(routeWithMeta, false);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
1
+ {"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, injectable } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from './MethodMeta';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { WpResponse, Service } from './Filter';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { ApiClient } from './ApiClient';\nimport { ClassType } from './ApiRoutingFactory';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n private method: (this: unknown, requestDto?: unknown) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.call(this.controller, meta.requestDto);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\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 *\n * DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(\n route.routeMeta.httpMethod,\n route.routeMeta.path\n );\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as { name?: string }).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n method as (this: unknown, requestDto?: unknown) => Promise<TResult>\n );\n\n // Return handler with route definition\n return new RouteHandlerWithMeta(\n handler as RouteHandler<unknown>,\n route,\n );\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +\n * the composed express-tier impl (filter chain → controller). This is what\n * {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl\n * WITHOUT ever seeing this RouteBuilder.\n */\n apiClients(): ApiClient[] {\n return this.routes.map(\n (routeWithMeta: RouteHandlerWithMeta) =>\n new ApiClient(\n routeWithMeta.definition.apiClass as ClassType,\n routeWithMeta.definition.routeMeta,\n this.createRouteHandler(routeWithMeta),\n ),\n );\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // ONE chain for both HTTP and in-process — no transport tier. The fixed framework\n // filters (ErrorLogFilter, AuthFilter) are auto-installed and read the transport-neutral\n // HttpRequest, so they run identically in both.\n const filterDefinitions = this.getFilterDefinitions();\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)\n // yields undefined; coerce to {} so the response is a non-null JSON body\n // (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\"No filters found for route — the framework auto-installs ErrorLogFilter + AuthFilter, so this indicates a wiring problem.\");\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!). Same chain as HTTP — auth included.\n return this.createRouteHandler(routeWithMeta);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { MethodMeta } from '@webpieces/http-filters';
1
+ import { MethodMeta } from './MethodMeta';
2
2
  /**
3
3
  * Handler class for routes.
4
4
  * Takes a MethodMeta and returns the controller method result.
@@ -1 +1 @@
1
- {"version":3,"file":"RouteHandler.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteHandler.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAsB,YAAY;CAOjC;AAPD,oCAOC","sourcesContent":["import { MethodMeta } from '@webpieces/http-filters';\n\n/**\n * Handler class for routes.\n * Takes a MethodMeta and returns the controller method result.\n *\n * Generic type parameter TResult represents the return type of the controller method.\n * Example: RouteHandler<SaveResponse> for a method that returns Promise<SaveResponse>\n *\n * Using unknown as default instead of any forces type safety - consumers must\n * handle the result appropriately rather than assuming any type.\n *\n * This is a class instead of a function type to make it easier to trace\n * who is calling what in the debugger/IDE.\n */\nexport abstract class RouteHandler<TResult = unknown> {\n /**\n * Execute the route handler.\n * @param meta - The method metadata containing request info and params\n * @returns Promise of the controller method result\n */\n abstract execute(meta: MethodMeta): Promise<TResult>;\n}\n"]}
1
+ {"version":3,"file":"RouteHandler.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteHandler.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAsB,YAAY;CAOjC;AAPD,oCAOC","sourcesContent":["import { MethodMeta } from './MethodMeta';\n\n/**\n * Handler class for routes.\n * Takes a MethodMeta and returns the controller method result.\n *\n * Generic type parameter TResult represents the return type of the controller method.\n * Example: RouteHandler<SaveResponse> for a method that returns Promise<SaveResponse>\n *\n * Using unknown as default instead of any forces type safety - consumers must\n * handle the result appropriately rather than assuming any type.\n *\n * This is a class instead of a function type to make it easier to trace\n * who is calling what in the debugger/IDE.\n */\nexport abstract class RouteHandler<TResult = unknown> {\n /**\n * Execute the route handler.\n * @param meta - The method metadata containing request info and params\n * @returns Promise of the controller method result\n */\n abstract execute(meta: MethodMeta): Promise<TResult>;\n}\n"]}
@@ -27,17 +27,9 @@ export declare class RouteDefinition {
27
27
  routeMeta: RouteMetadata;
28
28
  controllerClass: any;
29
29
  controllerFilepath?: string | undefined;
30
- constructor(routeMeta: RouteMetadata, controllerClass: any, controllerFilepath?: string | undefined);
30
+ apiClass?: unknown | undefined;
31
+ constructor(routeMeta: RouteMetadata, controllerClass: any, controllerFilepath?: string | undefined, apiClass?: unknown | undefined);
31
32
  }
32
- /**
33
- * A filter's execution tier:
34
- * - 'api' : runs for BOTH real HTTP requests AND the in-process createApiClient
35
- * (business/cross-cutting filters — logging, recording, context seeding).
36
- * - 'express' : runs ONLY for real HTTP requests mounted on express (transport-boundary
37
- * filters that need the raw request — e.g. service auth reading the
38
- * Authorization header). Skipped by the in-process client so tests don't 401.
39
- */
40
- export type FilterTier = 'express' | 'api';
41
33
  /**
42
34
  * Definition of a filter with priority.
43
35
  *
@@ -48,8 +40,9 @@ export type FilterTier = 'express' | 'api';
48
40
  *
49
41
  * If filepathPattern is not specified, the filter matches all controllers.
50
42
  *
51
- * tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over
52
- * HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.
43
+ * Every filter runs for BOTH real HTTP requests AND the in-process createApiClient there is
44
+ * no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that
45
+ * reads the transport-neutral HttpRequest, so it runs identically in both.
53
46
  */
54
47
  export declare class FilterDefinition {
55
48
  priority: number;
@@ -60,7 +53,5 @@ export declare class FilterDefinition {
60
53
  * If not specified, defaults to matching all controllers.
61
54
  */
62
55
  filepathPattern: string;
63
- /** Execution tier see {@link FilterTier}. Defaults to 'api'. */
64
- tier: FilterTier;
65
- constructor(priority: number, filterClass: any, filepathPattern: string, tier?: FilterTier);
56
+ constructor(priority: number, filterClass: any, filepathPattern: string);
66
57
  }
package/src/WebAppMeta.js CHANGED
@@ -11,10 +11,16 @@ class RouteDefinition {
11
11
  routeMeta;
12
12
  controllerClass;
13
13
  controllerFilepath;
14
- constructor(routeMeta, controllerClass, controllerFilepath) {
14
+ apiClass;
15
+ constructor(routeMeta,
16
+ // webpieces-disable no-any-unknown -- arbitrary DI controller class used as a container token
17
+ controllerClass, controllerFilepath,
18
+ // The @ApiPath prototype this route belongs to; surfaced on ApiFactory.apiClients().
19
+ apiClass) {
15
20
  this.routeMeta = routeMeta;
16
21
  this.controllerClass = controllerClass;
17
22
  this.controllerFilepath = controllerFilepath;
23
+ this.apiClass = apiClass;
18
24
  }
19
25
  }
20
26
  exports.RouteDefinition = RouteDefinition;
@@ -28,8 +34,9 @@ exports.RouteDefinition = RouteDefinition;
28
34
  *
29
35
  * If filepathPattern is not specified, the filter matches all controllers.
30
36
  *
31
- * tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over
32
- * HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.
37
+ * Every filter runs for BOTH real HTTP requests AND the in-process createApiClient there is
38
+ * no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that
39
+ * reads the transport-neutral HttpRequest, so it runs identically in both.
33
40
  */
34
41
  class FilterDefinition {
35
42
  priority;
@@ -42,14 +49,11 @@ class FilterDefinition {
42
49
  * If not specified, defaults to matching all controllers.
43
50
  */
44
51
  filepathPattern;
45
- /** Execution tier — see {@link FilterTier}. Defaults to 'api'. */
46
- tier;
47
52
  // webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token
48
- constructor(priority, filterClass, filepathPattern, tier = 'api') {
53
+ constructor(priority, filterClass, filepathPattern) {
49
54
  this.priority = priority;
50
55
  this.filterClass = filterClass;
51
56
  this.filepathPattern = filepathPattern;
52
- this.tier = tier;
53
57
  this.filter = undefined; // Set later by RouteBuilder
54
58
  }
55
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WebAppMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebAppMeta.ts"],"names":[],"mappings":";;;AAsBA;;;;;GAKG;AACH,MAAa,eAAe;IAEb;IACA;IACA;IAHX,YACW,SAAwB,EACxB,eAAoB,EACpB,kBAA2B;QAF3B,cAAS,GAAT,SAAS,CAAe;QACxB,oBAAe,GAAf,eAAe,CAAK;QACpB,uBAAkB,GAAlB,kBAAkB,CAAS;IACnC,CAAC;CACP;AAND,0CAMC;AAYD;;;;;;;;;;;;GAYG;AACH,MAAa,gBAAgB;IACzB,QAAQ,CAAS;IACjB,6FAA6F;IAC7F,WAAW,CAAM;IACjB,uFAAuF;IACvF,MAAM,CAAO,CAAC,+DAA+D;IAE7E;;;OAGG;IACH,eAAe,CAAS;IAExB,kEAAkE;IAClE,IAAI,CAAa;IAEjB,8FAA8F;IAC9F,YAAY,QAAgB,EAAE,WAAgB,EAAE,eAAuB,EAAE,OAAmB,KAAK;QAC7F,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,4BAA4B;IACzD,CAAC;CACJ;AAxBD,4CAwBC;AAGD,0FAA0F;AAC1F,2FAA2F;AAC3F,uGAAuG","sourcesContent":["import {RouteMetadata} from \"@webpieces/core-util\";\n\n/**\n * Represents a route configuration that can be registered with the router.\n * Similar to Java WebPieces Routes interface.\n */\nexport interface Routes {\n /**\n * Configure routes using the provided RouteBuilder.\n */\n configure(routeBuilder: RouteBuilder): void;\n}\n\n/**\n * Builder for registering routes.\n * Will be implemented in http-server package.\n */\nexport interface RouteBuilder {\n addRoute(route: RouteDefinition): void;\n addFilter(filter: FilterDefinition): void;\n}\n\n/**\n * Definition of a single route.\n *\n * Generic type parameter TResult represents the return type of the route handler.\n * This provides type safety for the entire request/response cycle.\n */\nexport class RouteDefinition {\n constructor(\n public routeMeta: RouteMetadata,\n public controllerClass: any,\n public controllerFilepath?: string,\n ) {}\n}\n\n/**\n * A filter's execution tier:\n * - 'api' : runs for BOTH real HTTP requests AND the in-process createApiClient\n * (business/cross-cutting filters logging, recording, context seeding).\n * - 'express' : runs ONLY for real HTTP requests mounted on express (transport-boundary\n * filters that need the raw request — e.g. service auth reading the\n * Authorization header). Skipped by the in-process client so tests don't 401.\n */\nexport type FilterTier = 'express' | 'api';\n\n/**\n * Definition of a filter with priority.\n *\n * Use filepathPattern to scope filters to specific controllers:\n * - 'src/controllers/admin/**' + '/*.ts' - All admin controllers\n * - '**' + '/admin/**' - Any file in admin directories\n * - '**' + '/UserController.ts' - Specific controller file\n *\n * If filepathPattern is not specified, the filter matches all controllers.\n *\n * tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over\n * HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.\n */\nexport class FilterDefinition {\n priority: number;\n // webpieces-disable no-any-unknown -- an arbitrary DI filter class used as a container token\n filterClass: any;\n // webpieces-disable no-any-unknown -- the resolved filter instance, of arbitrary shape\n filter?: any; // Filter instance (set by RouteBuilder when resolving from DI)\n\n /**\n * Glob pattern to match controller file paths.\n * If not specified, defaults to matching all controllers.\n */\n filepathPattern: string;\n\n /** Execution tier — see {@link FilterTier}. Defaults to 'api'. */\n tier: FilterTier;\n\n // webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token\n constructor(priority: number, filterClass: any, filepathPattern: string, tier: FilterTier = 'api') {\n this.priority = priority;\n this.filterClass = filterClass;\n this.filepathPattern = filepathPattern;\n this.tier = tier;\n this.filter = undefined; // Set later by RouteBuilder\n }\n}\n\n\n// The old WebAppMeta interface + WEBAPP_META_TOKEN were removed with the WebpiecesServer/\n// WebpiecesFactory flip. Apps now configure routes/filters imperatively on WebpiecesRouter\n// (see WebpiecesRouter.addRoutes/addFilter) instead of implementing WebAppMeta.getDIModules/getRoutes.\n"]}
1
+ {"version":3,"file":"WebAppMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebAppMeta.ts"],"names":[],"mappings":";;;AAsBA;;;;;GAKG;AACH,MAAa,eAAe;IAEb;IAEA;IACA;IAEA;IANX,YACW,SAAwB;IAC/B,8FAA8F;IACvF,eAAoB,EACpB,kBAA2B;IAClC,qFAAqF;IAC9E,QAAkB;QALlB,cAAS,GAAT,SAAS,CAAe;QAExB,oBAAe,GAAf,eAAe,CAAK;QACpB,uBAAkB,GAAlB,kBAAkB,CAAS;QAE3B,aAAQ,GAAR,QAAQ,CAAU;IAC1B,CAAC;CACP;AATD,0CASC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAa,gBAAgB;IACzB,QAAQ,CAAS;IACjB,6FAA6F;IAC7F,WAAW,CAAM;IACjB,uFAAuF;IACvF,MAAM,CAAO,CAAC,+DAA+D;IAE7E;;;OAGG;IACH,eAAe,CAAS;IAExB,8FAA8F;IAC9F,YAAY,QAAgB,EAAE,WAAgB,EAAE,eAAuB;QACnE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,4BAA4B;IACzD,CAAC;CACJ;AApBD,4CAoBC;AAGD,0FAA0F;AAC1F,2FAA2F;AAC3F,uGAAuG","sourcesContent":["import {RouteMetadata} from \"@webpieces/core-util\";\n\n/**\n * Represents a route configuration that can be registered with the router.\n * Similar to Java WebPieces Routes interface.\n */\nexport interface Routes {\n /**\n * Configure routes using the provided RouteBuilder.\n */\n configure(routeBuilder: RouteBuilder): void;\n}\n\n/**\n * Builder for registering routes.\n * Will be implemented in http-server package.\n */\nexport interface RouteBuilder {\n addRoute(route: RouteDefinition): void;\n addFilter(filter: FilterDefinition): void;\n}\n\n/**\n * Definition of a single route.\n *\n * Generic type parameter TResult represents the return type of the route handler.\n * This provides type safety for the entire request/response cycle.\n */\nexport class RouteDefinition {\n constructor(\n public routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- arbitrary DI controller class used as a container token\n public controllerClass: any,\n public controllerFilepath?: string,\n // The @ApiPath prototype this route belongs to; surfaced on ApiFactory.apiClients().\n public apiClass?: unknown,\n ) {}\n}\n\n/**\n * Definition of a filter with priority.\n *\n * Use filepathPattern to scope filters to specific controllers:\n * - 'src/controllers/admin/**' + '/*.ts' - All admin controllers\n * - '**' + '/admin/**' - Any file in admin directories\n * - '**' + '/UserController.ts' - Specific controller file\n *\n * If filepathPattern is not specified, the filter matches all controllers.\n *\n * Every filter runs for BOTH real HTTP requests AND the in-process createApiClient there is\n * no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that\n * reads the transport-neutral HttpRequest, so it runs identically in both.\n */\nexport class FilterDefinition {\n priority: number;\n // webpieces-disable no-any-unknown -- an arbitrary DI filter class used as a container token\n filterClass: any;\n // webpieces-disable no-any-unknown -- the resolved filter instance, of arbitrary shape\n filter?: any; // Filter instance (set by RouteBuilder when resolving from DI)\n\n /**\n * Glob pattern to match controller file paths.\n * If not specified, defaults to matching all controllers.\n */\n filepathPattern: string;\n\n // webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token\n constructor(priority: number, filterClass: any, filepathPattern: string) {\n this.priority = priority;\n this.filterClass = filterClass;\n this.filepathPattern = filepathPattern;\n this.filter = undefined; // Set later by RouteBuilder\n }\n}\n\n\n// The old WebAppMeta interface + WEBAPP_META_TOKEN were removed with the WebpiecesServer/\n// WebpiecesFactory flip. Apps now configure routes/filters imperatively on WebpiecesRouter\n// (see WebpiecesRouter.addRoutes/addFilter) instead of implementing WebAppMeta.getDIModules/getRoutes.\n"]}
@@ -3,6 +3,8 @@ import { RouteBuilderImpl } from './RouteBuilderImpl';
3
3
  import { ClassType } from './ApiRoutingFactory';
4
4
  import { FilterDefinition } from './WebAppMeta';
5
5
  import { WebpiecesConfig } from './WebpiecesConfig';
6
+ import { ApiFactory } from './ApiFactory';
7
+ import { ApiClient } from './ApiClient';
6
8
  /**
7
9
  * Options for {@link WebpiecesRouterFactory.create}.
8
10
  *
@@ -35,10 +37,10 @@ export interface WebpiecesRouterOptions {
35
37
  * appBindings: [WebpiecesModule, CompanyHeadersModule],
36
38
  * });
37
39
  * router.addRoutes(SaveApi, SaveController);
38
- * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // api tier
39
- * router.addFilter(new FilterDefinition(1950, ServiceAuthFilter, '*', 'express')); // express tier
40
+ * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
41
+ * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
40
42
  *
41
- * // test (no express): runs the api-tier filter chain -> controller
43
+ * // test (no express): runs the SAME filter chain (incl. auth) -> controller
42
44
  * const api = router.createApiClient(SaveApi);
43
45
  * await api.save(new SaveRequest(...));
44
46
  * ```
@@ -48,7 +50,7 @@ export interface WebpiecesRouterOptions {
48
50
  *
49
51
  * @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.
50
52
  */
51
- export declare class WebpiecesRouter {
53
+ export declare class WebpiecesRouter implements ApiFactory {
52
54
  private readonly routeBuilder;
53
55
  private webpiecesContainer;
54
56
  private appContainer;
@@ -59,6 +61,13 @@ export declare class WebpiecesRouter {
59
61
  * the factory after this router is resolved from the framework container.
60
62
  */
61
63
  initialize(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void>;
64
+ /**
65
+ * Auto-install the two fixed framework filters on every route (apps add only their own
66
+ * filters below these): ErrorLogFilter outermost (log + let the transport translate), then
67
+ * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND
68
+ * in-process — there is no transport tier.
69
+ */
70
+ private installFixedFilters;
62
71
  private loadDIModules;
63
72
  /**
64
73
  * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
@@ -66,8 +75,8 @@ export declare class WebpiecesRouter {
66
75
  */
67
76
  addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
68
77
  /**
69
- * Register a filter. Defaults to the 'api' tier (runs in-process AND over HTTP);
70
- * pass a 'express'-tier FilterDefinition for transport-boundary filters.
78
+ * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed
79
+ * ErrorLogFilter + AuthFilter).
71
80
  */
72
81
  addFilter(filter: FilterDefinition): this;
73
82
  /**
@@ -75,12 +84,14 @@ export declare class WebpiecesRouter {
75
84
  * with NO express/HTTP. The primary path for tests and node-only callers.
76
85
  */
77
86
  createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
87
+ /**
88
+ * Reify the registered routes as {@link ApiClient}s (api contract + routeMeta + composed
89
+ * filter-chain→controller impl). This is the ONLY handoff to the express layer — the
90
+ * internal RouteBuilder never leaves this class.
91
+ */
92
+ apiClients(): ApiClient[];
78
93
  /** The application DI container (child of the framework container). */
79
94
  getContainer(): Container;
80
- /** The framework container (holds the config token + @DocumentDesign design roots). */
81
- getFrameworkContainer(): Container;
82
- /** The route table + filter chain. Used by the express adapter to mount HTTP routes. */
83
- getRouteBuilder(): RouteBuilderImpl;
84
95
  }
85
96
  /**
86
97
  * Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors
@@ -8,8 +8,11 @@ const core_util_1 = require("@webpieces/core-util");
8
8
  const core_context_1 = require("@webpieces/core-context");
9
9
  const RouteBuilderImpl_1 = require("./RouteBuilderImpl");
10
10
  const ApiRoutingFactory_1 = require("./ApiRoutingFactory");
11
+ const WebAppMeta_1 = require("./WebAppMeta");
11
12
  const WebpiecesConfig_1 = require("./WebpiecesConfig");
12
13
  const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
14
+ const ErrorLogFilter_1 = require("./filters/ErrorLogFilter");
15
+ const AuthFilter_1 = require("./filters/AuthFilter");
13
16
  /**
14
17
  * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
15
18
  * chain + an in-process API client. It has NO express dependency, so it runs anywhere
@@ -29,10 +32,10 @@ const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
29
32
  * appBindings: [WebpiecesModule, CompanyHeadersModule],
30
33
  * });
31
34
  * router.addRoutes(SaveApi, SaveController);
32
- * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // api tier
33
- * router.addFilter(new FilterDefinition(1950, ServiceAuthFilter, '*', 'express')); // express tier
35
+ * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
36
+ * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
34
37
  *
35
- * // test (no express): runs the api-tier filter chain -> controller
38
+ * // test (no express): runs the SAME filter chain (incl. auth) -> controller
36
39
  * const api = router.createApiClient(SaveApi);
37
40
  * await api.save(new SaveRequest(...));
38
41
  * ```
@@ -60,6 +63,17 @@ let WebpiecesRouter = class WebpiecesRouter {
60
63
  this.appContainer = new inversify_1.Container({ parent: webpiecesContainer });
61
64
  this.routeBuilder.setContainer(this.appContainer);
62
65
  await this.loadDIModules(options);
66
+ this.installFixedFilters();
67
+ }
68
+ /**
69
+ * Auto-install the two fixed framework filters on every route (apps add only their own
70
+ * filters below these): ErrorLogFilter outermost (log + let the transport translate), then
71
+ * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND
72
+ * in-process — there is no transport tier.
73
+ */
74
+ installFixedFilters() {
75
+ this.addFilter(new WebAppMeta_1.FilterDefinition(1_000_000, ErrorLogFilter_1.ErrorLogFilter, '*'));
76
+ this.addFilter(new WebAppMeta_1.FilterDefinition(900_000, AuthFilter_1.AuthFilter, '*'));
63
77
  }
64
78
  async loadDIModules(options) {
65
79
  // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's
@@ -86,8 +100,8 @@ let WebpiecesRouter = class WebpiecesRouter {
86
100
  return this;
87
101
  }
88
102
  /**
89
- * Register a filter. Defaults to the 'api' tier (runs in-process AND over HTTP);
90
- * pass a 'express'-tier FilterDefinition for transport-boundary filters.
103
+ * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed
104
+ * ErrorLogFilter + AuthFilter).
91
105
  */
92
106
  addFilter(filter) {
93
107
  this.routeBuilder.addFilter(filter);
@@ -101,18 +115,18 @@ let WebpiecesRouter = class WebpiecesRouter {
101
115
  createApiClient(apiPrototype) {
102
116
  return new InProcessApiClientFactory_1.InProcessApiClientFactory(this.routeBuilder).createApiClient(apiPrototype);
103
117
  }
118
+ /**
119
+ * Reify the registered routes as {@link ApiClient}s (api contract + routeMeta + composed
120
+ * filter-chain→controller impl). This is the ONLY handoff to the express layer — the
121
+ * internal RouteBuilder never leaves this class.
122
+ */
123
+ apiClients() {
124
+ return this.routeBuilder.apiClients();
125
+ }
104
126
  /** The application DI container (child of the framework container). */
105
127
  getContainer() {
106
128
  return this.appContainer;
107
129
  }
108
- /** The framework container (holds the config token + @DocumentDesign design roots). */
109
- getFrameworkContainer() {
110
- return this.webpiecesContainer;
111
- }
112
- /** The route table + filter chain. Used by the express adapter to mount HTTP routes. */
113
- getRouteBuilder() {
114
- return this.routeBuilder;
115
- }
116
130
  };
117
131
  exports.WebpiecesRouter = WebpiecesRouter;
118
132
  exports.WebpiecesRouter = WebpiecesRouter = tslib_1.__decorate([