@webpieces/http-server 0.2.17 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-server",
3
- "version": "0.2.17",
3
+ "version": "0.2.23",
4
4
  "description": "WebPieces server with filter chain and dependency injection",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,8 +22,6 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-util": "0.2.17",
26
- "@webpieces/http-routing": "0.2.17",
27
- "@webpieces/http-filters": "0.2.17"
25
+ "@webpieces/http-routing": "0.2.23"
28
26
  }
29
27
  }
@@ -7,6 +7,15 @@ export declare class ExpressWrapper {
7
7
  private routeMeta;
8
8
  constructor(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata);
9
9
  execute(req: Request, res: Response, next: NextFunction): Promise<void>;
10
+ executeTryCatch(req: Request, res: Response, next: NextFunction): Promise<void>;
11
+ executeImpl(req: Request, res: Response, next: NextFunction): Promise<void>;
12
+ /**
13
+ * Read HTTP headers from Express request.
14
+ * Returns Map of header name (lowercase) -> array of values.
15
+ *
16
+ * HTTP spec allows multiple values for same header name.
17
+ */
18
+ private readExpressHeaders;
10
19
  /**
11
20
  * Read raw request body as text.
12
21
  * Used to manually parse JSON (instead of express.json() middleware).
@@ -46,7 +55,14 @@ export declare class ExpressWrapper {
46
55
  * Express's registered route handlers (created by RouteBuilder.createHandler()).
47
56
  * jsonTranslator only validates Content-Type and translates errors to JSON.
48
57
  *
49
- * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
58
+ * NEW: ExpressWrapper simplified - no longer handles JSON or headers
59
+ * - JSON parsing/serialization moved to JsonFilter
60
+ * - Header transfer moved to ContextFilter (injects PlatformHeadersExtension directly)
61
+ * - ExpressWrapper just creates RouterReqResp and invokes filter chain
62
+ *
63
+ * Extension vs Plugin pattern:
64
+ * - Extensions (DI-level): Contribute capabilities to framework (headers, converters, etc.)
65
+ * - Plugins (App-level): Provide complete features with modules + routes (Hibernate, Jackson, etc.)
50
66
  */
51
67
  export declare class WebpiecesMiddleware {
52
68
  /**
@@ -67,6 +83,8 @@ export declare class WebpiecesMiddleware {
67
83
  * Create an ExpressWrapper for a route.
68
84
  * The wrapper handles the full request/response cycle (symmetric design).
69
85
  *
86
+ * NEW: Simplified - no longer passes headers (ContextFilter handles it now)
87
+ *
70
88
  * @param service - The service wrapping the filter chain and controller
71
89
  * @param routeMeta - Route metadata for MethodMeta and DTO type
72
90
  * @returns ExpressWrapper instance
@@ -6,35 +6,71 @@ const inversify_1 = require("inversify");
6
6
  const http_routing_1 = require("@webpieces/http-routing");
7
7
  const http_api_1 = require("@webpieces/http-api");
8
8
  const core_util_1 = require("@webpieces/core-util");
9
+ const core_context_1 = require("@webpieces/core-context");
9
10
  class ExpressWrapper {
10
11
  constructor(service, routeMeta) {
11
12
  this.service = service;
12
13
  this.routeMeta = routeMeta;
13
14
  }
14
15
  async execute(req, res, next) {
16
+ // MOVED: Wrap entire request in RequestContext.run()
17
+ // This establishes AsyncLocalStorage context for the request
18
+ await core_context_1.RequestContext.run(async () => {
19
+ await this.executeTryCatch(req, res, next);
20
+ });
21
+ }
22
+ async executeTryCatch(req, res, next) {
23
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- ExpressWrapper catches errors to translate to HTTP responses
15
24
  try {
16
- // 1. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)
17
- let requestDto = {};
18
- if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
19
- // Read raw body as text
20
- const bodyText = await this.readRequestBody(req);
21
- // Parse JSON
22
- requestDto = bodyText ? JSON.parse(bodyText) : {};
23
- }
24
- // 2. Create MethodMeta with request DTO
25
- const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestDto);
26
- // 3. Invoke the service (filter chain + controller)
27
- const wpResponse = await this.service.invoke(methodMeta);
28
- if (!wpResponse.response)
29
- throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);
30
- // 4. Serialize response DTO to JSON (SYMMETRIC with client's response.json())
31
- const responseJson = JSON.stringify(wpResponse.response);
32
- res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);
25
+ await this.executeImpl(req, res, next);
33
26
  }
34
27
  catch (err) {
28
+ const error = (0, core_util_1.toError)(err);
35
29
  // 5. Handle errors
36
- this.handleError(res, err);
30
+ this.handleError(res, error);
31
+ }
32
+ }
33
+ async executeImpl(req, res, next) {
34
+ // 1. Read HTTP headers from Express request
35
+ const requestHeaders = this.readExpressHeaders(req);
36
+ // 2. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)
37
+ let requestDto = {};
38
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
39
+ // Read raw body as text
40
+ const bodyText = await this.readRequestBody(req);
41
+ // Parse JSON
42
+ requestDto = bodyText ? JSON.parse(bodyText) : {};
43
+ }
44
+ // 3. Create MethodMeta with headers and request DTO
45
+ const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestHeaders, requestDto);
46
+ // 4. Invoke the service (filter chain + controller)
47
+ const wpResponse = await this.service.invoke(methodMeta);
48
+ if (!wpResponse.response) {
49
+ throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);
50
+ }
51
+ // 5. Serialize response DTO to JSON (SYMMETRIC with client's response.json())
52
+ const responseJson = JSON.stringify(wpResponse.response);
53
+ res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);
54
+ }
55
+ /**
56
+ * Read HTTP headers from Express request.
57
+ * Returns Map of header name (lowercase) -> array of values.
58
+ *
59
+ * HTTP spec allows multiple values for same header name.
60
+ */
61
+ readExpressHeaders(req) {
62
+ const headers = new Map();
63
+ // Express stores headers in req.headers as Record<string, string | string[]>
64
+ for (const [name, value] of Object.entries(req.headers)) {
65
+ const lowerName = name.toLowerCase();
66
+ if (typeof value === 'string') {
67
+ headers.set(lowerName, [value]);
68
+ }
69
+ else if (Array.isArray(value)) {
70
+ headers.set(lowerName, value);
71
+ }
37
72
  }
73
+ return headers;
38
74
  }
39
75
  /**
40
76
  * Read raw request body as text.
@@ -149,7 +185,14 @@ exports.ExpressWrapper = ExpressWrapper;
149
185
  * Express's registered route handlers (created by RouteBuilder.createHandler()).
150
186
  * jsonTranslator only validates Content-Type and translates errors to JSON.
151
187
  *
152
- * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
188
+ * NEW: ExpressWrapper simplified - no longer handles JSON or headers
189
+ * - JSON parsing/serialization moved to JsonFilter
190
+ * - Header transfer moved to ContextFilter (injects PlatformHeadersExtension directly)
191
+ * - ExpressWrapper just creates RouterReqResp and invokes filter chain
192
+ *
193
+ * Extension vs Plugin pattern:
194
+ * - Extensions (DI-level): Contribute capabilities to framework (headers, converters, etc.)
195
+ * - Plugins (App-level): Provide complete features with modules + routes (Hibernate, Jackson, etc.)
153
196
  */
154
197
  let WebpiecesMiddleware = class WebpiecesMiddleware {
155
198
  /**
@@ -161,6 +204,7 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
161
204
  */
162
205
  async globalErrorHandler(req, res, next) {
163
206
  console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
207
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Global error handler IS the top-level catch-all
164
208
  try {
165
209
  // await next() catches BOTH:
166
210
  // 1. Synchronous throws from next() itself
@@ -202,6 +246,8 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
202
246
  * Create an ExpressWrapper for a route.
203
247
  * The wrapper handles the full request/response cycle (symmetric design).
204
248
  *
249
+ * NEW: Simplified - no longer passes headers (ContextFilter handles it now)
250
+ *
205
251
  * @param service - The service wrapping the filter chain and controller
206
252
  * @param routeMeta - Route metadata for MethodMeta and DTO type
207
253
  * @returns ExpressWrapper instance
@@ -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 } from 'express';\nimport { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta, ExpressRouteHandler } 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/http-api';\nimport { Service, WpResponse } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\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 try {\n // 1. 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 // 2. Create MethodMeta with request DTO\n const methodMeta = new MethodMeta(this.routeMeta, requestDto);\n\n // 3. Invoke the service (filter chain + controller)\n const wpResponse = await this.service.invoke(methodMeta);\n if(!wpResponse.response)\n throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);\n\n // 4. 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 } catch (err: unknown) {\n // 5. Handle errors\n this.handleError(res, err);\n }\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 console.log('[ExpressWrapper] User Error:', error.message);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n console.log('[ExpressWrapper] Bad Request:', error.message);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n console.log('[ExpressWrapper] Not Found:', error.message);\n } else if (error instanceof HttpTimeoutError) {\n console.error('[ExpressWrapper] Timeout Error:', error.message);\n } else if (error instanceof HttpVendorError) {\n console.error('[ExpressWrapper] Vendor Error:', error.message);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n console.log('[ExpressWrapper] Unauthorized:', error.message);\n } else if (error instanceof HttpForbiddenError) {\n console.log('[ExpressWrapper] Forbidden:', error.message);\n } else if (error instanceof HttpInternalServerError) {\n console.error('[ExpressWrapper] Internal Server Error:', error.message);\n } else if (error instanceof HttpBadGatewayError) {\n console.error('[ExpressWrapper] Bad Gateway:', error.message);\n } else if (error instanceof HttpGatewayTimeoutError) {\n console.error('[ExpressWrapper] Gateway Timeout:', error.message);\n } else {\n console.log('[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 console.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 * 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 * DI Pattern: This class is registered via @provideSingleton() with no dependencies.\n */\n@provideSingleton()\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 console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);\n\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 console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (success):',\n req.method,\n req.path,\n );\n } catch (err: unknown) {\n const error = toError(err);\n console.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 console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (error):',\n req.method,\n 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 console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n await next();\n console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\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 } from 'express';\nimport { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta, ExpressRouteHandler } 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/http-api';\nimport { Service, WpResponse } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\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) {\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: any) {\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) {\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. Create MethodMeta with headers and request DTO\n const methodMeta = new MethodMeta(this.routeMeta, requestHeaders, 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 console.log('[ExpressWrapper] User Error:', error.message);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n console.log('[ExpressWrapper] Bad Request:', error.message);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n console.log('[ExpressWrapper] Not Found:', error.message);\n } else if (error instanceof HttpTimeoutError) {\n console.error('[ExpressWrapper] Timeout Error:', error.message);\n } else if (error instanceof HttpVendorError) {\n console.error('[ExpressWrapper] Vendor Error:', error.message);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n console.log('[ExpressWrapper] Unauthorized:', error.message);\n } else if (error instanceof HttpForbiddenError) {\n console.log('[ExpressWrapper] Forbidden:', error.message);\n } else if (error instanceof HttpInternalServerError) {\n console.error('[ExpressWrapper] Internal Server Error:', error.message);\n } else if (error instanceof HttpBadGatewayError) {\n console.error('[ExpressWrapper] Bad Gateway:', error.message);\n } else if (error instanceof HttpGatewayTimeoutError) {\n console.error('[ExpressWrapper] Gateway Timeout:', error.message);\n } else {\n console.log('[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 console.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@provideSingleton()\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 console.log('🔴 [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 console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (success):',\n req.method,\n req.path,\n );\n } catch (err: any) {\n const error = toError(err);\n console.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 console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (error):',\n req.method,\n 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 console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n await next();\n console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\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"]}
@@ -139,4 +139,5 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
139
139
  * ```
140
140
  */
141
141
  createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
142
+ private runMethod;
142
143
  }
@@ -7,6 +7,7 @@ const inversify_1 = require("inversify");
7
7
  const binding_decorators_1 = require("@inversifyjs/binding-decorators");
8
8
  const http_routing_1 = require("@webpieces/http-routing");
9
9
  const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
10
+ const core_context_1 = require("@webpieces/core-context");
10
11
  /**
11
12
  * WebpiecesServerImpl - Internal server implementation.
12
13
  *
@@ -272,14 +273,29 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
272
273
  // Create invoker service ONCE (sets up filter chain once, not on every call!)
273
274
  const service = this.routeBuilder.createRouteInvoker(httpMethod, path);
274
275
  // Proxy method creates MethodMeta and calls the pre-configured service
276
+ // IMPORTANT: Tests MUST wrap calls in RequestContext.run() themselves
277
+ // This forces explicit context setup in tests, matching production behavior
275
278
  proxy[methodName] = async (requestDto) => {
276
- const meta = new http_routing_1.MethodMeta(routeMeta, requestDto);
277
- const responseWrapper = await service.invoke(meta);
278
- return responseWrapper.response;
279
+ // Verify we're inside an active RequestContext
280
+ // This helps test authors know they need to wrap their test in RequestContext.run()
281
+ if (!core_context_1.RequestContext.isActive()) {
282
+ //Many devs may not activate headers
283
+ return core_context_1.RequestContext.run(async () => {
284
+ return await this.runMethod(routeMeta, requestDto, service);
285
+ });
286
+ }
287
+ return await this.runMethod(routeMeta, requestDto, service);
279
288
  };
280
289
  }
281
290
  return proxy;
282
291
  }
292
+ async runMethod(routeMeta, requestDto, service) {
293
+ // Create MethodMeta without headers (test mode - no HTTP involved)
294
+ // requestHeaders is optional, so we can omit it
295
+ const meta = new http_routing_1.MethodMeta(routeMeta, undefined, requestDto);
296
+ const responseWrapper = await service.invoke(meta);
297
+ return responseWrapper.response;
298
+ }
283
299
  };
284
300
  exports.WebpiecesServerImpl = WebpiecesServerImpl;
285
301
  exports.WebpiecesServerImpl = WebpiecesServerImpl = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesServerImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServerImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import express, {Express, NextFunction, Request, Response} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n ExpressRouteHandler,\n getRoutes,\n MethodMeta,\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\n\n/**\n * WebpiecesServerImpl - Internal server implementation.\n *\n * This class implements the WebpiecesServer interface and contains\n * all the actual server logic. It is created by WebpiecesFactory.create().\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()\n * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.\n */\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\n private meta!: WebAppMeta;\n private webpiecesContainer!: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer!: Container;\n\n private initialized = false;\n private app?: Express;\n private server?: ReturnType<Express['listen']>;\n private port: number = 8200;\n\n constructor(\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called by WebpiecesFactory.create() after resolving this class from DI.\n * This method is internal and not exposed on the WebpiecesServer interface.\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n /**\n * Initialize the server asynchronously.\n * Use this when overrides module contains async operations (e.g., rebind() in new Inversify).\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n meta: WebAppMeta,\n overrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\n this.meta = meta;\n\n // Create application container as child of framework container\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n\n // Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)\n this.routeBuilder.setContainer(this.appContainer);\n\n // 1. Load DI modules asynchronously\n await this.loadDIModules(overrides);\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n *\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(overrides?: ContainerModule): Promise<void> {\n const modules = this.meta.getDIModules();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n await this.appContainer.load(module);\n }\n\n // Load overrides LAST so they can override existing bindings\n if (overrides) {\n await this.appContainer.load(overrides);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(this.routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n * Returns a Promise that resolves when the server is listening,\n * or rejects if the server fails to start.\n *\n * @param port - The port to listen on (default: 8080)\n * @returns Promise that resolves when server is ready\n */\n async start(port: number = 8200, testMode?: boolean): Promise<void> {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before start().');\n }\n\n this.port = port;\n\n if(testMode) {\n //In testMode, we eliminate express ENTIRELY and use\n //Router, method filters and controllers so that we can test full stack\n return;\n }\n\n // Create Express app\n this.app = express();\n\n // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)\n // Catches all unhandled errors and returns HTML 500 page\n this.app.use(this.middleware.globalErrorHandler.bind(this.middleware));\n\n // Layer 2: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes\n const routeCount = this.registerExpressRoutes();\n\n // Start listening - wrap in Promise\n const promise = new Promise<void>((resolve, reject) => {\n this.server = this.app!.listen(this.port, (error?: Error) => {\n if (error) {\n console.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\n }\n\n /**\n * Register Express routes - the SINGLE loop over routes.\n * For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.\n *\n * @returns Number of routes registered\n */\n private registerExpressRoutes(): number {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n const routes = this.routeBuilder.getRoutes();\n let count = 0;\n\n for (const routeWithMeta of routes) {\n const service = this.routeBuilder.createRouteHandler(routeWithMeta);\n const routeMeta = routeWithMeta.definition.routeMeta;\n\n // Create ExpressWrapper directly (handles full request/response cycle)\n const wrapper = this.middleware.createExpressWrapper(service, routeMeta);\n\n this.registerHandler(\n routeMeta.httpMethod,\n routeMeta.path,\n wrapper.execute.bind(wrapper),\n );\n count++;\n }\n\n return count;\n }\n\n registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler) {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n switch (httpMethod.toLowerCase()) {\n case 'get':\n this.app.get(path, expressHandler);\n break;\n case 'post':\n this.app.post(path, expressHandler);\n break;\n case 'put':\n this.app.put(path, expressHandler);\n break;\n case 'delete':\n this.app.delete(path, expressHandler);\n break;\n case 'patch':\n this.app.patch(path, expressHandler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${httpMethod}`);\n }\n }\n\n /**\n * Stop the HTTP server.\n * Returns a Promise that resolves when the server is stopped,\n * or rejects if there's an error stopping the server.\n *\n * @returns Promise that resolves when server is stopped\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err?: Error) => {\n if (err) {\n console.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n console.log('[WebpiecesServer] Server stopped');\n resolve();\n });\n });\n }\n\n /**\n * Get the application DI container.\n *\n * Useful for testing to verify state or access services directly.\n *\n * @returns The application Container\n */\n getContainer(): Container {\n return this.appContainer;\n }\n\n /**\n * Create an API client proxy for testing.\n *\n * This creates a client that routes calls through the full filter chain\n * and controller, but WITHOUT any HTTP overhead. Perfect for testing!\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 *\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 = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n */\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before createApiClient().');\n }\n\n // Get routes from the API prototype using decorators (loops over API methods, NOT all routes)\n const apiMethods = getRoutes(apiPrototype);\n\n // Create proxy object\n const proxy: Record<string, unknown> = {};\n\n // Loop over API methods and create proxy functions\n for (const routeMeta of apiMethods) {\n const methodName = routeMeta.methodName;\n const httpMethod = routeMeta.httpMethod.toUpperCase();\n const path = routeMeta.path;\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 proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n const meta = new MethodMeta(routeMeta, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n };\n }\n\n return proxy as T;\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesServerImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServerImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import express, {Express, NextFunction, Request, Response} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n ExpressRouteHandler,\n getRoutes,\n MethodMeta,\n provideSingleton,\n RouteBuilderImpl, RouteMetadata,\n WebAppMeta,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {RequestContext} from '@webpieces/core-context';\nimport {Service, WpResponse} from \"@webpieces/http-filters\";\n\n/**\n * WebpiecesServerImpl - Internal server implementation.\n *\n * This class implements the WebpiecesServer interface and contains\n * all the actual server logic. It is created by WebpiecesFactory.create().\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()\n * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.\n */\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\n private meta!: WebAppMeta;\n private webpiecesContainer!: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer!: Container;\n\n private initialized = false;\n private app?: Express;\n private server?: ReturnType<Express['listen']>;\n private port: number = 8200;\n\n constructor(\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called by WebpiecesFactory.create() after resolving this class from DI.\n * This method is internal and not exposed on the WebpiecesServer interface.\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n /**\n * Initialize the server asynchronously.\n * Use this when overrides module contains async operations (e.g., rebind() in new Inversify).\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n meta: WebAppMeta,\n overrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\n this.meta = meta;\n\n // Create application container as child of framework container\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n\n // Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)\n this.routeBuilder.setContainer(this.appContainer);\n\n // 1. Load DI modules asynchronously\n await this.loadDIModules(overrides);\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n *\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(overrides?: ContainerModule): Promise<void> {\n const modules = this.meta.getDIModules();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n await this.appContainer.load(module);\n }\n\n // Load overrides LAST so they can override existing bindings\n if (overrides) {\n await this.appContainer.load(overrides);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(this.routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n * Returns a Promise that resolves when the server is listening,\n * or rejects if the server fails to start.\n *\n * @param port - The port to listen on (default: 8080)\n * @returns Promise that resolves when server is ready\n */\n async start(port: number = 8200, testMode?: boolean): Promise<void> {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before start().');\n }\n\n this.port = port;\n\n if(testMode) {\n //In testMode, we eliminate express ENTIRELY and use\n //Router, method filters and controllers so that we can test full stack\n return;\n }\n\n // Create Express app\n this.app = express();\n\n // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)\n // Catches all unhandled errors and returns HTML 500 page\n this.app.use(this.middleware.globalErrorHandler.bind(this.middleware));\n\n // Layer 2: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes\n const routeCount = this.registerExpressRoutes();\n\n // Start listening - wrap in Promise\n const promise = new Promise<void>((resolve, reject) => {\n this.server = this.app!.listen(this.port, (error?: Error) => {\n if (error) {\n console.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\n }\n\n /**\n * Register Express routes - the SINGLE loop over routes.\n * For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.\n *\n * @returns Number of routes registered\n */\n private registerExpressRoutes(): number {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n const routes = this.routeBuilder.getRoutes();\n let count = 0;\n\n for (const routeWithMeta of routes) {\n const service = this.routeBuilder.createRouteHandler(routeWithMeta);\n const routeMeta = routeWithMeta.definition.routeMeta;\n\n // Create ExpressWrapper directly (handles full request/response cycle)\n const wrapper = this.middleware.createExpressWrapper(service, routeMeta);\n\n this.registerHandler(\n routeMeta.httpMethod,\n routeMeta.path,\n wrapper.execute.bind(wrapper),\n );\n count++;\n }\n\n return count;\n }\n\n registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler) {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n switch (httpMethod.toLowerCase()) {\n case 'get':\n this.app.get(path, expressHandler);\n break;\n case 'post':\n this.app.post(path, expressHandler);\n break;\n case 'put':\n this.app.put(path, expressHandler);\n break;\n case 'delete':\n this.app.delete(path, expressHandler);\n break;\n case 'patch':\n this.app.patch(path, expressHandler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${httpMethod}`);\n }\n }\n\n /**\n * Stop the HTTP server.\n * Returns a Promise that resolves when the server is stopped,\n * or rejects if there's an error stopping the server.\n *\n * @returns Promise that resolves when server is stopped\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err?: Error) => {\n if (err) {\n console.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n console.log('[WebpiecesServer] Server stopped');\n resolve();\n });\n });\n }\n\n /**\n * Get the application DI container.\n *\n * Useful for testing to verify state or access services directly.\n *\n * @returns The application Container\n */\n getContainer(): Container {\n return this.appContainer;\n }\n\n /**\n * Create an API client proxy for testing.\n *\n * This creates a client that routes calls through the full filter chain\n * and controller, but WITHOUT any HTTP overhead. Perfect for testing!\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 *\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 = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n */\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before createApiClient().');\n }\n\n // Get routes from the API prototype using decorators (loops over API methods, NOT all routes)\n const apiMethods = getRoutes(apiPrototype);\n\n // Create proxy object\n const proxy: Record<string, unknown> = {};\n\n // Loop over API methods and create proxy functions\n for (const routeMeta of apiMethods) {\n const methodName = routeMeta.methodName;\n const httpMethod = routeMeta.httpMethod.toUpperCase();\n const path = routeMeta.path;\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 // IMPORTANT: Tests MUST wrap calls in RequestContext.run() themselves\n // This forces explicit context setup in tests, matching production behavior\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Verify we're inside an active RequestContext\n // This helps test authors know they need to wrap their test in RequestContext.run()\n if (!RequestContext.isActive()) {\n //Many devs may not activate headers\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 private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>) {\n // Create MethodMeta without headers (test mode - no HTTP involved)\n // requestHeaders is optional, so we can omit it\n const meta = new MethodMeta(routeMeta, undefined, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n }\n}\n"]}
@@ -1,12 +1,42 @@
1
1
  import { MethodMeta } from '@webpieces/http-routing';
2
2
  import { Filter, WpResponse, Service } from '@webpieces/http-filters';
3
+ import { PlatformHeadersExtension, HeaderMethods } from '@webpieces/http-api';
3
4
  /**
4
- * ContextFilter - Sets up AsyncLocalStorage context for each request.
5
- * Priority: 140 (executes first)
5
+ * ContextFilter - Transfers platform headers and stores request metadata in RequestContext.
6
+ * Priority: 2000 (executes first in filter chain)
6
7
  *
7
- * This filter ensures that all subsequent filters and the controller
8
- * execute within a context that can store request-scoped data.
8
+ * NEW: Now handles header transfer from RouterRequest to RequestContext
9
+ * - Injects PlatformHeadersExtension instances via @multiInject (safe because filter created after modules load)
10
+ * - Reads headers from RouterRequest (Express-independent)
11
+ * - Transfers only headers marked with isWantTransferred=true
12
+ * - Generates REQUEST_ID if not present
13
+ *
14
+ * RequestContext lifecycle:
15
+ * 1. ExpressWrapper.execute() calls RequestContext.run() (establishes context)
16
+ * 2. ExpressWrapper creates RouterReqResp and MethodMeta
17
+ * 3. Filter chain executes, starting with ContextFilter
18
+ * 4. ContextFilter transfers headers from RouterRequest to RequestContext
19
+ * 5. ContextFilter stores metadata (METHOD_META, REQUEST_PATH, HTTP_METHOD)
20
+ * 6. Downstream filters and controller can access headers + metadata
21
+ * 7. Context auto-clears when RequestContext.run() completes
9
22
  */
10
23
  export declare class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {
24
+ private headerMethods;
25
+ constructor(extensions: PlatformHeadersExtension[] | undefined, headerMethods: HeaderMethods);
11
26
  filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
27
+ /**
28
+ * Transfer platform headers from MethodMeta.requestHeaders to RequestContext.
29
+ * Uses HeaderMethods.findTransferHeaders() to filter by isWantTransferred=true.
30
+ */
31
+ private transferHeaders;
32
+ /**
33
+ * Ensure REQUEST_ID is set in RequestContext.
34
+ * Generates one if not present.
35
+ */
36
+ private ensureRequestId;
37
+ /**
38
+ * Generate a unique request ID.
39
+ * Format: req-{timestamp}-{random}
40
+ */
41
+ private generateRequestId;
12
42
  }
@@ -6,29 +6,99 @@ const inversify_1 = require("inversify");
6
6
  const http_routing_1 = require("@webpieces/http-routing");
7
7
  const core_context_1 = require("@webpieces/core-context");
8
8
  const http_filters_1 = require("@webpieces/http-filters");
9
+ const http_api_1 = require("@webpieces/http-api");
10
+ const WebpiecesCoreHeaders_1 = require("../headers/WebpiecesCoreHeaders");
9
11
  /**
10
- * ContextFilter - Sets up AsyncLocalStorage context for each request.
11
- * Priority: 140 (executes first)
12
+ * ContextFilter - Transfers platform headers and stores request metadata in RequestContext.
13
+ * Priority: 2000 (executes first in filter chain)
12
14
  *
13
- * This filter ensures that all subsequent filters and the controller
14
- * execute within a context that can store request-scoped data.
15
+ * NEW: Now handles header transfer from RouterRequest to RequestContext
16
+ * - Injects PlatformHeadersExtension instances via @multiInject (safe because filter created after modules load)
17
+ * - Reads headers from RouterRequest (Express-independent)
18
+ * - Transfers only headers marked with isWantTransferred=true
19
+ * - Generates REQUEST_ID if not present
20
+ *
21
+ * RequestContext lifecycle:
22
+ * 1. ExpressWrapper.execute() calls RequestContext.run() (establishes context)
23
+ * 2. ExpressWrapper creates RouterReqResp and MethodMeta
24
+ * 3. Filter chain executes, starting with ContextFilter
25
+ * 4. ContextFilter transfers headers from RouterRequest to RequestContext
26
+ * 5. ContextFilter stores metadata (METHOD_META, REQUEST_PATH, HTTP_METHOD)
27
+ * 6. Downstream filters and controller can access headers + metadata
28
+ * 7. Context auto-clears when RequestContext.run() completes
15
29
  */
16
30
  let ContextFilter = class ContextFilter extends http_filters_1.Filter {
31
+ constructor(extensions = [], headerMethods) {
32
+ super();
33
+ // Flatten all headers from all extensions
34
+ const allHeaders = [];
35
+ for (const extension of extensions) {
36
+ allHeaders.push(...extension.getHeaders());
37
+ }
38
+ // Create HeaderMethods helper with flattened headers
39
+ this.headerMethods = headerMethods.findTransferHeaders(allHeaders);
40
+ console.log(`[ContextFilter] Collected ${allHeaders.length} platform headers from ${extensions.length} extensions`);
41
+ }
17
42
  async filter(meta, nextFilter) {
18
- // Run the rest of the filter chain within a new context
19
- return core_context_1.RequestContext.run(async () => {
20
- // Store request metadata in context for other filters to access
21
- core_context_1.RequestContext.put('METHOD_META', meta);
22
- core_context_1.RequestContext.put('REQUEST_PATH', meta.path);
23
- core_context_1.RequestContext.put('HTTP_METHOD', meta.httpMethod);
24
- return await nextFilter.invoke(meta);
25
- //RequestContext is auto cleared when done.
26
- });
43
+ // Transfer platform headers from MethodMeta.requestHeaders to RequestContext
44
+ this.transferHeaders(meta);
45
+ // Store request metadata in context for other filters/controllers to access
46
+ core_context_1.RequestContext.put('METHOD_META', meta);
47
+ core_context_1.RequestContext.put('REQUEST_PATH', meta.path);
48
+ core_context_1.RequestContext.put('HTTP_METHOD', meta.httpMethod);
49
+ // Execute next filter/controller
50
+ return await nextFilter.invoke(meta);
51
+ // RequestContext is auto-cleared by ExpressWrapper when request completes
52
+ }
53
+ /**
54
+ * Transfer platform headers from MethodMeta.requestHeaders to RequestContext.
55
+ * Uses HeaderMethods.findTransferHeaders() to filter by isWantTransferred=true.
56
+ */
57
+ transferHeaders(meta) {
58
+ if (!meta.requestHeaders) {
59
+ // No headers in test mode (createApiClient creates context but not headers)
60
+ this.ensureRequestId();
61
+ return;
62
+ }
63
+ // Transfer each header to RequestContext using RequestContext.putHeader()
64
+ for (const header of this.headerMethods) {
65
+ // Get values from requestHeaders (case-insensitive lookup)
66
+ const values = meta.requestHeaders.get(header.headerName.toLowerCase());
67
+ if (values && values.length > 0) {
68
+ // Use RequestContext.putHeader() which calls header.getHeaderName()
69
+ core_context_1.RequestContext.putHeader(header, values[0]);
70
+ }
71
+ }
72
+ // Clear request headers from MethodMeta - MUST FORCE USAGE of RequestContext!!!
73
+ meta.requestHeaders = undefined;
74
+ // Generate REQUEST_ID if not present (first service in chain)
75
+ this.ensureRequestId();
76
+ }
77
+ /**
78
+ * Ensure REQUEST_ID is set in RequestContext.
79
+ * Generates one if not present.
80
+ */
81
+ ensureRequestId() {
82
+ if (!core_context_1.RequestContext.hasHeader(WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.REQUEST_ID)) {
83
+ const requestId = this.generateRequestId();
84
+ core_context_1.RequestContext.putHeader(WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.REQUEST_ID, requestId);
85
+ }
86
+ }
87
+ /**
88
+ * Generate a unique request ID.
89
+ * Format: req-{timestamp}-{random}
90
+ */
91
+ generateRequestId() {
92
+ return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
27
93
  }
28
94
  };
29
95
  exports.ContextFilter = ContextFilter;
30
96
  exports.ContextFilter = ContextFilter = tslib_1.__decorate([
31
97
  (0, http_routing_1.provideSingleton)(),
32
- (0, inversify_1.injectable)()
98
+ (0, inversify_1.injectable)(),
99
+ tslib_1.__param(0, (0, inversify_1.multiInject)(http_api_1.HEADER_TYPES.PlatformHeadersExtension)),
100
+ tslib_1.__param(0, (0, inversify_1.optional)()),
101
+ tslib_1.__param(1, (0, inversify_1.inject)(http_api_1.HeaderMethods)),
102
+ tslib_1.__metadata("design:paramtypes", [Array, http_api_1.HeaderMethods])
33
103
  ], ContextFilter);
34
104
  //# sourceMappingURL=ContextFilter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAAuE;AACvE,0DAAyD;AACzD,0DAAsE;AAEtE;;;;;;GAMG;AAGI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAuC;IACtE,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,wDAAwD;QACxD,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YACjC,gEAAgE;YAChE,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACxC,6BAAc,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAEnD,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,2CAA2C;QAC/C,CAAC,CAAC,CAAC;IACP,CAAC;CACJ,CAAA;AAhBY,sCAAa;wBAAb,aAAa;IAFzB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,aAAa,CAgBzB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\n\n/**\n * ContextFilter - Sets up AsyncLocalStorage context for each request.\n * Priority: 140 (executes first)\n *\n * This filter ensures that all subsequent filters and the controller\n * execute within a context that can store request-scoped data.\n */\n@provideSingleton()\n@injectable()\nexport class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n // Run the rest of the filter chain within a new context\n return RequestContext.run(async () => {\n // Store request metadata in context for other filters to access\n RequestContext.put('METHOD_META', meta);\n RequestContext.put('REQUEST_PATH', meta.path);\n RequestContext.put('HTTP_METHOD', meta.httpMethod);\n\n return await nextFilter.invoke(meta);\n //RequestContext is auto cleared when done.\n });\n }\n}\n"]}
1
+ {"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import {inject, injectable, multiInject, optional} from 'inversify';\nimport { provideSingleton, MethodMeta } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport { PlatformHeader, PlatformHeadersExtension, HeaderMethods, HEADER_TYPES } from '@webpieces/http-api';\nimport {WebpiecesCoreHeaders} from \"../headers/WebpiecesCoreHeaders\";\n\n/**\n * ContextFilter - Transfers platform headers and stores request metadata in RequestContext.\n * Priority: 2000 (executes first in filter chain)\n *\n * NEW: Now handles header transfer from RouterRequest to RequestContext\n * - Injects PlatformHeadersExtension instances via @multiInject (safe because filter created after modules load)\n * - Reads headers from RouterRequest (Express-independent)\n * - Transfers only headers marked with isWantTransferred=true\n * - Generates REQUEST_ID if not present\n *\n * RequestContext lifecycle:\n * 1. ExpressWrapper.execute() calls RequestContext.run() (establishes context)\n * 2. ExpressWrapper creates RouterReqResp and MethodMeta\n * 3. Filter chain executes, starting with ContextFilter\n * 4. ContextFilter transfers headers from RouterRequest to RequestContext\n * 5. ContextFilter stores metadata (METHOD_META, REQUEST_PATH, HTTP_METHOD)\n * 6. Downstream filters and controller can access headers + metadata\n * 7. Context auto-clears when RequestContext.run() completes\n */\n@provideSingleton()\n@injectable()\nexport class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private headerMethods: PlatformHeader[];\n\n constructor(\n @multiInject(HEADER_TYPES.PlatformHeadersExtension) @optional()\n extensions: PlatformHeadersExtension[] = [],\n @inject(HeaderMethods) headerMethods: HeaderMethods\n ) {\n super();\n\n // Flatten all headers from all extensions\n const allHeaders: PlatformHeader[] = [];\n for (const extension of extensions) {\n allHeaders.push(...extension.getHeaders());\n }\n\n // Create HeaderMethods helper with flattened headers\n this.headerMethods = headerMethods.findTransferHeaders(allHeaders);\n\n console.log(`[ContextFilter] Collected ${allHeaders.length} platform headers from ${extensions.length} extensions`);\n }\n\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n // Transfer platform headers from MethodMeta.requestHeaders to RequestContext\n this.transferHeaders(meta);\n\n // Store request metadata in context for other filters/controllers to access\n RequestContext.put('METHOD_META', meta);\n RequestContext.put('REQUEST_PATH', meta.path);\n RequestContext.put('HTTP_METHOD', meta.httpMethod);\n\n // Execute next filter/controller\n return await nextFilter.invoke(meta);\n // RequestContext is auto-cleared by ExpressWrapper when request completes\n }\n\n /**\n * Transfer platform headers from MethodMeta.requestHeaders to RequestContext.\n * Uses HeaderMethods.findTransferHeaders() to filter by isWantTransferred=true.\n */\n private transferHeaders(meta: MethodMeta): void {\n if (!meta.requestHeaders) {\n // No headers in test mode (createApiClient creates context but not headers)\n this.ensureRequestId();\n return;\n }\n\n // Transfer each header to RequestContext using RequestContext.putHeader()\n for (const header of this.headerMethods) {\n // Get values from requestHeaders (case-insensitive lookup)\n const values = meta.requestHeaders.get(header.headerName.toLowerCase());\n if (values && values.length > 0) {\n // Use RequestContext.putHeader() which calls header.getHeaderName()\n RequestContext.putHeader(header, values[0]);\n }\n }\n\n // Clear request headers from MethodMeta - MUST FORCE USAGE of RequestContext!!!\n meta.requestHeaders = undefined;\n\n // Generate REQUEST_ID if not present (first service in chain)\n this.ensureRequestId();\n }\n\n /**\n * Ensure REQUEST_ID is set in RequestContext.\n * Generates one if not present.\n */\n private ensureRequestId(): void {\n if (!RequestContext.hasHeader(WebpiecesCoreHeaders.REQUEST_ID)) {\n const requestId = this.generateRequestId();\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID, requestId);\n }\n }\n\n /**\n * Generate a unique request ID.\n * Format: req-{timestamp}-{random}\n */\n private generateRequestId(): string {\n return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n}\n\n"]}
@@ -1,40 +1,26 @@
1
1
  import { MethodMeta } from '@webpieces/http-routing';
2
2
  import { Filter, WpResponse, Service } from '@webpieces/http-filters';
3
+ import { PlatformHeadersExtension, HeaderMethods } from '@webpieces/http-api';
3
4
  /**
4
5
  * LogApiFilter - Structured API logging for all requests/responses.
5
- * Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)
6
+ * Priority: 1800 (after ContextFilter at 2000, before custom filters)
6
7
  *
7
- * Logging patterns:
8
- * - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest
9
- * - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse
10
- * - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)
11
- * - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)
8
+ * Logging patterns (via LogApiCall):
9
+ * - [API-SVR-req] Class.method /url request={...} headers={...}
10
+ * - [API-SVR-resp-SUCCESS] Class.method response={...}
11
+ * - [API-SVR-resp-FAIL] Class.method error=... (server errors: 500, 502, 504)
12
+ * - [API-SVR-resp-OTHER] Class.method errorType=... (user errors: 400, 401, 403, 404, 266)
13
+ *
14
+ * Headers are read from RequestContext (NOT from meta.requestHeaders which is undefined
15
+ * after ContextFilter runs at priority 2000).
12
16
  *
13
17
  * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
14
18
  * because they are expected behavior from the server's perspective.
15
19
  */
16
20
  export declare class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {
21
+ private headerMethods;
22
+ private logApiCall;
23
+ private allHeaders;
24
+ constructor(extensions: PlatformHeadersExtension[] | undefined, headerMethods: HeaderMethods);
17
25
  filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
18
- /**
19
- * Get formatted class.method string for logging.
20
- */
21
- private getClassMethod;
22
- /**
23
- * Log incoming request.
24
- */
25
- private logRequest;
26
- /**
27
- * Log successful response.
28
- */
29
- private logSuccessResponse;
30
- /**
31
- * Log exception based on error type.
32
- * User errors get OTHER (no stack trace), server errors get FAIL.
33
- */
34
- private logException;
35
- /**
36
- * Check if error is a user error (expected behavior from server perspective).
37
- * These are NOT failures - just users making mistakes or validation issues.
38
- */
39
- private isUserError;
40
26
  }
@@ -8,85 +8,52 @@ const http_filters_1 = require("@webpieces/http-filters");
8
8
  const http_api_1 = require("@webpieces/http-api");
9
9
  /**
10
10
  * LogApiFilter - Structured API logging for all requests/responses.
11
- * Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)
11
+ * Priority: 1800 (after ContextFilter at 2000, before custom filters)
12
12
  *
13
- * Logging patterns:
14
- * - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest
15
- * - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse
16
- * - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)
17
- * - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)
13
+ * Logging patterns (via LogApiCall):
14
+ * - [API-SVR-req] Class.method /url request={...} headers={...}
15
+ * - [API-SVR-resp-SUCCESS] Class.method response={...}
16
+ * - [API-SVR-resp-FAIL] Class.method error=... (server errors: 500, 502, 504)
17
+ * - [API-SVR-resp-OTHER] Class.method errorType=... (user errors: 400, 401, 403, 404, 266)
18
+ *
19
+ * Headers are read from RequestContext (NOT from meta.requestHeaders which is undefined
20
+ * after ContextFilter runs at priority 2000).
18
21
  *
19
22
  * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
20
23
  * because they are expected behavior from the server's perspective.
21
24
  */
22
25
  let LogApiFilter = class LogApiFilter extends http_filters_1.Filter {
23
- async filter(meta, nextFilter) {
24
- const classMethod = this.getClassMethod(meta);
25
- const url = meta.path;
26
- // Log request
27
- this.logRequest(classMethod, url, meta.requestDto);
28
- try {
29
- const response = await nextFilter.invoke(meta);
30
- // Log success response
31
- this.logSuccessResponse(classMethod, url, response);
32
- return response;
33
- }
34
- catch (error) {
35
- // Log error and re-throw (jsonTranslator will handle serialization)
36
- this.logException(classMethod, url, error);
37
- throw error;
38
- }
39
- }
40
- /**
41
- * Get formatted class.method string for logging.
42
- */
43
- getClassMethod(meta) {
44
- const className = meta.routeMeta.controllerClassName ?? 'Unknown';
45
- return `${className}.${meta.methodName}`;
46
- }
47
- /**
48
- * Log incoming request.
49
- */
50
- logRequest(classMethod, url, request) {
51
- console.log(`[API-SVR-req] '${classMethod} ${url}' request=${JSON.stringify(request)}`);
52
- }
53
- /**
54
- * Log successful response.
55
- */
56
- logSuccessResponse(classMethod, url, response) {
57
- console.log(`[API-SVR-resp-SUCCESS] '${classMethod} ${url}' response=${JSON.stringify(response.response)}`);
58
- }
59
- /**
60
- * Log exception based on error type.
61
- * User errors get OTHER (no stack trace), server errors get FAIL.
62
- */
63
- logException(classMethod, url, error) {
64
- if (this.isUserError(error)) {
65
- // User errors (400, 401, 403, 404, 266) - no stack trace needed
66
- const errorType = error?.constructor.name ?? 'UnknownError';
67
- console.log(`[API-SVR-resp-OTHER] '${classMethod} ${url}' errorType=${errorType}`);
68
- }
69
- else {
70
- // Server errors (500, 502, etc.) - log full details
71
- const errorMessage = error instanceof Error ? error.message : String(error);
72
- console.error(`[API-SVR-resp-FAIL] '${classMethod} ${url}' error=${errorMessage}`);
26
+ constructor(extensions = [], headerMethods) {
27
+ super();
28
+ this.headerMethods = headerMethods;
29
+ // Flatten all headers from all extensions
30
+ this.allHeaders = [];
31
+ for (const extension of extensions) {
32
+ this.allHeaders.push(...extension.getHeaders());
73
33
  }
34
+ console.log(`[LogApiFilter] Collected ${this.allHeaders.length} platform headers from ${extensions.length} extensions`);
35
+ this.logApiCall = new http_api_1.LogApiCall();
74
36
  }
75
- /**
76
- * Check if error is a user error (expected behavior from server perspective).
77
- * These are NOT failures - just users making mistakes or validation issues.
78
- */
79
- isUserError(error) {
80
- return (error instanceof http_api_1.HttpBadRequestError ||
81
- error instanceof http_api_1.HttpUnauthorizedError ||
82
- error instanceof http_api_1.HttpForbiddenError ||
83
- error instanceof http_api_1.HttpNotFoundError ||
84
- error instanceof http_api_1.HttpUserError);
37
+ async filter(meta, nextFilter) {
38
+ // Build header map from RequestContext (headers are already transferred by ContextFilter)
39
+ const contextReader = new http_routing_1.RequestContextReader();
40
+ const headers = this.headerMethods.buildSecureMapForLogs(this.allHeaders, contextReader);
41
+ // Wrap nextFilter.invoke in a method that returns the response
42
+ const method = async () => {
43
+ const wpResponse = await nextFilter.invoke(meta);
44
+ return wpResponse.response;
45
+ };
46
+ const response = await this.logApiCall.execute("SVR", meta.routeMeta, meta.requestDto, headers, method);
47
+ return new http_filters_1.WpResponse(response);
85
48
  }
86
49
  };
87
50
  exports.LogApiFilter = LogApiFilter;
88
51
  exports.LogApiFilter = LogApiFilter = tslib_1.__decorate([
89
52
  (0, http_routing_1.provideSingleton)(),
90
- (0, inversify_1.injectable)()
53
+ (0, inversify_1.injectable)(),
54
+ tslib_1.__param(0, (0, inversify_1.multiInject)(http_api_1.HEADER_TYPES.PlatformHeadersExtension)),
55
+ tslib_1.__param(0, (0, inversify_1.optional)()),
56
+ tslib_1.__param(1, (0, inversify_1.inject)(http_api_1.HeaderMethods)),
57
+ tslib_1.__metadata("design:paramtypes", [Array, http_api_1.HeaderMethods])
91
58
  ], LogApiFilter);
92
59
  //# sourceMappingURL=LogApiFilter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAAuE;AACvE,0DAAsE;AACtE,kDAM6B;AAE7B;;;;;;;;;;;;GAYG;AAGI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,qBAAuC;IACrE,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QAEtB,cAAc;QACd,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAEnD,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE/C,uBAAuB;YACvB,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAEpD,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACtB,oEAAoE;YACpE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC3C,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,IAAgB;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,IAAI,SAAS,CAAC;QAClE,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED;;OAEG;IACK,UAAU,CAAC,WAAmB,EAAE,GAAW,EAAE,OAAgB;QACjE,OAAO,CAAC,GAAG,CAAC,kBAAkB,WAAW,IAAI,GAAG,aAAa,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,WAAmB,EAAE,GAAW,EAAE,QAA6B;QACtF,OAAO,CAAC,GAAG,CACP,2BAA2B,WAAW,IAAI,GAAG,cAAc,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CACjG,CAAC;IACN,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,WAAmB,EAAE,GAAW,EAAE,KAAc;QACjE,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,gEAAgE;YAChE,MAAM,SAAS,GAAI,KAAe,EAAE,WAAW,CAAC,IAAI,IAAI,cAAc,CAAC;YACvE,OAAO,CAAC,GAAG,CAAC,yBAAyB,WAAW,IAAI,GAAG,eAAe,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;aAAM,CAAC;YACJ,oDAAoD;YACpD,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO,CAAC,KAAK,CAAC,wBAAwB,WAAW,IAAI,GAAG,WAAW,YAAY,EAAE,CAAC,CAAC;QACvF,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,WAAW,CAAC,KAAc;QAC9B,OAAO,CACH,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,gCAAqB;YACtC,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,4BAAiB;YAClC,KAAK,YAAY,wBAAa,CACjC,CAAC;IACN,CAAC;CACJ,CAAA;AA9EY,oCAAY;uBAAZ,YAAY;IAFxB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,YAAY,CA8ExB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta } from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from '@webpieces/http-api';\n\n/**\n * LogApiFilter - Structured API logging for all requests/responses.\n * Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)\n *\n * Logging patterns:\n * - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest\n * - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse\n * - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)\n * - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)\n *\n * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,\n * because they are expected behavior from the server's perspective.\n */\n@provideSingleton()\n@injectable()\nexport class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const classMethod = this.getClassMethod(meta);\n const url = meta.path;\n\n // Log request\n this.logRequest(classMethod, url, meta.requestDto);\n\n try {\n const response = await nextFilter.invoke(meta);\n\n // Log success response\n this.logSuccessResponse(classMethod, url, response);\n\n return response;\n } catch (error: unknown) {\n // Log error and re-throw (jsonTranslator will handle serialization)\n this.logException(classMethod, url, error);\n throw error;\n }\n }\n\n /**\n * Get formatted class.method string for logging.\n */\n private getClassMethod(meta: MethodMeta): string {\n const className = meta.routeMeta.controllerClassName ?? 'Unknown';\n return `${className}.${meta.methodName}`;\n }\n\n /**\n * Log incoming request.\n */\n private logRequest(classMethod: string, url: string, request: unknown): void {\n console.log(`[API-SVR-req] '${classMethod} ${url}' request=${JSON.stringify(request)}`);\n }\n\n /**\n * Log successful response.\n */\n private logSuccessResponse(classMethod: string, url: string, response: WpResponse<unknown>): void {\n console.log(\n `[API-SVR-resp-SUCCESS] '${classMethod} ${url}' response=${JSON.stringify(response.response)}`,\n );\n }\n\n /**\n * Log exception based on error type.\n * User errors get OTHER (no stack trace), server errors get FAIL.\n */\n private logException(classMethod: string, url: string, error: unknown): void {\n if (this.isUserError(error)) {\n // User errors (400, 401, 403, 404, 266) - no stack trace needed\n const errorType = (error as Error)?.constructor.name ?? 'UnknownError';\n console.log(`[API-SVR-resp-OTHER] '${classMethod} ${url}' errorType=${errorType}`);\n } else {\n // Server errors (500, 502, etc.) - log full details\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`[API-SVR-resp-FAIL] '${classMethod} ${url}' error=${errorMessage}`);\n }\n }\n\n /**\n * Check if error is a user error (expected behavior from server perspective).\n * These are NOT failures - just users making mistakes or validation issues.\n */\n private isUserError(error: unknown): boolean {\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError ||\n error instanceof HttpUserError\n );\n }\n}\n"]}
1
+ {"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAoE;AACpE,0DAA2F;AAC3F,0DAAsE;AACtE,kDAM6B;AAE7B;;;;;;;;;;;;;;;GAeG;AAGI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,qBAAuC;IAIrE,YAEI,aAAyC,EAAE,EACZ,aAA4B;QAE3D,KAAK,EAAE,CAAC;QAFuB,kBAAa,GAAb,aAAa,CAAe;QAI3D,0CAA0C;QAC1C,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QACrB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,CAAC,UAAU,CAAC,MAAM,0BAA0B,UAAU,CAAC,MAAM,aAAa,CAAC,CAAC;QAExH,IAAI,CAAC,UAAU,GAAG,IAAI,qBAAU,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,0FAA0F;QAC1F,MAAM,aAAa,GAAG,IAAI,mCAAoB,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAEzF,+DAA+D;QAC/D,MAAM,MAAM,GAAG,KAAK,IAAsB,EAAE;YACxC,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACjD,OAAO,UAAU,CAAC,QAAQ,CAAC;QAC/B,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACxG,OAAO,IAAI,yBAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;CACJ,CAAA;AAvCY,oCAAY;uBAAZ,YAAY;IAFxB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IAMJ,mBAAA,IAAA,uBAAW,EAAC,uBAAY,CAAC,wBAAwB,CAAC,CAAA;IAAE,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAE9D,mBAAA,IAAA,kBAAM,EAAC,wBAAa,CAAC,CAAA;oDAAwB,wBAAa;GAPtD,YAAY,CAuCxB","sourcesContent":["import {inject, injectable, multiInject, optional} from 'inversify';\nimport {provideSingleton, MethodMeta, RequestContextReader} from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport {\n PlatformHeader,\n PlatformHeadersExtension,\n HeaderMethods,\n HEADER_TYPES,\n LogApiCall,\n} from '@webpieces/http-api';\n\n/**\n * LogApiFilter - Structured API logging for all requests/responses.\n * Priority: 1800 (after ContextFilter at 2000, before custom filters)\n *\n * Logging patterns (via LogApiCall):\n * - [API-SVR-req] Class.method /url request={...} headers={...}\n * - [API-SVR-resp-SUCCESS] Class.method response={...}\n * - [API-SVR-resp-FAIL] Class.method error=... (server errors: 500, 502, 504)\n * - [API-SVR-resp-OTHER] Class.method errorType=... (user errors: 400, 401, 403, 404, 266)\n *\n * Headers are read from RequestContext (NOT from meta.requestHeaders which is undefined\n * after ContextFilter runs at priority 2000).\n *\n * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,\n * because they are expected behavior from the server's perspective.\n */\n@provideSingleton()\n@injectable()\nexport class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private logApiCall: LogApiCall;\n private allHeaders: PlatformHeader[];\n\n constructor(\n @multiInject(HEADER_TYPES.PlatformHeadersExtension) @optional()\n extensions: PlatformHeadersExtension[] = [],\n @inject(HeaderMethods) private headerMethods: HeaderMethods\n ) {\n super();\n\n // Flatten all headers from all extensions\n this.allHeaders = [];\n for (const extension of extensions) {\n this.allHeaders.push(...extension.getHeaders());\n }\n\n console.log(`[LogApiFilter] Collected ${this.allHeaders.length} platform headers from ${extensions.length} extensions`);\n\n this.logApiCall = new LogApiCall();\n }\n\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n // Build header map from RequestContext (headers are already transferred by ContextFilter)\n const contextReader = new RequestContextReader();\n const headers = this.headerMethods.buildSecureMapForLogs(this.allHeaders, contextReader);\n\n // Wrap nextFilter.invoke in a method that returns the response\n const method = async (): Promise<unknown> => {\n const wpResponse = await nextFilter.invoke(meta);\n return wpResponse.response;\n };\n\n const response = await this.logApiCall.execute(\"SVR\", meta.routeMeta, meta.requestDto, headers, method);\n return new WpResponse(response);\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { PlatformHeader } from '@webpieces/http-api';
2
+ /**
3
+ * Core framework headers for distributed tracing and request correlation.
4
+ *
5
+ * These are the minimal headers needed by the WebPieces framework for:
6
+ * - Request tracking across services
7
+ * - Distributed tracing
8
+ * - Request correlation
9
+ * - Metrics and monitoring
10
+ *
11
+ * Pattern inspired by Java MicroSvcHeader enum.
12
+ */
13
+ export declare class WebpiecesCoreHeaders {
14
+ /**
15
+ * Unique ID for this request.
16
+ * Generated by the server if not provided.
17
+ * Used for distributed tracing and log correlation.
18
+ */
19
+ static readonly REQUEST_ID: PlatformHeader;
20
+ /**
21
+ * ID of the previous request in the call chain.
22
+ * When service A calls service B, B receives A's REQUEST_ID as PREVIOUS_REQUEST_ID.
23
+ * Used for building distributed trace trees.
24
+ */
25
+ static readonly PREVIOUS_REQUEST_ID: PlatformHeader;
26
+ /**
27
+ * Correlation ID that spans multiple related requests.
28
+ * Typically set by the API gateway or first service in the chain.
29
+ * All services in the call chain use the same CORRELATION_ID.
30
+ */
31
+ static readonly CORRELATION_ID: PlatformHeader;
32
+ /**
33
+ * Get all core headers as an array.
34
+ * Used by WebpiecesModule to bind headers to DI container.
35
+ *
36
+ * @returns Array of all core platform headers
37
+ */
38
+ static getAllHeaders(): PlatformHeader[];
39
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebpiecesCoreHeaders = void 0;
4
+ const http_api_1 = require("@webpieces/http-api");
5
+ /**
6
+ * Core framework headers for distributed tracing and request correlation.
7
+ *
8
+ * These are the minimal headers needed by the WebPieces framework for:
9
+ * - Request tracking across services
10
+ * - Distributed tracing
11
+ * - Request correlation
12
+ * - Metrics and monitoring
13
+ *
14
+ * Pattern inspired by Java MicroSvcHeader enum.
15
+ */
16
+ class WebpiecesCoreHeaders {
17
+ /**
18
+ * Get all core headers as an array.
19
+ * Used by WebpiecesModule to bind headers to DI container.
20
+ *
21
+ * @returns Array of all core platform headers
22
+ */
23
+ static getAllHeaders() {
24
+ return [
25
+ WebpiecesCoreHeaders.REQUEST_ID,
26
+ WebpiecesCoreHeaders.PREVIOUS_REQUEST_ID,
27
+ WebpiecesCoreHeaders.CORRELATION_ID,
28
+ ];
29
+ }
30
+ }
31
+ exports.WebpiecesCoreHeaders = WebpiecesCoreHeaders;
32
+ /**
33
+ * Unique ID for this request.
34
+ * Generated by the server if not provided.
35
+ * Used for distributed tracing and log correlation.
36
+ */
37
+ WebpiecesCoreHeaders.REQUEST_ID = new http_api_1.PlatformHeader('x-request-id', true, // transfer (propagate to downstream services)
38
+ false, // not secured (it's just an ID)
39
+ true // use for metrics dimensions
40
+ );
41
+ /**
42
+ * ID of the previous request in the call chain.
43
+ * When service A calls service B, B receives A's REQUEST_ID as PREVIOUS_REQUEST_ID.
44
+ * Used for building distributed trace trees.
45
+ */
46
+ WebpiecesCoreHeaders.PREVIOUS_REQUEST_ID = new http_api_1.PlatformHeader('x-previous-request-id', true, false, false);
47
+ /**
48
+ * Correlation ID that spans multiple related requests.
49
+ * Typically set by the API gateway or first service in the chain.
50
+ * All services in the call chain use the same CORRELATION_ID.
51
+ */
52
+ WebpiecesCoreHeaders.CORRELATION_ID = new http_api_1.PlatformHeader('x-correlation-id', true, false, true // use for metrics dimensions
53
+ );
54
+ //# sourceMappingURL=WebpiecesCoreHeaders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/headers/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,kDAAqD;AAErD;;;;;;;;;;GAUG;AACH,MAAa,oBAAoB;IAqC7B;;;;;OAKG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO;YACH,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,mBAAmB;YACxC,oBAAoB,CAAC,cAAc;SACtC,CAAC;IACN,CAAC;;AAjDL,oDAkDC;AAjDG;;;;GAIG;AACa,+BAAU,GAAG,IAAI,yBAAc,CAC3C,cAAc,EACd,IAAI,EAAG,8CAA8C;AACrD,KAAK,EAAE,gCAAgC;AACvC,IAAI,CAAG,6BAA6B;CACvC,CAAC;AAEF;;;;GAIG;AACa,wCAAmB,GAAG,IAAI,yBAAc,CACpD,uBAAuB,EACvB,IAAI,EACJ,KAAK,EACL,KAAK,CACR,CAAC;AAEF;;;;GAIG;AACa,mCAAc,GAAG,IAAI,yBAAc,CAC/C,kBAAkB,EAClB,IAAI,EACJ,KAAK,EACL,IAAI,CAAG,6BAA6B;CACvC,CAAC","sourcesContent":["import { PlatformHeader } from '@webpieces/http-api';\n\n/**\n * Core framework headers for distributed tracing and request correlation.\n *\n * These are the minimal headers needed by the WebPieces framework for:\n * - Request tracking across services\n * - Distributed tracing\n * - Request correlation\n * - Metrics and monitoring\n *\n * Pattern inspired by Java MicroSvcHeader enum.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * Unique ID for this request.\n * Generated by the server if not provided.\n * Used for distributed tracing and log correlation.\n */\n static readonly REQUEST_ID = new PlatformHeader(\n 'x-request-id',\n true, // transfer (propagate to downstream services)\n false, // not secured (it's just an ID)\n true // use for metrics dimensions\n );\n\n /**\n * ID of the previous request in the call chain.\n * When service A calls service B, B receives A's REQUEST_ID as PREVIOUS_REQUEST_ID.\n * Used for building distributed trace trees.\n */\n static readonly PREVIOUS_REQUEST_ID = new PlatformHeader(\n 'x-previous-request-id',\n true,\n false,\n false\n );\n\n /**\n * Correlation ID that spans multiple related requests.\n * Typically set by the API gateway or first service in the chain.\n * All services in the call chain use the same CORRELATION_ID.\n */\n static readonly CORRELATION_ID = new PlatformHeader(\n 'x-correlation-id',\n true,\n false,\n true // use for metrics dimensions\n );\n\n /**\n * Get all core headers as an array.\n * Used by WebpiecesModule to bind headers to DI container.\n *\n * @returns Array of all core platform headers\n */\n static getAllHeaders(): PlatformHeader[] {\n return [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.PREVIOUS_REQUEST_ID,\n WebpiecesCoreHeaders.CORRELATION_ID,\n ];\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -3,4 +3,6 @@ export { WebpiecesFactory } from './WebpiecesFactory';
3
3
  export { WebpiecesMiddleware } from './WebpiecesMiddleware';
4
4
  export { ContextFilter } from './filters/ContextFilter';
5
5
  export { LogApiFilter } from './filters/LogApiFilter';
6
+ export { WebpiecesModule } from './modules/WebpiecesModule';
7
+ export { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';
6
8
  export { RouteHandler, ExpressRouteHandler, MethodMeta, RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta, HttpFilter, FilterMatcher, } from '@webpieces/http-routing';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.MethodMeta = exports.RouteHandler = exports.LogApiFilter = exports.ContextFilter = exports.WebpiecesMiddleware = exports.WebpiecesFactory = void 0;
3
+ exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.MethodMeta = exports.RouteHandler = exports.WebpiecesCoreHeaders = exports.WebpiecesModule = exports.LogApiFilter = exports.ContextFilter = exports.WebpiecesMiddleware = exports.WebpiecesFactory = void 0;
4
4
  var WebpiecesFactory_1 = require("./WebpiecesFactory");
5
5
  Object.defineProperty(exports, "WebpiecesFactory", { enumerable: true, get: function () { return WebpiecesFactory_1.WebpiecesFactory; } });
6
6
  var WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
@@ -9,6 +9,11 @@ var ContextFilter_1 = require("./filters/ContextFilter");
9
9
  Object.defineProperty(exports, "ContextFilter", { enumerable: true, get: function () { return ContextFilter_1.ContextFilter; } });
10
10
  var LogApiFilter_1 = require("./filters/LogApiFilter");
11
11
  Object.defineProperty(exports, "LogApiFilter", { enumerable: true, get: function () { return LogApiFilter_1.LogApiFilter; } });
12
+ // Platform Headers
13
+ var WebpiecesModule_1 = require("./modules/WebpiecesModule");
14
+ Object.defineProperty(exports, "WebpiecesModule", { enumerable: true, get: function () { return WebpiecesModule_1.WebpiecesModule; } });
15
+ var WebpiecesCoreHeaders_1 = require("./headers/WebpiecesCoreHeaders");
16
+ Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return WebpiecesCoreHeaders_1.WebpiecesCoreHeaders; } });
12
17
  // Re-export from http-routing for backward compatibility
13
18
  var http_routing_1 = require("@webpieces/http-routing");
14
19
  Object.defineProperty(exports, "RouteHandler", { enumerable: true, get: function () { return http_routing_1.RouteHandler; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uDAAsD;AAA7C,4GAAA,YAAY,OAAA;AAErB,yDAAyD;AACzD,wDASiC;AAR7B,4GAAA,YAAY,OAAA;AAEZ,0GAAA,UAAU,OAAA;AACV,gHAAA,gBAAgB,OAAA;AAChB,oHAAA,oBAAoB,OAAA;AACpB,8GAAA,cAAc,OAAA;AAEd,6GAAA,aAAa,OAAA","sourcesContent":["export { WebpiecesServer } from './WebpiecesServer';\nexport { WebpiecesFactory } from './WebpiecesFactory';\nexport { WebpiecesMiddleware } from './WebpiecesMiddleware';\nexport { ContextFilter } from './filters/ContextFilter';\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// Re-export from http-routing for backward compatibility\nexport {\n RouteHandler,\n ExpressRouteHandler,\n MethodMeta,\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n HttpFilter,\n FilterMatcher,\n} from '@webpieces/http-routing';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uDAAsD;AAA7C,4GAAA,YAAY,OAAA;AAErB,mBAAmB;AACnB,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AACxB,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAE7B,yDAAyD;AACzD,wDASiC;AAR7B,4GAAA,YAAY,OAAA;AAEZ,0GAAA,UAAU,OAAA;AACV,gHAAA,gBAAgB,OAAA;AAChB,oHAAA,oBAAoB,OAAA;AACpB,8GAAA,cAAc,OAAA;AAEd,6GAAA,aAAa,OAAA","sourcesContent":["export { WebpiecesServer } from './WebpiecesServer';\nexport { WebpiecesFactory } from './WebpiecesFactory';\nexport { WebpiecesMiddleware } from './WebpiecesMiddleware';\nexport { ContextFilter } from './filters/ContextFilter';\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// Platform Headers\nexport { WebpiecesModule } from './modules/WebpiecesModule';\nexport { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';\n\n// Re-export from http-routing for backward compatibility\nexport {\n RouteHandler,\n ExpressRouteHandler,\n MethodMeta,\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n HttpFilter,\n FilterMatcher,\n} from '@webpieces/http-routing';\n"]}
@@ -0,0 +1,20 @@
1
+ import { ContainerModule } from 'inversify';
2
+ /**
3
+ * WebpiecesModule - Framework-level DI bindings.
4
+ *
5
+ * This module is loaded by WebpiecesFactory BEFORE application modules.
6
+ * It provides core framework services including platform headers.
7
+ *
8
+ * Platform Headers Pattern (Extension):
9
+ * - Create PlatformHeadersExtension with array of headers
10
+ * - Bind extension to HEADER_TYPES.PlatformHeadersExtension symbol
11
+ * - Multiple modules bind their own extensions
12
+ * - Consumer uses @multiInject to collect all extensions
13
+ * - Pattern inspired by Guice Multibinder
14
+ *
15
+ * Module Loading Order:
16
+ * 1. WebpiecesModule (framework headers) ← YOU ARE HERE
17
+ * 2. CompanyModule (company-wide headers)
18
+ * 3. InversifyModule (app-specific headers)
19
+ */
20
+ export declare const WebpiecesModule: ContainerModule;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebpiecesModule = void 0;
4
+ const inversify_1 = require("inversify");
5
+ const http_api_1 = require("@webpieces/http-api");
6
+ const WebpiecesCoreHeaders_1 = require("../headers/WebpiecesCoreHeaders");
7
+ /**
8
+ * WebpiecesModule - Framework-level DI bindings.
9
+ *
10
+ * This module is loaded by WebpiecesFactory BEFORE application modules.
11
+ * It provides core framework services including platform headers.
12
+ *
13
+ * Platform Headers Pattern (Extension):
14
+ * - Create PlatformHeadersExtension with array of headers
15
+ * - Bind extension to HEADER_TYPES.PlatformHeadersExtension symbol
16
+ * - Multiple modules bind their own extensions
17
+ * - Consumer uses @multiInject to collect all extensions
18
+ * - Pattern inspired by Guice Multibinder
19
+ *
20
+ * Module Loading Order:
21
+ * 1. WebpiecesModule (framework headers) ← YOU ARE HERE
22
+ * 2. CompanyModule (company-wide headers)
23
+ * 3. InversifyModule (app-specific headers)
24
+ */
25
+ exports.WebpiecesModule = new inversify_1.ContainerModule((options) => {
26
+ const { bind } = options;
27
+ // Bind HeaderMethods as singleton (stateless utility, can be shared)
28
+ bind(http_api_1.HeaderMethods).toSelf().inSingletonScope();
29
+ // Create extension with core headers
30
+ const coreExtension = new http_api_1.PlatformHeadersExtension(WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.getAllHeaders());
31
+ // Bind extension for multiInject collection
32
+ // WebpiecesMiddleware will collect all PlatformHeadersExtension bindings via @multiInject
33
+ bind(http_api_1.HEADER_TYPES.PlatformHeadersExtension).toConstantValue(coreExtension);
34
+ console.log(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);
35
+ });
36
+ //# sourceMappingURL=WebpiecesModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebpiecesModule.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/modules/WebpiecesModule.ts"],"names":[],"mappings":";;;AAAA,yCAA4C;AAC5C,kDAA4F;AAC5F,0EAAuE;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,eAAe,GAAG,IAAI,2BAAe,CAAC,CAAC,OAAO,EAAE,EAAE;IAC3D,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEzB,qEAAqE;IACrE,IAAI,CAAgB,wBAAa,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAE/D,qCAAqC;IACrC,MAAM,aAAa,GAAG,IAAI,mCAAwB,CAAC,2CAAoB,CAAC,aAAa,EAAE,CAAC,CAAC;IAEzF,4CAA4C;IAC5C,0FAA0F;IAC1F,IAAI,CAA2B,uBAAY,CAAC,wBAAwB,CAAC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAErG,OAAO,CAAC,GAAG,CAAC,qEAAqE,aAAa,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;AAC7H,CAAC,CAAC,CAAC","sourcesContent":["import { ContainerModule } from 'inversify';\nimport { HEADER_TYPES, PlatformHeadersExtension, HeaderMethods } from '@webpieces/http-api';\nimport { WebpiecesCoreHeaders } from '../headers/WebpiecesCoreHeaders';\n\n/**\n * WebpiecesModule - Framework-level DI bindings.\n *\n * This module is loaded by WebpiecesFactory BEFORE application modules.\n * It provides core framework services including platform headers.\n *\n * Platform Headers Pattern (Extension):\n * - Create PlatformHeadersExtension with array of headers\n * - Bind extension to HEADER_TYPES.PlatformHeadersExtension symbol\n * - Multiple modules bind their own extensions\n * - Consumer uses @multiInject to collect all extensions\n * - Pattern inspired by Guice Multibinder\n *\n * Module Loading Order:\n * 1. WebpiecesModule (framework headers) ← YOU ARE HERE\n * 2. CompanyModule (company-wide headers)\n * 3. InversifyModule (app-specific headers)\n */\nexport const WebpiecesModule = new ContainerModule((options) => {\n const { bind } = options;\n\n // Bind HeaderMethods as singleton (stateless utility, can be shared)\n bind<HeaderMethods>(HeaderMethods).toSelf().inSingletonScope();\n\n // Create extension with core headers\n const coreExtension = new PlatformHeadersExtension(WebpiecesCoreHeaders.getAllHeaders());\n\n // Bind extension for multiInject collection\n // WebpiecesMiddleware will collect all PlatformHeadersExtension bindings via @multiInject\n bind<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension).toConstantValue(coreExtension);\n\n console.log(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);\n});\n"]}