@webpieces/http-server 0.3.298 → 0.3.300

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-server",
3
- "version": "0.3.298",
3
+ "version": "0.3.300",
4
4
  "description": "WebPieces server with filter chain and dependency injection",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,10 +22,10 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-context": "0.3.298",
26
- "@webpieces/core-util": "0.3.298",
27
- "@webpieces/gcp-identity": "0.3.298",
28
- "@webpieces/http-routing": "0.3.298",
25
+ "@webpieces/core-context": "0.3.300",
26
+ "@webpieces/core-util": "0.3.300",
27
+ "@webpieces/gcp-identity": "0.3.300",
28
+ "@webpieces/http-routing": "0.3.300",
29
29
  "cors": "2.8.5",
30
30
  "express": "5.1.0",
31
31
  "inversify": "7.10.4"
@@ -42,8 +42,12 @@ export declare class WebpiecesExpressRouter {
42
42
  */
43
43
  bindAndStartExpress(app: Express, port?: number): Promise<HttpServer>;
44
44
  /**
45
- * Wrap one ApiClient's impl in an express handler (RequestContext.run, header read, manual
46
- * JSON body parse, error→ProtocolError) and register it on the app for its method + path.
45
+ * Bind EACH method of one ApiClient. The api's @ApiPath/@Endpoint decorators give the paths;
46
+ * for each we wrap the matching client method (the proxy RequestContext.run + header read +
47
+ * JSON body parse + error→ProtocolError all live in the wrapper/chain) and register the route.
48
+ * This is one-to-one with a test: an HTTP POST maps straight to `client[method](dto)`.
49
+ *
50
+ * @returns the number of routes mounted for this api.
47
51
  */
48
52
  private mountApiClient;
49
53
  private registerHandler;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebpiecesExpressRouter = void 0;
4
+ const http_routing_1 = require("@webpieces/http-routing");
4
5
  const core_util_1 = require("@webpieces/core-util");
5
6
  const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
6
7
  const log = core_util_1.LogManager.getLogger('WebpiecesExpressRouter');
@@ -39,11 +40,11 @@ class WebpiecesExpressRouter {
39
40
  * routes must stay untouched. The caller owns app.listen() and any global middleware.
40
41
  */
41
42
  bindExpress(app) {
42
- const apiClients = this.apiFactory.apiClients();
43
- for (const apiClient of apiClients) {
44
- this.mountApiClient(app, apiClient);
43
+ let count = 0;
44
+ for (const apiClient of this.apiFactory.apiClients()) {
45
+ count += this.mountApiClient(app, apiClient);
45
46
  }
46
- log.info(`[WebpiecesExpressRouter] Mounted ${apiClients.length} webpieces route(s) onto express`);
47
+ log.info(`[WebpiecesExpressRouter] Mounted ${count} webpieces route(s) onto express`);
47
48
  }
48
49
  /**
49
50
  * Add the webpieces global middleware (HTML error page, localhost CORS, request logging),
@@ -69,12 +70,25 @@ class WebpiecesExpressRouter {
69
70
  });
70
71
  }
71
72
  /**
72
- * Wrap one ApiClient's impl in an express handler (RequestContext.run, header read, manual
73
- * JSON body parse, error→ProtocolError) and register it on the app for its method + path.
73
+ * Bind EACH method of one ApiClient. The api's @ApiPath/@Endpoint decorators give the paths;
74
+ * for each we wrap the matching client method (the proxy RequestContext.run + header read +
75
+ * JSON body parse + error→ProtocolError all live in the wrapper/chain) and register the route.
76
+ * This is one-to-one with a test: an HTTP POST maps straight to `client[method](dto)`.
77
+ *
78
+ * @returns the number of routes mounted for this api.
74
79
  */
75
80
  mountApiClient(app, apiClient) {
76
- const wrapper = this.middleware.createExpressWrapper(apiClient.impl, apiClient.routeMeta);
77
- this.registerHandler(app, apiClient.routeMeta.httpMethod, apiClient.routeMeta.path, wrapper.execute.bind(wrapper));
81
+ const basePath = (0, http_routing_1.getApiPath)(apiClient.api) || '';
82
+ const endpoints = (0, http_routing_1.getEndpoints)(apiClient.api) || {};
83
+ let count = 0;
84
+ for (const [methodName, endpointPath] of Object.entries(endpoints)) {
85
+ const path = basePath + endpointPath;
86
+ const wrapper = this.middleware.createExpressWrapper(apiClient.client[methodName], path);
87
+ // All webpieces routes are POST (the api-tier convention).
88
+ this.registerHandler(app, 'POST', path, wrapper.execute.bind(wrapper));
89
+ count++;
90
+ }
91
+ return count;
78
92
  }
79
93
  registerHandler(app, httpMethod, path, expressHandler) {
80
94
  switch (httpMethod.toLowerCase()) {
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesExpressRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesExpressRouter.ts"],"names":[],"mappings":";;;AAEA,oDAAkD;AAClD,+DAAiF;AAEjF,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;AAK3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,sBAAsB;IAGF;IAFZ,UAAU,GAAG,IAAI,yCAAmB,EAAE,CAAC;IAExD,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEvD;;;;;;OAMG;IACH,WAAW,CAAC,GAAY;QACpB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QAChD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACxC,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,oCAAoC,UAAU,CAAC,MAAM,kCAAkC,CAAC,CAAC;IACtG,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,mBAAmB,CAAC,GAAY,EAAE,OAAe,IAAI;QACvD,+EAA+E;QAC/E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAClE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAC5C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAE5D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAEtB,OAAO,IAAI,OAAO,CACd,CAAC,OAAqC,EAAE,MAA4B,EAAE,EAAE;YACpE,MAAM,MAAM,GAAe,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC1D,IAAI,KAAK,EAAE,CAAC;oBACR,GAAG,CAAC,KAAK,CAAC,oDAAoD,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;oBAC9E,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACX,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,0DAA0D,IAAI,EAAE,CAAC,CAAC;gBAC3E,OAAO,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC,CAAC,CAAC;QACP,CAAC,CACJ,CAAC;IACN,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,GAAY,EAAE,SAAoB;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAC1F,IAAI,CAAC,eAAe,CAChB,GAAG,EACH,SAAS,CAAC,SAAS,CAAC,UAAU,EAC9B,SAAS,CAAC,SAAS,CAAC,IAAI,EACxB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAChC,CAAC;IACN,CAAC;IAEO,eAAe,CACnB,GAAY,EACZ,UAAkB,EAClB,IAAY,EACZ,cAAmC;QAEnC,QAAQ,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;YAC/B,KAAK,KAAK;gBACN,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBAC9B,MAAM;YACV,KAAK,MAAM;gBACP,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBAC/B,MAAM;YACV,KAAK,KAAK;gBACN,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBAC9B,MAAM;YACV,KAAK,QAAQ;gBACT,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBACjC,MAAM;YACV,KAAK,OAAO;gBACR,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBAChC,MAAM;YACV;gBACI,GAAG,CAAC,IAAI,CAAC,iDAAiD,UAAU,EAAE,CAAC,CAAC;QAChF,CAAC;IACL,CAAC;CACJ;AAxFD,wDAwFC","sourcesContent":["import { Express } from 'express';\nimport { ApiFactory, ApiClient } from '@webpieces/http-routing';\nimport { LogManager } from '@webpieces/core-util';\nimport { WebpiecesMiddleware, ExpressRouteHandler } from './WebpiecesMiddleware';\n\nconst log = LogManager.getLogger('WebpiecesExpressRouter');\n\n/** The value returned by express `app.listen(...)` (a node http.Server). */\ntype HttpServer = ReturnType<Express['listen']>;\n\n/**\n * WebpiecesExpressRouter - the express layer that sits ON TOP of a node-only\n * {@link ApiFactory} (a WebpiecesRouter). It is the ONLY place express lifecycle lives.\n *\n * It never reaches into routing internals: it asks the ApiFactory for `apiClients()` — each\n * an api + routeMeta + composed filter-chain→controller impl — and binds each to an express\n * route (`app.<verb>(path, handler)`) invoked when the matching HTTP request arrives. The\n * RouteBuilder stays hidden inside the ApiFactory.\n *\n * ```typescript\n * const apiFactory = await WebpiecesRouterFactory.create(config, { appBindings });\n * apiFactory.addRoutes(SaveApi, SaveController);\n * const express = new WebpiecesExpressRouter(apiFactory);\n *\n * // legacy / side-by-side: mount onto an existing app; you own listen + your middleware\n * express.bindExpress(existingApp);\n *\n * // non-legacy: add webpieces global middleware + listen for you\n * await express.bindAndStartExpress(express(), 8080);\n * ```\n */\nexport class WebpiecesExpressRouter {\n private readonly middleware = new WebpiecesMiddleware();\n\n constructor(private readonly apiFactory: ApiFactory) {}\n\n /**\n * Mount the webpieces routes (each fully self-contained: own body parse, RequestContext,\n * express-tier + api-tier filter chain, error→JSON) onto the caller's express app.\n *\n * Adds NO global app.use() middleware, so it is safe to attach to a legacy app whose other\n * routes must stay untouched. The caller owns app.listen() and any global middleware.\n */\n bindExpress(app: Express): void {\n const apiClients = this.apiFactory.apiClients();\n for (const apiClient of apiClients) {\n this.mountApiClient(app, apiClient);\n }\n log.info(`[WebpiecesExpressRouter] Mounted ${apiClients.length} webpieces route(s) onto express`);\n }\n\n /**\n * Add the webpieces global middleware (HTML error page, localhost CORS, request logging),\n * bind the routes, then app.listen(port). Convenience for a non-legacy webpieces server\n * where webpieces owns the whole express app. Resolves with the http.Server once listening.\n */\n async bindAndStartExpress(app: Express, port: number = 8080): Promise<HttpServer> {\n // Global middleware layers (outermost first) — only for a webpieces-owned app.\n app.use(this.middleware.globalErrorHandler.bind(this.middleware));\n app.use(this.middleware.corsForLocalhost());\n app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n this.bindExpress(app);\n\n return new Promise<HttpServer>(\n (resolve: (server: HttpServer) => void, reject: (err: Error) => void) => {\n const server: HttpServer = app.listen(port, (error?: Error) => {\n if (error) {\n log.error(`[WebpiecesExpressRouter] Failed to start on port ${port}:`, error);\n reject(error);\n return;\n }\n log.info(`[WebpiecesExpressRouter] Listening on http://localhost:${port}`);\n resolve(server);\n });\n },\n );\n }\n\n /**\n * Wrap one ApiClient's impl in an express handler (RequestContext.run, header read, manual\n * JSON body parse, error→ProtocolError) and register it on the app for its method + path.\n */\n private mountApiClient(app: Express, apiClient: ApiClient): void {\n const wrapper = this.middleware.createExpressWrapper(apiClient.impl, apiClient.routeMeta);\n this.registerHandler(\n app,\n apiClient.routeMeta.httpMethod,\n apiClient.routeMeta.path,\n wrapper.execute.bind(wrapper),\n );\n }\n\n private registerHandler(\n app: Express,\n httpMethod: string,\n path: string,\n expressHandler: ExpressRouteHandler,\n ): void {\n switch (httpMethod.toLowerCase()) {\n case 'get':\n app.get(path, expressHandler);\n break;\n case 'post':\n app.post(path, expressHandler);\n break;\n case 'put':\n app.put(path, expressHandler);\n break;\n case 'delete':\n app.delete(path, expressHandler);\n break;\n case 'patch':\n app.patch(path, expressHandler);\n break;\n default:\n log.warn(`[WebpiecesExpressRouter] Unknown HTTP method: ${httpMethod}`);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesExpressRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesExpressRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Express } from 'express';\nimport { ApiFactory, ApiClient, getApiPath, getEndpoints } from '@webpieces/http-routing';\nimport { LogManager } from '@webpieces/core-util';\nimport { WebpiecesMiddleware, ExpressRouteHandler } from './WebpiecesMiddleware';\n\nconst log = LogManager.getLogger('WebpiecesExpressRouter');\n\n/** The value returned by express `app.listen(...)` (a node http.Server). */\ntype HttpServer = ReturnType<Express['listen']>;\n\n/**\n * WebpiecesExpressRouter - the express layer that sits ON TOP of a node-only\n * {@link ApiFactory} (a WebpiecesRouter). It is the ONLY place express lifecycle lives.\n *\n * It never reaches into routing internals: it asks the ApiFactory for `apiClients()` — each\n * an api + routeMeta + composed filter-chain→controller impl — and binds each to an express\n * route (`app.<verb>(path, handler)`) invoked when the matching HTTP request arrives. The\n * RouteBuilder stays hidden inside the ApiFactory.\n *\n * ```typescript\n * const apiFactory = await WebpiecesRouterFactory.create(config, { appBindings });\n * apiFactory.addRoutes(SaveApi, SaveController);\n * const express = new WebpiecesExpressRouter(apiFactory);\n *\n * // legacy / side-by-side: mount onto an existing app; you own listen + your middleware\n * express.bindExpress(existingApp);\n *\n * // non-legacy: add webpieces global middleware + listen for you\n * await express.bindAndStartExpress(express(), 8080);\n * ```\n */\nexport class WebpiecesExpressRouter {\n private readonly middleware = new WebpiecesMiddleware();\n\n constructor(private readonly apiFactory: ApiFactory) {}\n\n /**\n * Mount the webpieces routes (each fully self-contained: own body parse, RequestContext,\n * express-tier + api-tier filter chain, error→JSON) onto the caller's express app.\n *\n * Adds NO global app.use() middleware, so it is safe to attach to a legacy app whose other\n * routes must stay untouched. The caller owns app.listen() and any global middleware.\n */\n bindExpress(app: Express): void {\n let count = 0;\n for (const apiClient of this.apiFactory.apiClients()) {\n count += this.mountApiClient(app, apiClient);\n }\n log.info(`[WebpiecesExpressRouter] Mounted ${count} webpieces route(s) onto express`);\n }\n\n /**\n * Add the webpieces global middleware (HTML error page, localhost CORS, request logging),\n * bind the routes, then app.listen(port). Convenience for a non-legacy webpieces server\n * where webpieces owns the whole express app. Resolves with the http.Server once listening.\n */\n async bindAndStartExpress(app: Express, port: number = 8080): Promise<HttpServer> {\n // Global middleware layers (outermost first) — only for a webpieces-owned app.\n app.use(this.middleware.globalErrorHandler.bind(this.middleware));\n app.use(this.middleware.corsForLocalhost());\n app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n this.bindExpress(app);\n\n return new Promise<HttpServer>(\n (resolve: (server: HttpServer) => void, reject: (err: Error) => void) => {\n const server: HttpServer = app.listen(port, (error?: Error) => {\n if (error) {\n log.error(`[WebpiecesExpressRouter] Failed to start on port ${port}:`, error);\n reject(error);\n return;\n }\n log.info(`[WebpiecesExpressRouter] Listening on http://localhost:${port}`);\n resolve(server);\n });\n },\n );\n }\n\n /**\n * Bind EACH method of one ApiClient. The api's @ApiPath/@Endpoint decorators give the paths;\n * for each we wrap the matching client method (the proxy — RequestContext.run + header read +\n * JSON body parse + error→ProtocolError all live in the wrapper/chain) and register the route.\n * This is one-to-one with a test: an HTTP POST maps straight to `client[method](dto)`.\n *\n * @returns the number of routes mounted for this api.\n */\n private mountApiClient(app: Express, apiClient: ApiClient): number {\n const basePath = getApiPath(apiClient.api) || '';\n const endpoints = getEndpoints(apiClient.api) || {};\n let count = 0;\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const path = basePath + endpointPath;\n const wrapper = this.middleware.createExpressWrapper(apiClient.client[methodName], path);\n // All webpieces routes are POST (the api-tier convention).\n this.registerHandler(app, 'POST', path, wrapper.execute.bind(wrapper));\n count++;\n }\n return count;\n }\n\n private registerHandler(\n app: Express,\n httpMethod: string,\n path: string,\n expressHandler: ExpressRouteHandler,\n ): void {\n switch (httpMethod.toLowerCase()) {\n case 'get':\n app.get(path, expressHandler);\n break;\n case 'post':\n app.post(path, expressHandler);\n break;\n case 'put':\n app.put(path, expressHandler);\n break;\n case 'delete':\n app.delete(path, expressHandler);\n break;\n case 'patch':\n app.patch(path, expressHandler);\n break;\n default:\n log.warn(`[WebpiecesExpressRouter] Unknown HTTP method: ${httpMethod}`);\n }\n }\n}\n"]}
@@ -1,7 +1,4 @@
1
1
  import { Request, Response, NextFunction, RequestHandler } from 'express';
2
- import { MethodMeta } from '@webpieces/http-routing';
3
- import { RouteMetadata } from '@webpieces/core-util';
4
- import { Service, WpResponse } from '@webpieces/http-routing';
5
2
  /**
6
3
  * Express route handler function type. Lives in http-server (the express adapter),
7
4
  * NOT in the node-only http-routing package, so http-routing stays express-free.
@@ -9,9 +6,9 @@ import { Service, WpResponse } from '@webpieces/http-routing';
9
6
  */
10
7
  export type ExpressRouteHandler = (req: Request, res: Response, next: NextFunction) => Promise<void>;
11
8
  export declare class ExpressWrapper {
12
- private service;
13
- private routeMeta;
14
- constructor(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata);
9
+ private clientMethod;
10
+ private path;
11
+ constructor(clientMethod: (requestDto: unknown) => Promise<unknown>, path: string);
15
12
  execute(req: Request, res: Response, next: NextFunction): Promise<void>;
16
13
  executeTryCatch(req: Request, res: Response, next: NextFunction): Promise<void>;
17
14
  executeImpl(req: Request, res: Response, next: NextFunction): Promise<void>;
@@ -97,13 +94,13 @@ export declare class WebpiecesMiddleware {
97
94
  corsForLocalhost(): RequestHandler;
98
95
  /**
99
96
  * Create an ExpressWrapper for a route.
100
- * The wrapper handles the full request/response cycle (symmetric design).
97
+ * The wrapper handles the full request/response cycle (symmetric design): it publishes the
98
+ * HttpRequest + fills the context, then invokes the api client method (the proxy).
101
99
  *
102
- * NEW: Simplified - no longer passes headers (ContextFilter handles it now)
103
- *
104
- * @param service - The service wrapping the filter chain and controller
105
- * @param routeMeta - Route metadata for MethodMeta and DTO type
100
+ * @param clientMethod - The api client's method for this route (dto response); the proxy
101
+ * runs the filter chain + controller.
102
+ * @param path - The route path (used to build the HttpRequest).
106
103
  * @returns ExpressWrapper instance
107
104
  */
108
- createExpressWrapper(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata): ExpressWrapper;
105
+ createExpressWrapper(clientMethod: (requestDto: unknown) => Promise<unknown>, path: string): ExpressWrapper;
109
106
  }
@@ -11,11 +11,13 @@ const core_context_1 = require("@webpieces/core-context");
11
11
  const core_util_3 = require("@webpieces/core-util");
12
12
  const log = core_util_3.LogManager.getLogger('WebpiecesMiddleware');
13
13
  class ExpressWrapper {
14
- service;
15
- routeMeta;
16
- constructor(service, routeMeta) {
17
- this.service = service;
18
- this.routeMeta = routeMeta;
14
+ clientMethod;
15
+ path;
16
+ constructor(
17
+ // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary
18
+ clientMethod, path) {
19
+ this.clientMethod = clientMethod;
20
+ this.path = path;
19
21
  }
20
22
  async execute(req, res, next) {
21
23
  // MOVED: Wrap entire request in RequestContext.run()
@@ -48,16 +50,14 @@ class ExpressWrapper {
48
50
  }
49
51
  // 3. Publish the transport-neutral HttpRequest + fill the context (platform-header
50
52
  // transfer + request id) ABOVE the boundary — the chain reads it, never express req.
51
- core_context_1.RequestContext.setRequest(new core_context_1.HttpRequest(req.method, this.routeMeta.path, requestHeaders));
53
+ core_context_1.RequestContext.setRequest(new core_context_1.HttpRequest(req.method, this.path, requestHeaders));
52
54
  (0, http_routing_1.fillContext)();
53
- const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestDto);
54
- // 4. Invoke the service (filter chain + controller)
55
- const wpResponse = await this.service.invoke(methodMeta);
56
- if (!wpResponse.response) {
57
- throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);
58
- }
59
- // 5. Serialize response DTO to JSON (SYMMETRIC with client's response.json())
60
- const responseJson = JSON.stringify(wpResponse.response);
55
+ // 4. Invoke the api CLIENT method — the SAME proxy tests use. Its filter chain + controller
56
+ // run here; because the request is already published above, the proxy uses it (it does
57
+ // not re-synthesize one). So HTTP and in-process share one invocation path.
58
+ const result = await this.clientMethod(requestDto);
59
+ // 5. Serialize the response DTO to JSON (SYMMETRIC with client's response.json())
60
+ const responseJson = JSON.stringify(result);
61
61
  res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);
62
62
  }
63
63
  /**
@@ -286,16 +286,18 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
286
286
  }
287
287
  /**
288
288
  * Create an ExpressWrapper for a route.
289
- * The wrapper handles the full request/response cycle (symmetric design).
290
- *
291
- * NEW: Simplified - no longer passes headers (ContextFilter handles it now)
289
+ * The wrapper handles the full request/response cycle (symmetric design): it publishes the
290
+ * HttpRequest + fills the context, then invokes the api client method (the proxy).
292
291
  *
293
- * @param service - The service wrapping the filter chain and controller
294
- * @param routeMeta - Route metadata for MethodMeta and DTO type
292
+ * @param clientMethod - The api client's method for this route (dto → response); the proxy
293
+ * runs the filter chain + controller.
294
+ * @param path - The route path (used to build the HttpRequest).
295
295
  * @returns ExpressWrapper instance
296
296
  */
297
- createExpressWrapper(service, routeMeta) {
298
- return new ExpressWrapper(service, routeMeta);
297
+ createExpressWrapper(
298
+ // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary
299
+ clientMethod, path) {
300
+ return new ExpressWrapper(clientMethod, path);
299
301
  }
300
302
  };
301
303
  exports.WebpiecesMiddleware = WebpiecesMiddleware;
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction, RequestHandler } from 'express';\nimport cors from 'cors';\nimport { injectable } from 'inversify';\nimport { provideFrameworkSingleton, MethodMeta, fillContext } from '@webpieces/http-routing';\nimport {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpVendorError,\n HttpUserError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n RouteMetadata,\n} from '@webpieces/core-util';\nimport { Service, WpResponse } from '@webpieces/http-routing';\nimport { toError } from '@webpieces/core-util';\nimport { RequestContext, HttpRequest } from '@webpieces/core-context';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WebpiecesMiddleware');\n\n/**\n * Express route handler function type. Lives in http-server (the express adapter),\n * NOT in the node-only http-routing package, so http-routing stays express-free.\n * Used by WebpiecesExpressRouter to register handlers Express can call.\n */\nexport type ExpressRouteHandler = (\n req: Request,\n res: Response,\n next: NextFunction,\n) => Promise<void>;\n\nexport class ExpressWrapper {\n constructor(\n private service: Service<MethodMeta, WpResponse<unknown>>,\n private routeMeta: RouteMetadata\n ) {\n }\n\n public async execute(req: Request, res: Response, next: NextFunction) {\n // MOVED: Wrap entire request in RequestContext.run()\n // This establishes AsyncLocalStorage context for the request\n await RequestContext.run(async () => {\n await this.executeTryCatch(req, res, next);\n });\n }\n\n public async executeTryCatch(req: Request, res: Response, next: NextFunction): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- ExpressWrapper catches errors to translate to HTTP responses\n try {\n await this.executeImpl(req, res, next);\n } catch (err: unknown) {\n const error = toError(err);\n // 5. Handle errors\n this.handleError(res, error);\n }\n }\n\n public async executeImpl(req: Request, res: Response, next: NextFunction): Promise<void> {\n // 1. Read HTTP headers from Express request\n const requestHeaders = this.readExpressHeaders(req);\n\n // 2. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)\n let requestDto: unknown = {};\n if (['POST', 'PUT', 'PATCH'].includes(req.method)) {\n // Read raw body as text\n const bodyText = await this.readRequestBody(req);\n // Parse JSON\n requestDto = bodyText ? JSON.parse(bodyText) : {};\n }\n\n // 3. Publish the transport-neutral HttpRequest + fill the context (platform-header\n // transfer + request id) ABOVE the boundary — the chain reads it, never express req.\n RequestContext.setRequest(new HttpRequest(req.method, this.routeMeta.path, requestHeaders));\n fillContext();\n const methodMeta = new MethodMeta(this.routeMeta, requestDto);\n\n // 4. Invoke the service (filter chain + controller)\n const wpResponse = await this.service.invoke(methodMeta);\n if (!wpResponse.response) {\n throw new Error(\n `Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`\n );\n }\n\n // 5. Serialize response DTO to JSON (SYMMETRIC with client's response.json())\n const responseJson = JSON.stringify(wpResponse.response);\n res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n\n /**\n * Read HTTP headers from Express request.\n * Returns Map of header name (lowercase) -> array of values.\n *\n * HTTP spec allows multiple values for same header name.\n */\n private readExpressHeaders(req: Request): Map<string, string[]> {\n const headers = new Map<string, string[]>();\n\n // Express stores headers in req.headers as Record<string, string | string[]>\n for (const [name, value] of Object.entries(req.headers)) {\n const lowerName = name.toLowerCase();\n\n if (typeof value === 'string') {\n headers.set(lowerName, [value]);\n } else if (Array.isArray(value)) {\n headers.set(lowerName, value);\n }\n }\n\n return headers;\n }\n\n /**\n * Read raw request body as text.\n * Used to manually parse JSON (instead of express.json() middleware).\n */\n private async readRequestBody(req: Request): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n resolve(body);\n });\n req.on('error', (err) => {\n reject(err);\n });\n });\n }\n\n /**\n * Handle errors - translate to JSON ProtocolError (SYMMETRIC with ClientErrorTranslator).\n * PUBLIC so wrapExpress can call it for symmetric error handling.\n * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.\n *\n * Maps all HttpError types (must match ClientErrorTranslator.translateError()):\n * - HttpUserError → 266 (with errorCode)\n * - HttpBadRequestError → 400 (with field, guiAlertMessage)\n * - HttpUnauthorizedError → 401\n * - HttpForbiddenError → 403\n * - HttpNotFoundError → 404\n * - HttpTimeoutError → 408\n * - HttpInternalServerError → 500\n * - HttpBadGatewayError → 502\n * - HttpGatewayTimeoutError → 504\n * - HttpVendorError → 598 (with waitSeconds)\n */\n public handleError(res: Response, error: unknown): void {\n if (res.headersSent) {\n return;\n }\n\n const protocolError = new ProtocolError();\n\n if (error instanceof HttpError) {\n // Set common fields for all HttpError types\n protocolError.message = error.message;\n protocolError.subType = error.subType;\n protocolError.name = error.name;\n\n // Set type-specific fields (MUST match ClientErrorTranslator)\n if (error instanceof HttpUserError) {\n log.info(`[ExpressWrapper] User Error: ${error.message}`);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n log.info(`[ExpressWrapper] Bad Request: ${error.message}`);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n log.info(`[ExpressWrapper] Not Found: ${error.message}`);\n } else if (error instanceof HttpTimeoutError) {\n log.error(`[ExpressWrapper] Timeout Error: ${error.message}`);\n } else if (error instanceof HttpVendorError) {\n log.error(`[ExpressWrapper] Vendor Error: ${error.message}`);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n log.info(`[ExpressWrapper] Unauthorized: ${error.message}`);\n } else if (error instanceof HttpForbiddenError) {\n log.info(`[ExpressWrapper] Forbidden: ${error.message}`);\n } else if (error instanceof HttpInternalServerError) {\n log.error(`[ExpressWrapper] Internal Server Error: ${error.message}`);\n } else if (error instanceof HttpBadGatewayError) {\n log.error(`[ExpressWrapper] Bad Gateway: ${error.message}`);\n } else if (error instanceof HttpGatewayTimeoutError) {\n log.error(`[ExpressWrapper] Gateway Timeout: ${error.message}`);\n } else {\n log.info(`[ExpressWrapper] Generic HttpError: ${error.message}`);\n }\n\n // Serialize ProtocolError to JSON (SYMMETRIC with client)\n const responseJson = JSON.stringify(protocolError);\n res.status(error.code).setHeader('Content-Type', 'application/json').send(responseJson);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n log.error('[ExpressWrapper] Unexpected error:', err);\n const responseJson = JSON.stringify(protocolError);\n res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n }\n}\n\n/**\n * WebpiecesMiddleware - Express middleware for WebPieces server.\n *\n * This class contains all Express middleware used by WebpiecesServer:\n * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page\n * 2. logNextLayer - Request/response logging\n * 3. jsonTranslator - JSON Content-Type validation and error translation\n *\n * The middleware is injected into WebpiecesServerImpl and registered with Express\n * in the start() method.\n *\n * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via\n * Express's registered route handlers (created by RouteBuilder.createHandler()).\n * jsonTranslator only validates Content-Type and translates errors to JSON.\n *\n * NEW: ExpressWrapper simplified - no longer handles JSON or headers\n * - JSON parsing/serialization moved to JsonFilter\n * - Header transfer moved to ContextFilter (injects PlatformHeadersExtension directly)\n * - ExpressWrapper just creates RouterReqResp and invokes filter chain\n *\n * Extension vs Plugin pattern:\n * - Extensions (DI-level): Contribute capabilities to framework (headers, converters, etc.)\n * - Plugins (App-level): Provide complete features with modules + routes (Hibernate, Jackson, etc.)\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class WebpiecesMiddleware {\n\n /**\n * Global error handler middleware - catches ALL unhandled errors.\n * Returns HTML 500 error page for any errors that escape the filter chain.\n *\n * This is the outermost safety net - JsonTranslator catches JSON API errors,\n * this catches everything else.\n */\n async globalErrorHandler(\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> {\n log.info(`🔴 [Layer 1: GlobalErrorHandler] Request START: ${req.method} ${req.path}`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Global error handler IS the top-level catch-all\n try {\n // await next() catches BOTH:\n // 1. Synchronous throws from next() itself\n // 2. Rejected promises from downstream async middleware\n await next();\n log.info(\n `🔴 [Layer 1: GlobalErrorHandler] Request END (success): ${req.method} ${req.path}`,\n );\n } catch (err: unknown) {\n const error = toError(err);\n log.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonTranslator handles JSON errors)\n res.status(500).send(`\n <!DOCTYPE html>\n <html>\n <head><title>Server Error</title></head>\n <body>\n <h1>You hit a server error</h1>\n <p>An unexpected error occurred while processing your request.</p>\n <pre>${error.message}</pre>\n </body>\n </html>\n `);\n }\n log.info(\n `🔴 [Layer 1: GlobalErrorHandler] Request END (error): ${req.method} ${req.path}`,\n );\n }\n }\n\n /**\n * Logging middleware - logs request/response flow.\n * Demonstrates middleware execution order.\n * IMPORTANT: Must be async and await next() to properly chain with async middleware.\n */\n async logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void> {\n log.info(`🟡 [Layer 2: LogNextLayer] Before next() - ${req.method} ${req.path}`);\n await next();\n log.info(`🟡 [Layer 2: LogNextLayer] After next() - ${req.method} ${req.path}`);\n }\n\n /**\n * CORS middleware for localhost development.\n * Only enables CORS when request origin is localhost:*.\n *\n * Wide open for all headers/methods in dev mode.\n * Non-localhost origins are blocked.\n *\n * @returns Express middleware handler for CORS\n */\n corsForLocalhost(): RequestHandler {\n log.info('[WebpiecesMiddleware] CORS enabled for localhost:* origins');\n\n return cors({\n origin: function (origin, callback) {\n // Allow requests with no origin (same-origin, Postman, curl)\n if (!origin) {\n callback(null, true);\n return;\n }\n\n // Only allow localhost origins\n if (origin.startsWith('http://localhost:') || origin.startsWith('https://localhost:')) {\n callback(null, true);\n } else {\n log.info(`[CORS] Blocked origin: ${origin} (only localhost:* allowed)`);\n callback(new Error(`CORS not allowed for origin: ${origin}`));\n }\n },\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: '*', // Wide open for dev\n exposedHeaders: '*', // Expose all response headers to browser JS\n maxAge: 3600,\n });\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\n *\n * NEW: Simplified - no longer passes headers (ContextFilter handles it now)\n *\n * @param service - The service wrapping the filter chain and controller\n * @param routeMeta - Route metadata for MethodMeta and DTO type\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n service: Service<MethodMeta, WpResponse<unknown>>,\n routeMeta: RouteMetadata,\n ): ExpressWrapper {\n return new ExpressWrapper(service, routeMeta);\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction, RequestHandler } from 'express';\nimport cors from 'cors';\nimport { injectable } from 'inversify';\nimport { provideFrameworkSingleton, fillContext } from '@webpieces/http-routing';\nimport {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpVendorError,\n HttpUserError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n} from '@webpieces/core-util';\nimport { toError } from '@webpieces/core-util';\nimport { RequestContext, HttpRequest } from '@webpieces/core-context';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WebpiecesMiddleware');\n\n/**\n * Express route handler function type. Lives in http-server (the express adapter),\n * NOT in the node-only http-routing package, so http-routing stays express-free.\n * Used by WebpiecesExpressRouter to register handlers Express can call.\n */\nexport type ExpressRouteHandler = (\n req: Request,\n res: Response,\n next: NextFunction,\n) => Promise<void>;\n\nexport class ExpressWrapper {\n constructor(\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n private clientMethod: (requestDto: unknown) => Promise<unknown>,\n private path: string\n ) {\n }\n\n public async execute(req: Request, res: Response, next: NextFunction) {\n // MOVED: Wrap entire request in RequestContext.run()\n // This establishes AsyncLocalStorage context for the request\n await RequestContext.run(async () => {\n await this.executeTryCatch(req, res, next);\n });\n }\n\n public async executeTryCatch(req: Request, res: Response, next: NextFunction): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- ExpressWrapper catches errors to translate to HTTP responses\n try {\n await this.executeImpl(req, res, next);\n } catch (err: unknown) {\n const error = toError(err);\n // 5. Handle errors\n this.handleError(res, error);\n }\n }\n\n public async executeImpl(req: Request, res: Response, next: NextFunction): Promise<void> {\n // 1. Read HTTP headers from Express request\n const requestHeaders = this.readExpressHeaders(req);\n\n // 2. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)\n let requestDto: unknown = {};\n if (['POST', 'PUT', 'PATCH'].includes(req.method)) {\n // Read raw body as text\n const bodyText = await this.readRequestBody(req);\n // Parse JSON\n requestDto = bodyText ? JSON.parse(bodyText) : {};\n }\n\n // 3. Publish the transport-neutral HttpRequest + fill the context (platform-header\n // transfer + request id) ABOVE the boundary — the chain reads it, never express req.\n RequestContext.setRequest(new HttpRequest(req.method, this.path, requestHeaders));\n fillContext();\n\n // 4. Invoke the api CLIENT method — the SAME proxy tests use. Its filter chain + controller\n // run here; because the request is already published above, the proxy uses it (it does\n // not re-synthesize one). So HTTP and in-process share one invocation path.\n const result = await this.clientMethod(requestDto);\n\n // 5. Serialize the response DTO to JSON (SYMMETRIC with client's response.json())\n const responseJson = JSON.stringify(result);\n res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n\n /**\n * Read HTTP headers from Express request.\n * Returns Map of header name (lowercase) -> array of values.\n *\n * HTTP spec allows multiple values for same header name.\n */\n private readExpressHeaders(req: Request): Map<string, string[]> {\n const headers = new Map<string, string[]>();\n\n // Express stores headers in req.headers as Record<string, string | string[]>\n for (const [name, value] of Object.entries(req.headers)) {\n const lowerName = name.toLowerCase();\n\n if (typeof value === 'string') {\n headers.set(lowerName, [value]);\n } else if (Array.isArray(value)) {\n headers.set(lowerName, value);\n }\n }\n\n return headers;\n }\n\n /**\n * Read raw request body as text.\n * Used to manually parse JSON (instead of express.json() middleware).\n */\n private async readRequestBody(req: Request): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n resolve(body);\n });\n req.on('error', (err) => {\n reject(err);\n });\n });\n }\n\n /**\n * Handle errors - translate to JSON ProtocolError (SYMMETRIC with ClientErrorTranslator).\n * PUBLIC so wrapExpress can call it for symmetric error handling.\n * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.\n *\n * Maps all HttpError types (must match ClientErrorTranslator.translateError()):\n * - HttpUserError → 266 (with errorCode)\n * - HttpBadRequestError → 400 (with field, guiAlertMessage)\n * - HttpUnauthorizedError → 401\n * - HttpForbiddenError → 403\n * - HttpNotFoundError → 404\n * - HttpTimeoutError → 408\n * - HttpInternalServerError → 500\n * - HttpBadGatewayError → 502\n * - HttpGatewayTimeoutError → 504\n * - HttpVendorError → 598 (with waitSeconds)\n */\n public handleError(res: Response, error: unknown): void {\n if (res.headersSent) {\n return;\n }\n\n const protocolError = new ProtocolError();\n\n if (error instanceof HttpError) {\n // Set common fields for all HttpError types\n protocolError.message = error.message;\n protocolError.subType = error.subType;\n protocolError.name = error.name;\n\n // Set type-specific fields (MUST match ClientErrorTranslator)\n if (error instanceof HttpUserError) {\n log.info(`[ExpressWrapper] User Error: ${error.message}`);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n log.info(`[ExpressWrapper] Bad Request: ${error.message}`);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n log.info(`[ExpressWrapper] Not Found: ${error.message}`);\n } else if (error instanceof HttpTimeoutError) {\n log.error(`[ExpressWrapper] Timeout Error: ${error.message}`);\n } else if (error instanceof HttpVendorError) {\n log.error(`[ExpressWrapper] Vendor Error: ${error.message}`);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n log.info(`[ExpressWrapper] Unauthorized: ${error.message}`);\n } else if (error instanceof HttpForbiddenError) {\n log.info(`[ExpressWrapper] Forbidden: ${error.message}`);\n } else if (error instanceof HttpInternalServerError) {\n log.error(`[ExpressWrapper] Internal Server Error: ${error.message}`);\n } else if (error instanceof HttpBadGatewayError) {\n log.error(`[ExpressWrapper] Bad Gateway: ${error.message}`);\n } else if (error instanceof HttpGatewayTimeoutError) {\n log.error(`[ExpressWrapper] Gateway Timeout: ${error.message}`);\n } else {\n log.info(`[ExpressWrapper] Generic HttpError: ${error.message}`);\n }\n\n // Serialize ProtocolError to JSON (SYMMETRIC with client)\n const responseJson = JSON.stringify(protocolError);\n res.status(error.code).setHeader('Content-Type', 'application/json').send(responseJson);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n log.error('[ExpressWrapper] Unexpected error:', err);\n const responseJson = JSON.stringify(protocolError);\n res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n }\n}\n\n/**\n * WebpiecesMiddleware - Express middleware for WebPieces server.\n *\n * This class contains all Express middleware used by WebpiecesServer:\n * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page\n * 2. logNextLayer - Request/response logging\n * 3. jsonTranslator - JSON Content-Type validation and error translation\n *\n * The middleware is injected into WebpiecesServerImpl and registered with Express\n * in the start() method.\n *\n * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via\n * Express's registered route handlers (created by RouteBuilder.createHandler()).\n * jsonTranslator only validates Content-Type and translates errors to JSON.\n *\n * NEW: ExpressWrapper simplified - no longer handles JSON or headers\n * - JSON parsing/serialization moved to JsonFilter\n * - Header transfer moved to ContextFilter (injects PlatformHeadersExtension directly)\n * - ExpressWrapper just creates RouterReqResp and invokes filter chain\n *\n * Extension vs Plugin pattern:\n * - Extensions (DI-level): Contribute capabilities to framework (headers, converters, etc.)\n * - Plugins (App-level): Provide complete features with modules + routes (Hibernate, Jackson, etc.)\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class WebpiecesMiddleware {\n\n /**\n * Global error handler middleware - catches ALL unhandled errors.\n * Returns HTML 500 error page for any errors that escape the filter chain.\n *\n * This is the outermost safety net - JsonTranslator catches JSON API errors,\n * this catches everything else.\n */\n async globalErrorHandler(\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> {\n log.info(`🔴 [Layer 1: GlobalErrorHandler] Request START: ${req.method} ${req.path}`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Global error handler IS the top-level catch-all\n try {\n // await next() catches BOTH:\n // 1. Synchronous throws from next() itself\n // 2. Rejected promises from downstream async middleware\n await next();\n log.info(\n `🔴 [Layer 1: GlobalErrorHandler] Request END (success): ${req.method} ${req.path}`,\n );\n } catch (err: unknown) {\n const error = toError(err);\n log.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonTranslator handles JSON errors)\n res.status(500).send(`\n <!DOCTYPE html>\n <html>\n <head><title>Server Error</title></head>\n <body>\n <h1>You hit a server error</h1>\n <p>An unexpected error occurred while processing your request.</p>\n <pre>${error.message}</pre>\n </body>\n </html>\n `);\n }\n log.info(\n `🔴 [Layer 1: GlobalErrorHandler] Request END (error): ${req.method} ${req.path}`,\n );\n }\n }\n\n /**\n * Logging middleware - logs request/response flow.\n * Demonstrates middleware execution order.\n * IMPORTANT: Must be async and await next() to properly chain with async middleware.\n */\n async logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void> {\n log.info(`🟡 [Layer 2: LogNextLayer] Before next() - ${req.method} ${req.path}`);\n await next();\n log.info(`🟡 [Layer 2: LogNextLayer] After next() - ${req.method} ${req.path}`);\n }\n\n /**\n * CORS middleware for localhost development.\n * Only enables CORS when request origin is localhost:*.\n *\n * Wide open for all headers/methods in dev mode.\n * Non-localhost origins are blocked.\n *\n * @returns Express middleware handler for CORS\n */\n corsForLocalhost(): RequestHandler {\n log.info('[WebpiecesMiddleware] CORS enabled for localhost:* origins');\n\n return cors({\n origin: function (origin, callback) {\n // Allow requests with no origin (same-origin, Postman, curl)\n if (!origin) {\n callback(null, true);\n return;\n }\n\n // Only allow localhost origins\n if (origin.startsWith('http://localhost:') || origin.startsWith('https://localhost:')) {\n callback(null, true);\n } else {\n log.info(`[CORS] Blocked origin: ${origin} (only localhost:* allowed)`);\n callback(new Error(`CORS not allowed for origin: ${origin}`));\n }\n },\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: '*', // Wide open for dev\n exposedHeaders: '*', // Expose all response headers to browser JS\n maxAge: 3600,\n });\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design): it publishes the\n * HttpRequest + fills the context, then invokes the api client method (the proxy).\n *\n * @param clientMethod - The api client's method for this route (dto → response); the proxy\n * runs the filter chain + controller.\n * @param path - The route path (used to build the HttpRequest).\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n clientMethod: (requestDto: unknown) => Promise<unknown>,\n path: string,\n ): ExpressWrapper {\n return new ExpressWrapper(clientMethod, path);\n }\n}\n"]}
@@ -71,7 +71,7 @@ let RecordingFilter = class RecordingFilter extends http_routing_2.Filter {
71
71
  for (const entry of logMap.entries()) {
72
72
  ctxSnapshot[entry[0]] = entry[1];
73
73
  }
74
- const apiName = meta.routeMeta.controllerClassName ?? 'UnknownController';
74
+ const apiName = meta.routeMeta.apiName ?? meta.routeMeta.controllerClassName ?? 'UnknownApi';
75
75
  return new core_util_1.RecordedEndpoint(apiName, meta.methodName, [meta.requestDto], ctxSnapshot);
76
76
  }
77
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"RecordingFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/RecordingFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,0DAMiC;AACjC,0DAAyD;AACzD,0DAAsE;AACtE,oDAQ8B;AAE9B,2EAAwE;AAExE;;;;;;;;;;;;;;;;;GAiBG;AAII,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,qBAAuC;IAI5B;IAHpC,aAAa,GAAG,IAAI,yBAAa,EAAE,CAAC;IAE5C,YAC4C,MAAuB;QAE/D,KAAK,EAAE,CAAC;QAFgC,WAAM,GAAN,MAAM,CAAiB;IAGnE,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC/B,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,2CAAoB,EAAE,CAAC;QAC5C,6BAAc,CAAC,SAAS,CAAC,wBAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE1D,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAEtD,4HAA4H;QAC5H,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC/C,cAAc,CAAC,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC;YACnD,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,cAAc,CAAC,eAAe,GAAG,IAAI,yBAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC9E,MAAM,GAAG,CAAC;QACd,CAAC;gBAAS,CAAC;YACP,6BAAc,CAAC,MAAM,CAAC,wBAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClD,QAAQ,CAAC,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACvE,CAAC;IACL,CAAC;IAEO,oBAAoB;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,4EAA4E;QAC5E,OAAO,6BAAc,CAAC,SAAS,CAAC,gCAAoB,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;IAEO,mBAAmB,CAAC,IAAgB;QACxC,8EAA8E;QAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,0BAAc,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,EAAE,IAAI,mCAAoB,EAAE,CAAC,CAAC;QAC1H,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;YACnC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,IAAI,mBAAmB,CAAC;QAC1E,OAAO,IAAI,4BAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC;IAC1F,CAAC;CACJ,CAAA;AAzDY,0CAAe;0BAAf,eAAe;IAH3B,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IACb,sGAAsG;;IAK7F,mBAAA,IAAA,kBAAM,EAAC,qCAAsB,CAAC,CAAA;6CAAiB,8BAAe;GAJ1D,eAAe,CAyD3B","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n provideFrameworkSingleton,\n MethodMeta,\n RequestContextReader,\n WebpiecesConfig,\n WEBPIECES_CONFIG_TOKEN,\n} from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-routing';\nimport {\n HeaderMethods,\n HeaderRegistry,\n RecordedEndpoint,\n RecordedError,\n RecorderKeys,\n WebpiecesCoreHeaders,\n toError,\n} from '@webpieces/core-util';\n\nimport { TestCaseRecorderImpl } from '../recorder/TestCaseRecorderImpl';\n\n/**\n * RecordingFilter - Records a request as a replayable test case (port of Java\n * RecordingFilter).\n *\n * Suggested priority: 1850 (after ContextFilter 2000 so the RECORDING header\n * has been transferred to the context, after AuthFilter 1900 so only real\n * authorized flows are recorded, before LogApiFilter 1800).\n *\n * Activates when WebpiecesConfig.recordingAlwaysOn is set OR the request\n * carries WebpiecesCoreHeaders.RECORDING (x-webpieces-recording). While\n * active, a TestCaseRecorderImpl travels in the RequestContext under\n * RecorderKeys.RECORDER; the http-client proxy and recordable() wrappers add\n * every downstream call. On completion the fixture + generated spec are\n * logged (and written to config.recordingDir when set).\n *\n * Recording NEVER alters the response - failures inside the recorder are\n * caught and logged.\n */\n@provideFrameworkSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\nexport class RecordingFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private headerMethods = new HeaderMethods();\n\n constructor(\n @inject(WEBPIECES_CONFIG_TOKEN) private config: WebpiecesConfig,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n if (!this.isRecordingRequested()) {\n return await nextFilter.invoke(meta);\n }\n\n const recorder = new TestCaseRecorderImpl();\n RequestContext.putHeader(RecorderKeys.RECORDER, recorder);\n\n const serverEndpoint = this.buildServerEndpoint(meta);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await nextFilter.invoke(meta);\n serverEndpoint.successResponse = response.response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n serverEndpoint.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n } finally {\n RequestContext.remove(RecorderKeys.RECORDER.name);\n recorder.spitOutTestCase(serverEndpoint, this.config.recordingDir);\n }\n }\n\n private isRecordingRequested(): boolean {\n if (this.config.recordingAlwaysOn) {\n return true;\n }\n // ContextFilter (priority 2000) already transferred the header into context\n return RequestContext.hasHeader(WebpiecesCoreHeaders.RECORDING);\n }\n\n private buildServerEndpoint(meta: MethodMeta): RecordedEndpoint {\n // Masked snapshot of the magic context (secured values masked, keyed by name)\n const logMap = this.headerMethods.buildSecureMapForLogs(HeaderRegistry.get().getLoggedKeys(), new RequestContextReader());\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of logMap.entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n\n const apiName = meta.routeMeta.controllerClassName ?? 'UnknownController';\n return new RecordedEndpoint(apiName, meta.methodName, [meta.requestDto], ctxSnapshot);\n }\n}\n"]}
1
+ {"version":3,"file":"RecordingFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/RecordingFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n provideFrameworkSingleton,\n MethodMeta,\n RequestContextReader,\n WebpiecesConfig,\n WEBPIECES_CONFIG_TOKEN,\n} from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-routing';\nimport {\n HeaderMethods,\n HeaderRegistry,\n RecordedEndpoint,\n RecordedError,\n RecorderKeys,\n WebpiecesCoreHeaders,\n toError,\n} from '@webpieces/core-util';\n\nimport { TestCaseRecorderImpl } from '../recorder/TestCaseRecorderImpl';\n\n/**\n * RecordingFilter - Records a request as a replayable test case (port of Java\n * RecordingFilter).\n *\n * Suggested priority: 1850 (after ContextFilter 2000 so the RECORDING header\n * has been transferred to the context, after AuthFilter 1900 so only real\n * authorized flows are recorded, before LogApiFilter 1800).\n *\n * Activates when WebpiecesConfig.recordingAlwaysOn is set OR the request\n * carries WebpiecesCoreHeaders.RECORDING (x-webpieces-recording). While\n * active, a TestCaseRecorderImpl travels in the RequestContext under\n * RecorderKeys.RECORDER; the http-client proxy and recordable() wrappers add\n * every downstream call. On completion the fixture + generated spec are\n * logged (and written to config.recordingDir when set).\n *\n * Recording NEVER alters the response - failures inside the recorder are\n * caught and logged.\n */\n@provideFrameworkSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\nexport class RecordingFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private headerMethods = new HeaderMethods();\n\n constructor(\n @inject(WEBPIECES_CONFIG_TOKEN) private config: WebpiecesConfig,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response type flexibility\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n if (!this.isRecordingRequested()) {\n return await nextFilter.invoke(meta);\n }\n\n const recorder = new TestCaseRecorderImpl();\n RequestContext.putHeader(RecorderKeys.RECORDER, recorder);\n\n const serverEndpoint = this.buildServerEndpoint(meta);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await nextFilter.invoke(meta);\n serverEndpoint.successResponse = response.response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n serverEndpoint.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n } finally {\n RequestContext.remove(RecorderKeys.RECORDER.name);\n recorder.spitOutTestCase(serverEndpoint, this.config.recordingDir);\n }\n }\n\n private isRecordingRequested(): boolean {\n if (this.config.recordingAlwaysOn) {\n return true;\n }\n // ContextFilter (priority 2000) already transferred the header into context\n return RequestContext.hasHeader(WebpiecesCoreHeaders.RECORDING);\n }\n\n private buildServerEndpoint(meta: MethodMeta): RecordedEndpoint {\n // Masked snapshot of the magic context (secured values masked, keyed by name)\n const logMap = this.headerMethods.buildSecureMapForLogs(HeaderRegistry.get().getLoggedKeys(), new RequestContextReader());\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of logMap.entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n\n const apiName = meta.routeMeta.apiName ?? meta.routeMeta.controllerClassName ?? 'UnknownApi';\n return new RecordedEndpoint(apiName, meta.methodName, [meta.requestDto], ctxSnapshot);\n }\n}\n"]}