@webpieces/http-routing 0.3.293 → 0.3.297

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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-routing",
3
- "version": "0.3.293",
3
+ "version": "0.3.297",
4
4
  "description": "Decorator-based routing with auto-wiring for WebPieces",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,9 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@inversifyjs/binding-decorators": "1.1.5",
25
- "@webpieces/core-context": "0.3.293",
26
- "@webpieces/core-util": "0.3.293",
27
- "@webpieces/http-filters": "0.3.293",
25
+ "@webpieces/core-context": "0.3.297",
26
+ "@webpieces/core-util": "0.3.297",
28
27
  "inversify": "7.10.4",
29
28
  "minimatch": "10.0.1"
30
29
  }
@@ -0,0 +1,19 @@
1
+ import { RouteMetadata } from '@webpieces/core-util';
2
+ import { ClassType } from './ApiRoutingFactory';
3
+ import { MethodMeta } from './MethodMeta';
4
+ import { Service, WpResponse } from './Filter';
5
+ /**
6
+ * ApiClient - one reified endpoint of an API: the API contract it belongs to, its route
7
+ * metadata (http method + path + auth), and the composed `impl` — the filter chain that
8
+ * ends in the controller method, invoked per request.
9
+ *
10
+ * Data-only structure (a class, per the webpieces guidelines). {@link ApiFactory.apiClients}
11
+ * returns the full list; the express layer (WebpiecesExpressRouter) binds each `impl` to a
12
+ * route, so the internal RouteBuilder never leaks to upper layers.
13
+ */
14
+ export declare class ApiClient {
15
+ readonly api: ClassType;
16
+ readonly routeMeta: RouteMetadata;
17
+ readonly impl: Service<MethodMeta, WpResponse<unknown>>;
18
+ constructor(api: ClassType, routeMeta: RouteMetadata, impl: Service<MethodMeta, WpResponse<unknown>>);
19
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiClient = void 0;
4
+ /**
5
+ * ApiClient - one reified endpoint of an API: the API contract it belongs to, its route
6
+ * metadata (http method + path + auth), and the composed `impl` — the filter chain that
7
+ * ends in the controller method, invoked per request.
8
+ *
9
+ * Data-only structure (a class, per the webpieces guidelines). {@link ApiFactory.apiClients}
10
+ * returns the full list; the express layer (WebpiecesExpressRouter) binds each `impl` to a
11
+ * route, so the internal RouteBuilder never leaks to upper layers.
12
+ */
13
+ class ApiClient {
14
+ api;
15
+ routeMeta;
16
+ impl;
17
+ constructor(api, routeMeta,
18
+ // webpieces-disable no-any-unknown -- WpResponse<unknown>: the composed impl is response-type-erased at the filter boundary
19
+ impl) {
20
+ this.api = api;
21
+ this.routeMeta = routeMeta;
22
+ this.impl = impl;
23
+ }
24
+ }
25
+ exports.ApiClient = ApiClient;
26
+ //# sourceMappingURL=ApiClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClient.ts"],"names":[],"mappings":";;;AAKA;;;;;;;;GAQG;AACH,MAAa,SAAS;IAEE;IACA;IAEA;IAJpB,YACoB,GAAc,EACd,SAAwB;IACxC,4HAA4H;IAC5G,IAA8C;QAH9C,QAAG,GAAH,GAAG,CAAW;QACd,cAAS,GAAT,SAAS,CAAe;QAExB,SAAI,GAAJ,IAAI,CAA0C;IAC/D,CAAC;CACP;AAPD,8BAOC","sourcesContent":["import { RouteMetadata } from '@webpieces/core-util';\nimport { ClassType } from './ApiRoutingFactory';\nimport { MethodMeta } from './MethodMeta';\nimport { Service, WpResponse } from './Filter';\n\n/**\n * ApiClient - one reified endpoint of an API: the API contract it belongs to, its route\n * metadata (http method + path + auth), and the composed `impl` — the filter chain that\n * ends in the controller method, invoked per request.\n *\n * Data-only structure (a class, per the webpieces guidelines). {@link ApiFactory.apiClients}\n * returns the full list; the express layer (WebpiecesExpressRouter) binds each `impl` to a\n * route, so the internal RouteBuilder never leaks to upper layers.\n */\nexport class ApiClient {\n constructor(\n public readonly api: ClassType,\n public readonly routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- WpResponse<unknown>: the composed impl is response-type-erased at the filter boundary\n public readonly impl: Service<MethodMeta, WpResponse<unknown>>,\n ) {}\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import { Container } from 'inversify';
2
+ import { ClassType } from './ApiRoutingFactory';
3
+ import { FilterDefinition } from './WebAppMeta';
4
+ import { ApiClient } from './ApiClient';
5
+ /**
6
+ * ApiFactory - the node-only, EXPRESS-FREE surface for declaring an app's API surface and
7
+ * getting it back as data. It is the ONE abstraction upper layers use:
8
+ *
9
+ * - {@link addRoutes} / {@link addFilter} declare the surface (api → controller, + filters).
10
+ * - {@link apiClients} returns each endpoint as an {@link ApiClient} (api + routeMeta +
11
+ * composed impl). The express layer (WebpiecesExpressRouter) binds these; the internal
12
+ * RouteBuilder is never exposed.
13
+ * - {@link createApiClient} builds an in-process proxy (the primary test path, no HTTP).
14
+ * - {@link getContainer} exposes the DI container for test rebinds.
15
+ *
16
+ * Implemented by {@link WebpiecesRouter} (the node-only heart). Hand an ApiFactory to
17
+ * WebpiecesExpressRouter in @webpieces/http-server to serve it over HTTP.
18
+ */
19
+ export interface ApiFactory {
20
+ addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
21
+ addFilter(filter: FilterDefinition): this;
22
+ apiClients(): ApiClient[];
23
+ createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
24
+ getContainer(): Container;
25
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ApiFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiFactory.ts"],"names":[],"mappings":"","sourcesContent":["import { Container } from 'inversify';\nimport { ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { ApiClient } from './ApiClient';\n\n/**\n * ApiFactory - the node-only, EXPRESS-FREE surface for declaring an app's API surface and\n * getting it back as data. It is the ONE abstraction upper layers use:\n *\n * - {@link addRoutes} / {@link addFilter} declare the surface (api → controller, + filters).\n * - {@link apiClients} returns each endpoint as an {@link ApiClient} (api + routeMeta +\n * composed impl). The express layer (WebpiecesExpressRouter) binds these; the internal\n * RouteBuilder is never exposed.\n * - {@link createApiClient} builds an in-process proxy (the primary test path, no HTTP).\n * - {@link getContainer} exposes the DI container for test rebinds.\n *\n * Implemented by {@link WebpiecesRouter} (the node-only heart). Hand an ApiFactory to\n * WebpiecesExpressRouter in @webpieces/http-server to serve it over HTTP.\n */\nexport interface ApiFactory {\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this;\n\n addFilter(filter: FilterDefinition): this;\n\n apiClients(): ApiClient[];\n\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;\n\n getContainer(): Container;\n}\n"]}
@@ -74,7 +74,7 @@ class ApiRoutingFactory {
74
74
  }
75
75
  const fullPath = basePath + endpointPath;
76
76
  const routeMeta = new core_util_1.RouteMetadata('POST', fullPath, methodName, controllerName, authMeta);
77
- routeBuilder.addRoute(new WebAppMeta_1.RouteDefinition(routeMeta, this.controllerClass, controllerFilepath));
77
+ routeBuilder.addRoute(new WebAppMeta_1.RouteDefinition(routeMeta, this.controllerClass, controllerFilepath, this.apiMetaClass));
78
78
  }
79
79
  }
80
80
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ApiRoutingFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiRoutingFactory.ts"],"names":[],"mappings":";;;AAAA,6CAAqE;AACrE,oDAAiH;AACjH,4BAA0B;AAC1B,6CAAqD;AAQrD;;;;;;;;;;;;;;;;GAgBG;AACH,+FAA+F;AAC/F,MAAa,iBAAiB;IAClB,YAAY,CAAkB;IAC9B,eAAe,CAAyB;IAEhD;;;OAGG;IACH,YAAY,YAA6B,EAAE,eAAuC;QAC9E,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,qDAAqD;QACrD,IAAI,CAAC,IAAA,qBAAS,EAAC,YAAY,CAAC,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,IAAI,SAAS,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,SAAS,SAAS,oCAAoC,CAAC,CAAC;QAC5E,CAAC;QAED,+DAA+D;QAC/D,mFAAmF;QACnF,kFAAkF;QAClF,mFAAmF;QACnF,+CAA+C;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,IAAI,SAAS,CAAC;QAC/C,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,IAAI,SAAS,CAAC;QACzD,IAAI,CAAE,YAAY,CAAC,SAAoB,CAAC,aAAa,CAAC,eAAe,CAAC,SAAmB,CAAC,EAAE,CAAC;YACzF,MAAM,IAAI,KAAK,CACX,cAAc,cAAc,gBAAgB,OAAO,IAAI;gBACvD,mCAAmC;gBACnC,iBAAiB,cAAc,YAAY,OAAO,WAAW,CAChE,CAAC;QACN,CAAC;IAEL,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,YAA0B;QAChC,MAAM,QAAQ,GAAG,IAAA,sBAAU,EAAC,IAAI,CAAC,YAAY,CAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACxD,MAAM,kBAAkB,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QACxD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,SAAS,CAAC;QACpD,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI,SAAS,CAAC;QAE9D,KAAK,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,6CAA6C;YAC7C,IAAI,OAAO,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,UAAU,EAAE,CAAC;gBACnE,MAAM,IAAI,KAAK,CACX,cAAc,cAAc,0BAA0B,UAAU,aAAa,OAAO,EAAE,CACzF,CAAC;YACN,CAAC;YAED,+DAA+D;YAC/D,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAC5D,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CACX,aAAa,UAAU,QAAQ,OAAO,qCAAqC;oBAC3E,4EAA4E,CAC/E,CAAC;YACN,CAAC;YAED,MAAM,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;YACzC,MAAM,SAAS,GAAG,IAAI,yBAAa,CAC/B,MAAM,EACN,QAAQ,EACR,UAAU,EACV,cAAc,EACd,QAAQ,CACX,CAAC;YAEF,YAAY,CAAC,QAAQ,CAAC,IAAI,4BAAe,CAAC,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC,CAAC;QACpG,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,qBAAqB;QACzB,oDAAoD;QACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAChC,kCAAqB,CAAC,eAAe,EACrC,IAAI,CAAC,eAAe,CACvB,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACX,OAAO,QAAQ,CAAC;QACpB,CAAC;QAED,iCAAiC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;QAC5C,OAAO,SAAS,CAAC,CAAC,CAAC,MAAM,SAAS,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACxD,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,UAAkB;QACnC,OAAO,IAAA,uBAAW,EAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,WAAW;QACP,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,kBAAkB;QACd,OAAO,IAAI,CAAC,eAAe,CAAC;IAChC,CAAC;CACJ;AAnHD,8CAmHC","sourcesContent":["import { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport { isApiPath, getApiPath, getEndpoints, getAuthMeta, RouteMetadata, AuthMeta } from '@webpieces/core-util';\nimport 'reflect-metadata';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\n/**\n * Type representing a class constructor (abstract or concrete).\n */\n// webpieces-disable no-any-unknown -- generic type alias requires unconstrained default\nexport type ClassType<T = unknown> = Function & { prototype: T };\n\n/**\n * ApiRoutingFactory - Automatically wire API interfaces to controllers.\n * Reads @ApiPath/@Endpoint decorators from an API prototype class and\n * registers POST routes for each endpoint.\n *\n * Replaces the old RESTApiRoutes class.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new ApiRoutingFactory(SaveApi, SaveController),\n * ];\n * }\n * ```\n */\n// webpieces-disable no-any-unknown -- generic class requires unconstrained default type params\nexport class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController>;\n\n /**\n * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators\n * @param controllerClass - The controller class that implements the API\n */\n constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>) {\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked with @ApiPath\n if (!isApiPath(apiMetaClass)) {\n const className = apiMetaClass.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n // Validate that controllerClass actually extends apiMetaClass.\n // TypeScript's structural typing won't catch a missing `extends` here, so we check\n // the runtime prototype chain. Without this, a controller can silently drift from\n // the API contract (wrong method names, wrong signatures) and only fail later as a\n // confusing routing or method-not-found error.\n const apiName = apiMetaClass.name || 'Unknown';\n const controllerName = controllerClass.name || 'Unknown';\n if (!(apiMetaClass.prototype as object).isPrototypeOf(controllerClass.prototype as object)) {\n throw new Error(\n `Controller ${controllerName} must extend ${apiName}. ` +\n `Change the class declaration to: ` +\n `'export class ${controllerName} extends ${apiName} { ... }'`,\n );\n }\n\n }\n\n /**\n * Configure routes by reading @ApiPath + @Endpoint metadata.\n * Validates controller methods and auth decorators in single loop.\n */\n configure(routeBuilder: RouteBuilder): void {\n const basePath = getApiPath(this.apiMetaClass)!;\n const endpoints = getEndpoints(this.apiMetaClass) || {};\n const controllerFilepath = this.getControllerFilepath();\n const apiName = this.apiMetaClass.name || 'Unknown';\n const controllerName = this.controllerClass.name || 'Unknown';\n\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n // Validate controller implements this method\n if (typeof this.controllerClass.prototype[methodName] !== 'function') {\n throw new Error(\n `Controller ${controllerName} must implement method ${methodName} from API ${apiName}`,\n );\n }\n\n // Validate auth decorator exists (class-level or method-level)\n const authMeta = getAuthMeta(this.apiMetaClass, methodName);\n if (!authMeta) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no @Authentication decorator. ` +\n `Add @Authentication(new AuthenticationConfig(...)) to the class or method.`,\n );\n }\n\n const fullPath = basePath + endpointPath;\n const routeMeta = new RouteMetadata(\n 'POST',\n fullPath,\n methodName,\n controllerName,\n authMeta,\n );\n\n routeBuilder.addRoute(new RouteDefinition(routeMeta, this.controllerClass, controllerFilepath));\n }\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n */\n private getControllerFilepath(): string | undefined {\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = this.controllerClass.name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get auth metadata for a specific method, falling back to class-level.\n */\n getAuthMetaForMethod(methodName: string): AuthMeta | undefined {\n return getAuthMeta(this.apiMetaClass, methodName);\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): ClassType<TApi> {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerClass(): ClassType<TController> {\n return this.controllerClass;\n }\n}\n"]}
1
+ {"version":3,"file":"ApiRoutingFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiRoutingFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport { isApiPath, getApiPath, getEndpoints, getAuthMeta, RouteMetadata, AuthMeta } from '@webpieces/core-util';\nimport 'reflect-metadata';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\n/**\n * Type representing a class constructor (abstract or concrete).\n */\n// webpieces-disable no-any-unknown -- generic type alias requires unconstrained default\nexport type ClassType<T = unknown> = Function & { prototype: T };\n\n/**\n * ApiRoutingFactory - Automatically wire API interfaces to controllers.\n * Reads @ApiPath/@Endpoint decorators from an API prototype class and\n * registers POST routes for each endpoint.\n *\n * Replaces the old RESTApiRoutes class.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new ApiRoutingFactory(SaveApi, SaveController),\n * ];\n * }\n * ```\n */\n// webpieces-disable no-any-unknown -- generic class requires unconstrained default type params\nexport class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController>;\n\n /**\n * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators\n * @param controllerClass - The controller class that implements the API\n */\n constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>) {\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked with @ApiPath\n if (!isApiPath(apiMetaClass)) {\n const className = apiMetaClass.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n // Validate that controllerClass actually extends apiMetaClass.\n // TypeScript's structural typing won't catch a missing `extends` here, so we check\n // the runtime prototype chain. Without this, a controller can silently drift from\n // the API contract (wrong method names, wrong signatures) and only fail later as a\n // confusing routing or method-not-found error.\n const apiName = apiMetaClass.name || 'Unknown';\n const controllerName = controllerClass.name || 'Unknown';\n if (!(apiMetaClass.prototype as object).isPrototypeOf(controllerClass.prototype as object)) {\n throw new Error(\n `Controller ${controllerName} must extend ${apiName}. ` +\n `Change the class declaration to: ` +\n `'export class ${controllerName} extends ${apiName} { ... }'`,\n );\n }\n\n }\n\n /**\n * Configure routes by reading @ApiPath + @Endpoint metadata.\n * Validates controller methods and auth decorators in single loop.\n */\n configure(routeBuilder: RouteBuilder): void {\n const basePath = getApiPath(this.apiMetaClass)!;\n const endpoints = getEndpoints(this.apiMetaClass) || {};\n const controllerFilepath = this.getControllerFilepath();\n const apiName = this.apiMetaClass.name || 'Unknown';\n const controllerName = this.controllerClass.name || 'Unknown';\n\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n // Validate controller implements this method\n if (typeof this.controllerClass.prototype[methodName] !== 'function') {\n throw new Error(\n `Controller ${controllerName} must implement method ${methodName} from API ${apiName}`,\n );\n }\n\n // Validate auth decorator exists (class-level or method-level)\n const authMeta = getAuthMeta(this.apiMetaClass, methodName);\n if (!authMeta) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no @Authentication decorator. ` +\n `Add @Authentication(new AuthenticationConfig(...)) to the class or method.`,\n );\n }\n\n const fullPath = basePath + endpointPath;\n const routeMeta = new RouteMetadata(\n 'POST',\n fullPath,\n methodName,\n controllerName,\n authMeta,\n );\n\n routeBuilder.addRoute(\n new RouteDefinition(routeMeta, this.controllerClass, controllerFilepath, this.apiMetaClass),\n );\n }\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n */\n private getControllerFilepath(): string | undefined {\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = this.controllerClass.name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get auth metadata for a specific method, falling back to class-level.\n */\n getAuthMetaForMethod(methodName: string): AuthMeta | undefined {\n return getAuthMeta(this.apiMetaClass, methodName);\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): ClassType<TApi> {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerClass(): ClassType<TController> {\n return this.controllerClass;\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Principal - the authenticated caller established by {@link AuthConfig.verifyJwt}.
3
+ * Data-only structure (a class, per the webpieces guidelines).
4
+ */
5
+ export declare class Principal {
6
+ readonly userId: string;
7
+ readonly claims: Record<string, unknown>;
8
+ constructor(userId: string, claims?: Record<string, unknown>);
9
+ }
10
+ /**
11
+ * AuthConfig - the app-provided verifiers the framework {@link AuthFilter} injects to enforce
12
+ * each endpoint's AuthMode. It is an ABSTRACT CLASS (not a Symbol) so it is injected by type
13
+ * (per the webpieces no-symbol-di-tokens guidance) and rebindable in tests.
14
+ *
15
+ * It is BOUND IN THE APP CONTAINER (appBindings) — remember the two containers: the framework
16
+ * AuthFilter is resolved from the app child container, so the app's binding (or a test's
17
+ * appOverrides rebind) is what it sees. Keeping the concrete verifiers here (not in http-routing)
18
+ * means http-routing needs NO crypto / gcp-identity — it stays transport- and provider-neutral.
19
+ *
20
+ * A public-only server need not bind one (AuthFilter injects it @optional); a non-public route
21
+ * with no AuthConfig bound fails fast.
22
+ */
23
+ export declare abstract class AuthConfig {
24
+ /** Verify a user JWT (kind:'jwt'); return the principal or throw HttpUnauthorizedError. */
25
+ abstract verifyJwt(token: string): Principal;
26
+ /** Verify a Google OIDC token from an allowed caller SA (kind:'oidc'); throw on failure. */
27
+ abstract verifyOidc(token: string, callers: string[]): Promise<void>;
28
+ /** The expected shared secret for the given env var name (kind:'shared-secret'). */
29
+ abstract sharedSecret(secretEnv: string): string | undefined;
30
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthConfig = exports.Principal = void 0;
4
+ /**
5
+ * Principal - the authenticated caller established by {@link AuthConfig.verifyJwt}.
6
+ * Data-only structure (a class, per the webpieces guidelines).
7
+ */
8
+ class Principal {
9
+ userId;
10
+ claims;
11
+ constructor(userId,
12
+ // webpieces-disable no-any-unknown -- JWT claims are an arbitrary provider-defined bag
13
+ claims = {}) {
14
+ this.userId = userId;
15
+ this.claims = claims;
16
+ }
17
+ }
18
+ exports.Principal = Principal;
19
+ /**
20
+ * AuthConfig - the app-provided verifiers the framework {@link AuthFilter} injects to enforce
21
+ * each endpoint's AuthMode. It is an ABSTRACT CLASS (not a Symbol) so it is injected by type
22
+ * (per the webpieces no-symbol-di-tokens guidance) and rebindable in tests.
23
+ *
24
+ * It is BOUND IN THE APP CONTAINER (appBindings) — remember the two containers: the framework
25
+ * AuthFilter is resolved from the app child container, so the app's binding (or a test's
26
+ * appOverrides rebind) is what it sees. Keeping the concrete verifiers here (not in http-routing)
27
+ * means http-routing needs NO crypto / gcp-identity — it stays transport- and provider-neutral.
28
+ *
29
+ * A public-only server need not bind one (AuthFilter injects it @optional); a non-public route
30
+ * with no AuthConfig bound fails fast.
31
+ */
32
+ class AuthConfig {
33
+ }
34
+ exports.AuthConfig = AuthConfig;
35
+ //# sourceMappingURL=AuthConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthConfig.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,SAAS;IAEE;IAEA;IAHpB,YACoB,MAAc;IAC9B,uFAAuF;IACvE,SAAkC,EAAE;QAFpC,WAAM,GAAN,MAAM,CAAQ;QAEd,WAAM,GAAN,MAAM,CAA8B;IACrD,CAAC;CACP;AAND,8BAMC;AAED;;;;;;;;;;;;GAYG;AACH,MAAsB,UAAU;CAS/B;AATD,gCASC","sourcesContent":["/**\n * Principal - the authenticated caller established by {@link AuthConfig.verifyJwt}.\n * Data-only structure (a class, per the webpieces guidelines).\n */\nexport class Principal {\n constructor(\n public readonly userId: string,\n // webpieces-disable no-any-unknown -- JWT claims are an arbitrary provider-defined bag\n public readonly claims: Record<string, unknown> = {},\n ) {}\n}\n\n/**\n * AuthConfig - the app-provided verifiers the framework {@link AuthFilter} injects to enforce\n * each endpoint's AuthMode. It is an ABSTRACT CLASS (not a Symbol) so it is injected by type\n * (per the webpieces no-symbol-di-tokens guidance) and rebindable in tests.\n *\n * It is BOUND IN THE APP CONTAINER (appBindings) — remember the two containers: the framework\n * AuthFilter is resolved from the app child container, so the app's binding (or a test's\n * appOverrides rebind) is what it sees. Keeping the concrete verifiers here (not in http-routing)\n * means http-routing needs NO crypto / gcp-identity — it stays transport- and provider-neutral.\n *\n * A public-only server need not bind one (AuthFilter injects it @optional); a non-public route\n * with no AuthConfig bound fails fast.\n */\nexport abstract class AuthConfig {\n /** Verify a user JWT (kind:'jwt'); return the principal or throw HttpUnauthorizedError. */\n abstract verifyJwt(token: string): Principal;\n\n /** Verify a Google OIDC token from an allowed caller SA (kind:'oidc'); throw on failure. */\n abstract verifyOidc(token: string, callers: string[]): Promise<void>;\n\n /** The expected shared secret for the given env var name (kind:'shared-secret'). */\n abstract sharedSecret(secretEnv: string): string | undefined;\n}\n"]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * WpResponse - Wraps controller responses for the filter chain.
3
+ *
4
+ * Generic type parameter TResult represents the controller's return type.
5
+ * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
6
+ *
7
+ * The jsonTranslator middleware is responsible for:
8
+ * 1. Serializing WpResponse.response to JSON
9
+ * 2. Writing the JSON to the HTTP response body
10
+ * 3. Setting the HTTP status code from WpResponse.statusCode
11
+ */
12
+ export declare class WpResponse<TResult = unknown> {
13
+ response: TResult;
14
+ constructor(response: TResult);
15
+ }
16
+ /**
17
+ * Service interface - Similar to Java WebPieces Service<REQ, RESP>.
18
+ * Represents any component that can process a request and return a response.
19
+ *
20
+ * Used for:
21
+ * - Final controller invocation
22
+ * - Wrapping filters as services in the chain
23
+ * - Functional composition of filters
24
+ */
25
+ export interface Service<REQ, RESP> {
26
+ /**
27
+ * Invoke the service with the given metadata.
28
+ * @param meta - Request metadata
29
+ * @returns Promise of the response
30
+ */
31
+ invoke(meta: REQ): Promise<RESP>;
32
+ }
33
+ /**
34
+ * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
35
+ *
36
+ * Filters are STATELESS and can handle N concurrent requests.
37
+ * They wrap the execution of subsequent filters and the controller.
38
+ *
39
+ * Key principles:
40
+ * - STATELESS: No instance variables for request data
41
+ * - COMPOSABLE: Use chain() methods for functional composition
42
+ *
43
+ * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
44
+ * - MethodMeta: Standardized request metadata (defined in http-server)
45
+ * - WpResponse<unknown>: Wraps any controller response
46
+ */
47
+ export declare abstract class Filter<REQ, RESP> {
48
+ /**
49
+ * Filter method that wraps the next filter/controller.
50
+ *
51
+ * @param meta - Metadata about the method being invoked
52
+ * @param nextFilter - Next filter/controller as a Service
53
+ * @returns Promise of the response
54
+ */
55
+ abstract filter(meta: REQ, nextFilter: Service<REQ, RESP>): Promise<RESP>;
56
+ /**
57
+ * Chain this filter with another filter.
58
+ * Returns a new Filter that composes both filters.
59
+ *
60
+ * Similar to Java: filter1.chain(filter2)
61
+ *
62
+ * @param nextFilter - The filter to execute after this one
63
+ * @returns Composed filter
64
+ */
65
+ chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP>;
66
+ /**
67
+ * Chain this filter with a final service (controller).
68
+ * Returns a Service that can be invoked.
69
+ *
70
+ * Similar to Java: filter.chain(service)
71
+ *
72
+ * @param svc - The final service (controller) to execute
73
+ * @returns Service wrapping the entire filter chain
74
+ */
75
+ chainService(svc: Service<REQ, RESP>): Service<REQ, RESP>;
76
+ }
package/src/Filter.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Filter = exports.WpResponse = void 0;
4
+ /**
5
+ * WpResponse - Wraps controller responses for the filter chain.
6
+ *
7
+ * Generic type parameter TResult represents the controller's return type.
8
+ * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.
9
+ *
10
+ * The jsonTranslator middleware is responsible for:
11
+ * 1. Serializing WpResponse.response to JSON
12
+ * 2. Writing the JSON to the HTTP response body
13
+ * 3. Setting the HTTP status code from WpResponse.statusCode
14
+ */
15
+ // webpieces-disable no-any-unknown -- generic default: the filter chain handles all response types uniformly
16
+ class WpResponse {
17
+ response;
18
+ constructor(response) {
19
+ this.response = response;
20
+ }
21
+ }
22
+ exports.WpResponse = WpResponse;
23
+ /**
24
+ * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.
25
+ *
26
+ * Filters are STATELESS and can handle N concurrent requests.
27
+ * They wrap the execution of subsequent filters and the controller.
28
+ *
29
+ * Key principles:
30
+ * - STATELESS: No instance variables for request data
31
+ * - COMPOSABLE: Use chain() methods for functional composition
32
+ *
33
+ * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:
34
+ * - MethodMeta: Standardized request metadata (defined in http-server)
35
+ * - WpResponse<unknown>: Wraps any controller response
36
+ */
37
+ class Filter {
38
+ /**
39
+ * Chain this filter with another filter.
40
+ * Returns a new Filter that composes both filters.
41
+ *
42
+ * Similar to Java: filter1.chain(filter2)
43
+ *
44
+ * @param nextFilter - The filter to execute after this one
45
+ * @returns Composed filter
46
+ */
47
+ chain(nextFilter) {
48
+ const self = this;
49
+ return new (class extends Filter {
50
+ async filter(meta, nextService) {
51
+ // Call outer filter, passing next filter wrapped as a Service
52
+ return self.filter(meta, {
53
+ invoke: (m) => nextFilter.filter(m, nextService),
54
+ });
55
+ }
56
+ })();
57
+ }
58
+ /**
59
+ * Chain this filter with a final service (controller).
60
+ * Returns a Service that can be invoked.
61
+ *
62
+ * Similar to Java: filter.chain(service)
63
+ *
64
+ * @param svc - The final service (controller) to execute
65
+ * @returns Service wrapping the entire filter chain
66
+ */
67
+ chainService(svc) {
68
+ const self = this;
69
+ return {
70
+ invoke: (meta) => self.filter(meta, svc),
71
+ };
72
+ }
73
+ }
74
+ exports.Filter = Filter;
75
+ //# sourceMappingURL=Filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Filter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/Filter.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;GAUG;AACH,6GAA6G;AAC7G,MAAa,UAAU;IACnB,QAAQ,CAAU;IAElB,YAAY,QAAiB;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAND,gCAMC;AAoBD;;;;;;;;;;;;;GAaG;AACH,MAAsB,MAAM;IAaxB;;;;;;;;OAQG;IACH,KAAK,CAAC,UAA6B;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO,IAAI,CAAC,KAAM,SAAQ,MAAiB;YACvC,KAAK,CAAC,MAAM,CAAC,IAAS,EAAE,WAA+B;gBACnD,8DAA8D;gBAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;oBACrB,MAAM,EAAE,CAAC,CAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC;iBACxD,CAAC,CAAC;YACP,CAAC;SACJ,CAAC,EAAE,CAAC;IACT,CAAC;IAED;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAuB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,OAAO;YACH,MAAM,EAAE,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC;SAChD,CAAC;IACN,CAAC;CACJ;AAnDD,wBAmDC","sourcesContent":["/**\n * WpResponse - Wraps controller responses for the filter chain.\n *\n * Generic type parameter TResult represents the controller's return type.\n * The filter chain uses WpResponse<unknown> because it handles all response types uniformly.\n *\n * The jsonTranslator middleware is responsible for:\n * 1. Serializing WpResponse.response to JSON\n * 2. Writing the JSON to the HTTP response body\n * 3. Setting the HTTP status code from WpResponse.statusCode\n */\n// webpieces-disable no-any-unknown -- generic default: the filter chain handles all response types uniformly\nexport class WpResponse<TResult = unknown> {\n response: TResult;\n\n constructor(response: TResult) {\n this.response = response;\n }\n}\n\n/**\n * Service interface - Similar to Java WebPieces Service<REQ, RESP>.\n * Represents any component that can process a request and return a response.\n *\n * Used for:\n * - Final controller invocation\n * - Wrapping filters as services in the chain\n * - Functional composition of filters\n */\nexport interface Service<REQ, RESP> {\n /**\n * Invoke the service with the given metadata.\n * @param meta - Request metadata\n * @returns Promise of the response\n */\n invoke(meta: REQ): Promise<RESP>;\n}\n\n/**\n * Filter abstract class - Similar to Java WebPieces Filter<REQ, RESP>.\n *\n * Filters are STATELESS and can handle N concurrent requests.\n * They wrap the execution of subsequent filters and the controller.\n *\n * Key principles:\n * - STATELESS: No instance variables for request data\n * - COMPOSABLE: Use chain() methods for functional composition\n *\n * For HTTP filters, use Filter<MethodMeta, WpResponse<unknown>>:\n * - MethodMeta: Standardized request metadata (defined in http-server)\n * - WpResponse<unknown>: Wraps any controller response\n */\nexport abstract class Filter<REQ, RESP> {\n //priority is determined by how it is chained only here\n //DO NOT add priority here\n\n /**\n * Filter method that wraps the next filter/controller.\n *\n * @param meta - Metadata about the method being invoked\n * @param nextFilter - Next filter/controller as a Service\n * @returns Promise of the response\n */\n abstract filter(meta: REQ, nextFilter: Service<REQ, RESP>): Promise<RESP>;\n\n /**\n * Chain this filter with another filter.\n * Returns a new Filter that composes both filters.\n *\n * Similar to Java: filter1.chain(filter2)\n *\n * @param nextFilter - The filter to execute after this one\n * @returns Composed filter\n */\n chain(nextFilter: Filter<REQ, RESP>): Filter<REQ, RESP> {\n const self = this;\n\n return new (class extends Filter<REQ, RESP> {\n async filter(meta: REQ, nextService: Service<REQ, RESP>): Promise<RESP> {\n // Call outer filter, passing next filter wrapped as a Service\n return self.filter(meta, {\n invoke: (m: REQ) => nextFilter.filter(m, nextService),\n });\n }\n })();\n }\n\n /**\n * Chain this filter with a final service (controller).\n * Returns a Service that can be invoked.\n *\n * Similar to Java: filter.chain(service)\n *\n * @param svc - The final service (controller) to execute\n * @returns Service wrapping the entire filter chain\n */\n chainService(svc: Service<REQ, RESP>): Service<REQ, RESP> {\n const self = this;\n\n return {\n invoke: (meta: REQ) => self.filter(meta, svc),\n };\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { Filter } from './Filter';
2
+ /**
3
+ * FilterChain - Manages execution of filters in priority order.
4
+ * Similar to Java servlet filter chains.
5
+ *
6
+ * Filters are sorted by priority (highest first) and each filter
7
+ * calls nextFilter.invoke() to invoke the next filter in the chain.
8
+ *
9
+ * The final "filter" in the chain is the controller method itself.
10
+ */
11
+ export declare class FilterChain<REQ, RESP> {
12
+ private filters;
13
+ constructor(filters: Filter<REQ, RESP>[]);
14
+ /**
15
+ * Execute the filter chain.
16
+ *
17
+ * @param meta - Request metadata
18
+ * @param finalHandler - The controller method to execute at the end
19
+ * @returns Promise of the response
20
+ */
21
+ execute(meta: REQ, finalHandler: () => Promise<RESP>): Promise<RESP>;
22
+ /**
23
+ * Get all filters in the chain (sorted by priority).
24
+ */
25
+ getFilters(): Filter<REQ, RESP>[];
26
+ /**
27
+ * Get the number of filters in the chain.
28
+ */
29
+ size(): number;
30
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterChain = void 0;
4
+ /**
5
+ * FilterChain - Manages execution of filters in priority order.
6
+ * Similar to Java servlet filter chains.
7
+ *
8
+ * Filters are sorted by priority (highest first) and each filter
9
+ * calls nextFilter.invoke() to invoke the next filter in the chain.
10
+ *
11
+ * The final "filter" in the chain is the controller method itself.
12
+ */
13
+ class FilterChain {
14
+ filters;
15
+ constructor(filters) {
16
+ // Filters are already sorted by priority from FilterMatcher
17
+ // No need to sort again (priority is in FilterDefinition, not Filter)
18
+ this.filters = filters;
19
+ }
20
+ /**
21
+ * Execute the filter chain.
22
+ *
23
+ * @param meta - Request metadata
24
+ * @param finalHandler - The controller method to execute at the end
25
+ * @returns Promise of the response
26
+ */
27
+ async execute(meta, finalHandler) {
28
+ const filters = this.filters;
29
+ // Create Service adapter that recursively calls filters
30
+ const createServiceForIndex = (currentIndex) => {
31
+ return {
32
+ invoke: async (m) => {
33
+ if (currentIndex < filters.length) {
34
+ const filter = filters[currentIndex];
35
+ const nextService = createServiceForIndex(currentIndex + 1);
36
+ return filter.filter(m, nextService);
37
+ }
38
+ else {
39
+ // All filters executed, now execute the controller
40
+ return finalHandler();
41
+ }
42
+ },
43
+ };
44
+ };
45
+ // Start execution with first filter
46
+ const service = createServiceForIndex(0);
47
+ return service.invoke(meta);
48
+ }
49
+ /**
50
+ * Get all filters in the chain (sorted by priority).
51
+ */
52
+ getFilters() {
53
+ return [...this.filters];
54
+ }
55
+ /**
56
+ * Get the number of filters in the chain.
57
+ */
58
+ size() {
59
+ return this.filters.length;
60
+ }
61
+ }
62
+ exports.FilterChain = FilterChain;
63
+ //# sourceMappingURL=FilterChain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterChain.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/FilterChain.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;GAQG;AACH,MAAa,WAAW;IACZ,OAAO,CAAsB;IAErC,YAAY,OAA4B;QACpC,4DAA4D;QAC5D,sEAAsE;QACtE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,IAAS,EAAE,YAAiC;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE7B,wDAAwD;QACxD,MAAM,qBAAqB,GAAG,CAAC,YAAoB,EAAsB,EAAE;YACvE,OAAO;gBACH,MAAM,EAAE,KAAK,EAAE,CAAM,EAAiB,EAAE;oBACpC,IAAI,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;wBAChC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;wBACrC,MAAM,WAAW,GAAG,qBAAqB,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;wBAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;oBACzC,CAAC;yBAAM,CAAC;wBACJ,mDAAmD;wBACnD,OAAO,YAAY,EAAE,CAAC;oBAC1B,CAAC;gBACL,CAAC;aACJ,CAAC;QACN,CAAC,CAAC;QAEF,oCAAoC;QACpC,MAAM,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,UAAU;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,IAAI;QACA,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;CACJ;AArDD,kCAqDC","sourcesContent":["import { Filter, Service } from './Filter';\n\n/**\n * FilterChain - Manages execution of filters in priority order.\n * Similar to Java servlet filter chains.\n *\n * Filters are sorted by priority (highest first) and each filter\n * calls nextFilter.invoke() to invoke the next filter in the chain.\n *\n * The final \"filter\" in the chain is the controller method itself.\n */\nexport class FilterChain<REQ, RESP> {\n private filters: Filter<REQ, RESP>[];\n\n constructor(filters: Filter<REQ, RESP>[]) {\n // Filters are already sorted by priority from FilterMatcher\n // No need to sort again (priority is in FilterDefinition, not Filter)\n this.filters = filters;\n }\n\n /**\n * Execute the filter chain.\n *\n * @param meta - Request metadata\n * @param finalHandler - The controller method to execute at the end\n * @returns Promise of the response\n */\n async execute(meta: REQ, finalHandler: () => Promise<RESP>): Promise<RESP> {\n const filters = this.filters;\n\n // Create Service adapter that recursively calls filters\n const createServiceForIndex = (currentIndex: number): Service<REQ, RESP> => {\n return {\n invoke: async (m: REQ): Promise<RESP> => {\n if (currentIndex < filters.length) {\n const filter = filters[currentIndex];\n const nextService = createServiceForIndex(currentIndex + 1);\n return filter.filter(m, nextService);\n } else {\n // All filters executed, now execute the controller\n return finalHandler();\n }\n },\n };\n };\n\n // Start execution with first filter\n const service = createServiceForIndex(0);\n return service.invoke(meta);\n }\n\n /**\n * Get all filters in the chain (sorted by priority).\n */\n getFilters(): Filter<REQ, RESP>[] {\n return [...this.filters];\n }\n\n /**\n * Get the number of filters in the chain.\n */\n size(): number {\n return this.filters.length;\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { Filter, WpResponse } from '@webpieces/http-filters';
2
- import { MethodMeta } from '@webpieces/http-filters';
1
+ import { Filter, WpResponse } from './Filter';
2
+ import { MethodMeta } from './MethodMeta';
3
3
  import { FilterDefinition } from './WebAppMeta';
4
4
  /**
5
5
  * Type alias for HTTP filters that work with MethodMeta and ResponseWrapper.
@@ -1 +1 @@
1
- {"version":3,"file":"FilterMatcher.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/FilterMatcher.ts"],"names":[],"mappings":";;;AAGA,yCAAsC;AAOtC;;;;;;;;;;;;GAYG;AACH,MAAa,aAAa;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CACtB,kBAAsC,EACtC,UAAmC;QAEnC,MAAM,eAAe,GAAoD,EAAE,CAAC;QAE5E,KAAK,MAAM,UAAU,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC;YAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAoB,CAAC;YAE/C,4DAA4D;YAC5D,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;gBAClB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAChE,SAAS;YACb,CAAC;YAED,yDAAyD;YACzD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACtB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;oBACrB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACpE,CAAC;gBACD,SAAS;YACb,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;gBACrC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;YACpE,CAAC;QACL,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;IACtD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAgB;QACrC,OAAO,QAAQ;aACV,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,yCAAyC;aAC7D,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB;IACrD,CAAC;CACJ;AA5DD,sCA4DC","sourcesContent":["import { Filter, WpResponse } from '@webpieces/http-filters';\nimport { MethodMeta } from '@webpieces/http-filters';\nimport { FilterDefinition } from './WebAppMeta';\nimport { minimatch } from 'minimatch';\n\n/**\n * Type alias for HTTP filters that work with MethodMeta and ResponseWrapper.\n */\nexport type HttpFilter = Filter<MethodMeta, WpResponse<unknown>>;\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<FilterDefinition>,\n ): HttpFilter[] {\n const matchingFilters: Array<{ filter: HttpFilter; priority: number }> = [];\n\n for (const definition of allFilters) {\n const pattern = definition.filepathPattern;\n const filter = definition.filter as HttpFilter;\n\n // Special case: '*' matches all controllers (global filter)\n if (pattern === '*') {\n matchingFilters.push({ filter, priority: definition.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: definition.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: definition.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
+ {"version":3,"file":"FilterMatcher.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/FilterMatcher.ts"],"names":[],"mappings":";;;AAGA,yCAAsC;AAOtC;;;;;;;;;;;;GAYG;AACH,MAAa,aAAa;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CACtB,kBAAsC,EACtC,UAAmC;QAEnC,MAAM,eAAe,GAAoD,EAAE,CAAC;QAE5E,KAAK,MAAM,UAAU,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,UAAU,CAAC,eAAe,CAAC;YAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAoB,CAAC;YAE/C,4DAA4D;YAC5D,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;gBAClB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAChE,SAAS;YACb,CAAC;YAED,yDAAyD;YACzD,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBACtB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;oBACrB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACpE,CAAC;gBACD,SAAS;YACb,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;gBACrC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;YACpE,CAAC;QACL,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;IACtD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAgB;QACrC,OAAO,QAAQ;aACV,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,yCAAyC;aAC7D,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,sBAAsB;IACrD,CAAC;CACJ;AA5DD,sCA4DC","sourcesContent":["import { Filter, WpResponse } from './Filter';\nimport { MethodMeta } from './MethodMeta';\nimport { FilterDefinition } from './WebAppMeta';\nimport { minimatch } from 'minimatch';\n\n/**\n * Type alias for HTTP filters that work with MethodMeta and ResponseWrapper.\n */\nexport type HttpFilter = Filter<MethodMeta, WpResponse<unknown>>;\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<FilterDefinition>,\n ): HttpFilter[] {\n const matchingFilters: Array<{ filter: HttpFilter; priority: number }> = [];\n\n for (const definition of allFilters) {\n const pattern = definition.filepathPattern;\n const filter = definition.filter as HttpFilter;\n\n // Special case: '*' matches all controllers (global filter)\n if (pattern === '*') {\n matchingFilters.push({ filter, priority: definition.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: definition.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: definition.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"]}
@@ -16,6 +16,7 @@ import { RouteBuilderImpl } from './RouteBuilderImpl';
16
16
  */
17
17
  export declare class InProcessApiClientFactory {
18
18
  private routeBuilder;
19
+ private readonly contextMgr;
19
20
  constructor(routeBuilder: RouteBuilderImpl);
20
21
  /**
21
22
  * Create an API client proxy for testing.
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InProcessApiClientFactory = void 0;
4
4
  const core_util_1 = require("@webpieces/core-util");
5
- const http_filters_1 = require("@webpieces/http-filters");
5
+ const MethodMeta_1 = require("./MethodMeta");
6
6
  const core_context_1 = require("@webpieces/core-context");
7
+ const fillContext_1 = require("./fillContext");
7
8
  /**
8
9
  * InProcessApiClientFactory - Creates API client proxies that invoke routes
9
10
  * in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.
@@ -21,6 +22,9 @@ const core_context_1 = require("@webpieces/core-context");
21
22
  */
22
23
  class InProcessApiClientFactory {
23
24
  routeBuilder;
25
+ // Builds request headers the SAME way the real HTTP client does — from the ambient
26
+ // RequestContext — so a credential a test put in context travels as a real request header.
27
+ contextMgr = new core_util_1.ContextMgr(new core_context_1.RequestContextReader());
24
28
  constructor(routeBuilder) {
25
29
  this.routeBuilder = routeBuilder;
26
30
  }
@@ -68,8 +72,16 @@ class InProcessApiClientFactory {
68
72
  }
69
73
  // webpieces-disable no-any-unknown -- DTO types are erased at the routing layer
70
74
  async runMethod(routeMeta, requestDto, service) {
71
- // Create MethodMeta without headers (in-process mode - no HTTP involved)
72
- const meta = new http_filters_1.MethodMeta(routeMeta, undefined, requestDto);
75
+ // In-process: publish a transport-neutral HttpRequest (headers come from whatever the
76
+ // caller set in the context; empty by default) so the SAME chain that runs over HTTP
77
+ // can read RequestContext.getRequest(). Then build the DTO-only meta.
78
+ const headers = new Map();
79
+ this.contextMgr.buildOutboundHeaders().forEach((value, name) => {
80
+ headers.set(name.toLowerCase(), [value]);
81
+ });
82
+ core_context_1.RequestContext.setRequest(new core_context_1.HttpRequest(routeMeta.httpMethod, routeMeta.path, headers));
83
+ (0, fillContext_1.fillContext)();
84
+ const meta = new MethodMeta_1.MethodMeta(routeMeta, requestDto);
73
85
  const responseWrapper = await service.invoke(meta);
74
86
  return responseWrapper.response;
75
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"InProcessApiClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/InProcessApiClientFactory.ts"],"names":[],"mappings":";;;AAAA,oDAK8B;AAC9B,0DAA0E;AAC1E,0DAAyD;AAGzD;;;;;;;;;;;;;;GAcG;AACH,MAAa,yBAAyB;IACd;IAApB,YAAoB,YAA8B;QAA9B,iBAAY,GAAZ,YAAY,CAAkB;IAAG,CAAC;IAEtD;;;;;;;;;;;OAWG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,IAAA,sBAAU,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAEnD,sBAAsB;QACtB,kFAAkF;QAClF,MAAM,KAAK,GAA4B,EAAE,CAAC;QAE1C,qDAAqD;QACrD,KAAK,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,MAAM,UAAU,GAAG,MAAM,CAAC;YAC1B,MAAM,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAC;YAErC,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACvD,MAAM,SAAS,GAAG,IAAI,yBAAa,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAE/F,8EAA8E;YAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEvE,uEAAuE;YACvE,2FAA2F;YAC3F,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,EAAE,UAAmB,EAAoB,EAAE;gBAChE,0EAA0E;gBAC1E,IAAI,CAAC,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;oBAC7B,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;wBACjC,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;oBAChE,CAAC,CAAC,CAAC;gBACP,CAAC;gBACD,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YAChE,CAAC,CAAC;QACN,CAAC;QAED,OAAO,KAAU,CAAC;IACtB,CAAC;IAED,gFAAgF;IACxE,KAAK,CAAC,SAAS,CAAC,SAAwB,EAAE,UAAmB,EAAE,OAAiD;QACpH,yEAAyE;QACzE,MAAM,IAAI,GAAG,IAAI,yBAAU,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QAC9D,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;CACJ;AA3DD,8DA2DC","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpoints,\n RouteMetadata,\n} from '@webpieces/core-util';\nimport { MethodMeta, Service, WpResponse } from '@webpieces/http-filters';\nimport { RequestContext } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\n\n/**\n * InProcessApiClientFactory - Creates API client proxies that invoke routes\n * in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.\n *\n * This is the PRIMARY in-process/test builder. It lives in the node-only http-routing\n * package (no express dependency) so both the node-only WebpiecesRouter and the express\n * adapter (WebpiecesRouteCreator) share one code path.\n *\n * The client uses the ApiPrototype class to discover routes via decorators,\n * then creates pre-configured invoker functions for each API method.\n *\n * IMPORTANT: This loops over the API methods (from decorators), NOT all routes.\n * For each API method, it sets up the filter chain ONCE during proxy creation,\n * so subsequent calls reuse the same filter chain (efficient!).\n */\nexport class InProcessApiClientFactory {\n constructor(private routeBuilder: RouteBuilderImpl) {}\n\n /**\n * Create an API client proxy for testing.\n *\n * @param apiPrototype - The API prototype class with routing decorators (can be abstract)\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = factory.createApiClient<SaveApi>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n // Get endpoints from the API prototype using @ApiPath/@Endpoint decorators\n const basePath = getApiPath(apiPrototype) || '';\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // Create proxy object\n // webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes\n const proxy: Record<string, unknown> = {};\n\n // Loop over API endpoints and create proxy functions\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const httpMethod = 'POST';\n const path = basePath + endpointPath;\n\n const authMeta = getAuthMeta(apiPrototype, methodName);\n const routeMeta = new RouteMetadata(httpMethod, path, methodName, apiPrototype.name, authMeta);\n\n // Create invoker service ONCE (sets up filter chain once, not on every call!)\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // Proxy method creates MethodMeta and calls the pre-configured service\n // webpieces-disable no-any-unknown -- request/response DTO types are erased at proxy level\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Auto-activate a RequestContext if the test did not wrap the call itself\n if (!RequestContext.isActive()) {\n return RequestContext.run(async () => {\n return await this.runMethod(routeMeta, requestDto, service);\n });\n }\n return await this.runMethod(routeMeta, requestDto, service);\n };\n }\n\n return proxy as T;\n }\n\n // webpieces-disable no-any-unknown -- DTO types are erased at the routing layer\n private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>): Promise<unknown> {\n // Create MethodMeta without headers (in-process mode - no HTTP involved)\n const meta = new MethodMeta(routeMeta, undefined, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n }\n}\n"]}
1
+ {"version":3,"file":"InProcessApiClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/InProcessApiClientFactory.ts"],"names":[],"mappings":";;;AAAA,oDAM8B;AAC9B,6CAA0C;AAE1C,0DAA4F;AAE5F,+CAA4C;AAE5C;;;;;;;;;;;;;;GAcG;AACH,MAAa,yBAAyB;IAKd;IAJpB,mFAAmF;IACnF,2FAA2F;IAC1E,UAAU,GAAG,IAAI,sBAAU,CAAC,IAAI,mCAAoB,EAAE,CAAC,CAAC;IAEzE,YAAoB,YAA8B;QAA9B,iBAAY,GAAZ,YAAY,CAAkB;IAAG,CAAC;IAEtD;;;;;;;;;;;OAWG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,IAAA,sBAAU,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAEnD,sBAAsB;QACtB,kFAAkF;QAClF,MAAM,KAAK,GAA4B,EAAE,CAAC;QAE1C,qDAAqD;QACrD,KAAK,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,MAAM,UAAU,GAAG,MAAM,CAAC;YAC1B,MAAM,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAC;YAErC,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACvD,MAAM,SAAS,GAAG,IAAI,yBAAa,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAE/F,8EAA8E;YAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEvE,uEAAuE;YACvE,2FAA2F;YAC3F,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,EAAE,UAAmB,EAAoB,EAAE;gBAChE,0EAA0E;gBAC1E,IAAI,CAAC,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;oBAC7B,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;wBACjC,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;oBAChE,CAAC,CAAC,CAAC;gBACP,CAAC;gBACD,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YAChE,CAAC,CAAC;QACN,CAAC;QAED,OAAO,KAAU,CAAC;IACtB,CAAC;IAED,gFAAgF;IACxE,KAAK,CAAC,SAAS,CAAC,SAAwB,EAAE,UAAmB,EAAE,OAAiD;QACpH,sFAAsF;QACtF,qFAAqF;QACrF,sEAAsE;QACtE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,OAAO,CAAC,CAAC,KAAa,EAAE,IAAY,EAAE,EAAE;YAC3E,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,6BAAc,CAAC,UAAU,CAAC,IAAI,0BAAW,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1F,IAAA,yBAAW,GAAE,CAAC;QACd,MAAM,IAAI,GAAG,IAAI,uBAAU,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QACnD,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;CACJ;AAvED,8DAuEC","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpoints,\n RouteMetadata,\n ContextMgr,\n} from '@webpieces/core-util';\nimport { MethodMeta } from './MethodMeta';\nimport { Service, WpResponse } from './Filter';\nimport { RequestContext, HttpRequest, RequestContextReader } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { fillContext } from './fillContext';\n\n/**\n * InProcessApiClientFactory - Creates API client proxies that invoke routes\n * in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.\n *\n * This is the PRIMARY in-process/test builder. It lives in the node-only http-routing\n * package (no express dependency) so both the node-only WebpiecesRouter and the express\n * adapter (WebpiecesRouteCreator) share one code path.\n *\n * The client uses the ApiPrototype class to discover routes via decorators,\n * then creates pre-configured invoker functions for each API method.\n *\n * IMPORTANT: This loops over the API methods (from decorators), NOT all routes.\n * For each API method, it sets up the filter chain ONCE during proxy creation,\n * so subsequent calls reuse the same filter chain (efficient!).\n */\nexport class InProcessApiClientFactory {\n // Builds request headers the SAME way the real HTTP client does — from the ambient\n // RequestContext — so a credential a test put in context travels as a real request header.\n private readonly contextMgr = new ContextMgr(new RequestContextReader());\n\n constructor(private routeBuilder: RouteBuilderImpl) {}\n\n /**\n * Create an API client proxy for testing.\n *\n * @param apiPrototype - The API prototype class with routing decorators (can be abstract)\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = factory.createApiClient<SaveApi>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n // Get endpoints from the API prototype using @ApiPath/@Endpoint decorators\n const basePath = getApiPath(apiPrototype) || '';\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // Create proxy object\n // webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes\n const proxy: Record<string, unknown> = {};\n\n // Loop over API endpoints and create proxy functions\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const httpMethod = 'POST';\n const path = basePath + endpointPath;\n\n const authMeta = getAuthMeta(apiPrototype, methodName);\n const routeMeta = new RouteMetadata(httpMethod, path, methodName, apiPrototype.name, authMeta);\n\n // Create invoker service ONCE (sets up filter chain once, not on every call!)\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // Proxy method creates MethodMeta and calls the pre-configured service\n // webpieces-disable no-any-unknown -- request/response DTO types are erased at proxy level\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Auto-activate a RequestContext if the test did not wrap the call itself\n if (!RequestContext.isActive()) {\n return RequestContext.run(async () => {\n return await this.runMethod(routeMeta, requestDto, service);\n });\n }\n return await this.runMethod(routeMeta, requestDto, service);\n };\n }\n\n return proxy as T;\n }\n\n // webpieces-disable no-any-unknown -- DTO types are erased at the routing layer\n private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>): Promise<unknown> {\n // In-process: publish a transport-neutral HttpRequest (headers come from whatever the\n // caller set in the context; empty by default) so the SAME chain that runs over HTTP\n // can read RequestContext.getRequest(). Then build the DTO-only meta.\n const headers = new Map<string, string[]>();\n this.contextMgr.buildOutboundHeaders().forEach((value: string, name: string) => {\n headers.set(name.toLowerCase(), [value]);\n });\n RequestContext.setRequest(new HttpRequest(routeMeta.httpMethod, routeMeta.path, headers));\n fillContext();\n const meta = new MethodMeta(routeMeta, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n }\n}\n"]}