@webpieces/http-server 0.3.247 → 0.3.248
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 +3 -2
- package/src/WebpiecesMiddleware.js +22 -20
- package/src/WebpiecesMiddleware.js.map +1 -1
- package/src/WebpiecesRouteCreator.d.ts +0 -38
- package/src/WebpiecesRouteCreator.js +3 -1
- package/src/WebpiecesRouteCreator.js.map +1 -1
- package/src/WebpiecesServerImpl.d.ts +1 -25
- package/src/WebpiecesServerImpl.js +8 -6
- package/src/WebpiecesServerImpl.js.map +1 -1
- package/src/filters/ContextFilter.d.ts +0 -19
- package/src/filters/ContextFilter.js +3 -1
- package/src/filters/ContextFilter.js.map +1 -1
- package/src/filters/LogApiFilter.d.ts +0 -16
- package/src/filters/LogApiFilter.js +3 -1
- package/src/filters/LogApiFilter.js.map +1 -1
- package/src/modules/WebpiecesModule.js +3 -1
- package/src/modules/WebpiecesModule.js.map +1 -1
- package/src/recorder/TestCaseRecorderImpl.d.ts +0 -16
- package/src/recorder/TestCaseRecorderImpl.js +5 -3
- package/src/recorder/TestCaseRecorderImpl.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.248",
|
|
4
4
|
"description": "WebPieces server with filter chain and dependency injection",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/http-routing": "0.3.
|
|
25
|
+
"@webpieces/http-routing": "0.3.248",
|
|
26
|
+
"@webpieces/wp-logging": "0.3.248",
|
|
26
27
|
"cors": "2.8.5"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
@@ -8,6 +8,8 @@ const http_routing_1 = require("@webpieces/http-routing");
|
|
|
8
8
|
const http_api_1 = require("@webpieces/http-api");
|
|
9
9
|
const core_util_1 = require("@webpieces/core-util");
|
|
10
10
|
const core_context_1 = require("@webpieces/core-context");
|
|
11
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
12
|
+
const log = wp_logging_1.LogManager.getLogger('WebpiecesMiddleware');
|
|
11
13
|
class ExpressWrapper {
|
|
12
14
|
service;
|
|
13
15
|
routeMeta;
|
|
@@ -122,41 +124,41 @@ class ExpressWrapper {
|
|
|
122
124
|
protocolError.name = error.name;
|
|
123
125
|
// Set type-specific fields (MUST match ClientErrorTranslator)
|
|
124
126
|
if (error instanceof http_api_1.HttpUserError) {
|
|
125
|
-
|
|
127
|
+
log.info('[ExpressWrapper] User Error:', error.message);
|
|
126
128
|
protocolError.errorCode = error.errorCode;
|
|
127
129
|
}
|
|
128
130
|
else if (error instanceof http_api_1.HttpBadRequestError) {
|
|
129
|
-
|
|
131
|
+
log.info('[ExpressWrapper] Bad Request:', error.message);
|
|
130
132
|
protocolError.field = error.field;
|
|
131
133
|
protocolError.guiAlertMessage = error.guiMessage;
|
|
132
134
|
}
|
|
133
135
|
else if (error instanceof http_api_1.HttpNotFoundError) {
|
|
134
|
-
|
|
136
|
+
log.info('[ExpressWrapper] Not Found:', error.message);
|
|
135
137
|
}
|
|
136
138
|
else if (error instanceof http_api_1.HttpTimeoutError) {
|
|
137
|
-
|
|
139
|
+
log.error('[ExpressWrapper] Timeout Error:', error.message);
|
|
138
140
|
}
|
|
139
141
|
else if (error instanceof http_api_1.HttpVendorError) {
|
|
140
|
-
|
|
142
|
+
log.error('[ExpressWrapper] Vendor Error:', error.message);
|
|
141
143
|
protocolError.waitSeconds = error.waitSeconds;
|
|
142
144
|
}
|
|
143
145
|
else if (error instanceof http_api_1.HttpUnauthorizedError) {
|
|
144
|
-
|
|
146
|
+
log.info('[ExpressWrapper] Unauthorized:', error.message);
|
|
145
147
|
}
|
|
146
148
|
else if (error instanceof http_api_1.HttpForbiddenError) {
|
|
147
|
-
|
|
149
|
+
log.info('[ExpressWrapper] Forbidden:', error.message);
|
|
148
150
|
}
|
|
149
151
|
else if (error instanceof http_api_1.HttpInternalServerError) {
|
|
150
|
-
|
|
152
|
+
log.error('[ExpressWrapper] Internal Server Error:', error.message);
|
|
151
153
|
}
|
|
152
154
|
else if (error instanceof http_api_1.HttpBadGatewayError) {
|
|
153
|
-
|
|
155
|
+
log.error('[ExpressWrapper] Bad Gateway:', error.message);
|
|
154
156
|
}
|
|
155
157
|
else if (error instanceof http_api_1.HttpGatewayTimeoutError) {
|
|
156
|
-
|
|
158
|
+
log.error('[ExpressWrapper] Gateway Timeout:', error.message);
|
|
157
159
|
}
|
|
158
160
|
else {
|
|
159
|
-
|
|
161
|
+
log.info('[ExpressWrapper] Generic HttpError:', error.message);
|
|
160
162
|
}
|
|
161
163
|
// Serialize ProtocolError to JSON (SYMMETRIC with client)
|
|
162
164
|
const responseJson = JSON.stringify(protocolError);
|
|
@@ -166,7 +168,7 @@ class ExpressWrapper {
|
|
|
166
168
|
// Unknown error - 500
|
|
167
169
|
const err = (0, core_util_1.toError)(error);
|
|
168
170
|
protocolError.message = 'Internal Server Error';
|
|
169
|
-
|
|
171
|
+
log.error('[ExpressWrapper] Unexpected error:', err);
|
|
170
172
|
const responseJson = JSON.stringify(protocolError);
|
|
171
173
|
res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);
|
|
172
174
|
}
|
|
@@ -206,18 +208,18 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
|
206
208
|
* this catches everything else.
|
|
207
209
|
*/
|
|
208
210
|
async globalErrorHandler(req, res, next) {
|
|
209
|
-
|
|
211
|
+
log.info('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
|
|
210
212
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Global error handler IS the top-level catch-all
|
|
211
213
|
try {
|
|
212
214
|
// await next() catches BOTH:
|
|
213
215
|
// 1. Synchronous throws from next() itself
|
|
214
216
|
// 2. Rejected promises from downstream async middleware
|
|
215
217
|
await next();
|
|
216
|
-
|
|
218
|
+
log.info('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
|
|
217
219
|
}
|
|
218
220
|
catch (err) {
|
|
219
221
|
const error = (0, core_util_1.toError)(err);
|
|
220
|
-
|
|
222
|
+
log.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
|
|
221
223
|
if (!res.headersSent) {
|
|
222
224
|
// Return HTML error page (not JSON - JsonTranslator handles JSON errors)
|
|
223
225
|
res.status(500).send(`
|
|
@@ -232,7 +234,7 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
|
232
234
|
</html>
|
|
233
235
|
`);
|
|
234
236
|
}
|
|
235
|
-
|
|
237
|
+
log.info('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
/**
|
|
@@ -241,9 +243,9 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
|
241
243
|
* IMPORTANT: Must be async and await next() to properly chain with async middleware.
|
|
242
244
|
*/
|
|
243
245
|
async logNextLayer(req, res, next) {
|
|
244
|
-
|
|
246
|
+
log.info('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
|
|
245
247
|
await next();
|
|
246
|
-
|
|
248
|
+
log.info('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
|
|
247
249
|
}
|
|
248
250
|
/**
|
|
249
251
|
* CORS middleware for localhost development.
|
|
@@ -255,7 +257,7 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
|
255
257
|
* @returns Express middleware handler for CORS
|
|
256
258
|
*/
|
|
257
259
|
corsForLocalhost() {
|
|
258
|
-
|
|
260
|
+
log.info('[WebpiecesMiddleware] CORS enabled for localhost:* origins');
|
|
259
261
|
return (0, cors_1.default)({
|
|
260
262
|
origin: function (origin, callback) {
|
|
261
263
|
// Allow requests with no origin (same-origin, Postman, curl)
|
|
@@ -268,7 +270,7 @@ let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
|
268
270
|
callback(null, true);
|
|
269
271
|
}
|
|
270
272
|
else {
|
|
271
|
-
|
|
273
|
+
log.info(`[CORS] Blocked origin: ${origin} (only localhost:* allowed)`);
|
|
272
274
|
callback(new Error(`CORS not allowed for origin: ${origin}`));
|
|
273
275
|
}
|
|
274
276
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction, RequestHandler } from 'express';\nimport cors from 'cors';\nimport { injectable } from 'inversify';\nimport { 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): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- ExpressWrapper catches errors to translate to HTTP responses\n try {\n await this.executeImpl(req, res, next);\n } catch (err: unknown) {\n const error = toError(err);\n // 5. Handle errors\n this.handleError(res, error);\n }\n }\n\n public async executeImpl(req: Request, res: Response, next: NextFunction) {\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: 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 * CORS middleware for localhost development.\n * Only enables CORS when request origin is localhost:*.\n *\n * Wide open for all headers/methods in dev mode.\n * Non-localhost origins are blocked.\n *\n * @returns Express middleware handler for CORS\n */\n corsForLocalhost(): RequestHandler {\n console.log('[WebpiecesMiddleware] CORS enabled for localhost:* origins');\n\n return cors({\n origin: function (origin, callback) {\n // Allow requests with no origin (same-origin, Postman, curl)\n if (!origin) {\n callback(null, true);\n return;\n }\n\n // Only allow localhost origins\n if (origin.startsWith('http://localhost:') || origin.startsWith('https://localhost:')) {\n callback(null, true);\n } else {\n console.log(`[CORS] Blocked origin: ${origin} (only localhost:* allowed)`);\n callback(new Error(`CORS not allowed for origin: ${origin}`));\n }\n },\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: '*', // Wide open for dev\n exposedHeaders: '*', // Expose all response headers to browser JS\n maxAge: 3600,\n });\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\n *\n * NEW: Simplified - no longer passes headers (ContextFilter handles it now)\n *\n * @param service - The service wrapping the filter chain and controller\n * @param routeMeta - Route metadata for MethodMeta and DTO type\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n service: Service<MethodMeta, WpResponse<unknown>>,\n routeMeta: RouteMetadata,\n ): ExpressWrapper {\n return new ExpressWrapper(service, routeMeta);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction, RequestHandler } from 'express';\nimport cors from 'cors';\nimport { injectable } from 'inversify';\nimport { 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';\nimport { LogManager } from '@webpieces/wp-logging';\n\nconst log = LogManager.getLogger('WebpiecesMiddleware');\n\nexport class ExpressWrapper {\n constructor(\n private service: Service<MethodMeta, WpResponse<unknown>>,\n private routeMeta: RouteMetadata\n ) {\n }\n\n public async execute(req: Request, res: Response, next: NextFunction) {\n // MOVED: Wrap entire request in RequestContext.run()\n // This establishes AsyncLocalStorage context for the request\n await RequestContext.run(async () => {\n await this.executeTryCatch(req, res, next);\n });\n }\n\n public async executeTryCatch(req: Request, res: Response, next: NextFunction): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- ExpressWrapper catches errors to translate to HTTP responses\n try {\n await this.executeImpl(req, res, next);\n } catch (err: unknown) {\n const error = toError(err);\n // 5. Handle errors\n this.handleError(res, error);\n }\n }\n\n public async executeImpl(req: Request, res: Response, next: NextFunction) {\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 log.info('[ExpressWrapper] User Error:', error.message);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n log.info('[ExpressWrapper] Bad Request:', error.message);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n log.info('[ExpressWrapper] Not Found:', error.message);\n } else if (error instanceof HttpTimeoutError) {\n log.error('[ExpressWrapper] Timeout Error:', error.message);\n } else if (error instanceof HttpVendorError) {\n log.error('[ExpressWrapper] Vendor Error:', error.message);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n log.info('[ExpressWrapper] Unauthorized:', error.message);\n } else if (error instanceof HttpForbiddenError) {\n log.info('[ExpressWrapper] Forbidden:', error.message);\n } else if (error instanceof HttpInternalServerError) {\n log.error('[ExpressWrapper] Internal Server Error:', error.message);\n } else if (error instanceof HttpBadGatewayError) {\n log.error('[ExpressWrapper] Bad Gateway:', error.message);\n } else if (error instanceof HttpGatewayTimeoutError) {\n log.error('[ExpressWrapper] Gateway Timeout:', error.message);\n } else {\n log.info('[ExpressWrapper] Generic HttpError:', error.message);\n }\n\n // Serialize ProtocolError to JSON (SYMMETRIC with client)\n const responseJson = JSON.stringify(protocolError);\n res.status(error.code).setHeader('Content-Type', 'application/json').send(responseJson);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n log.error('[ExpressWrapper] Unexpected error:', err);\n const responseJson = JSON.stringify(protocolError);\n res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n }\n}\n\n/**\n * WebpiecesMiddleware - Express middleware for WebPieces server.\n *\n * This class contains all Express middleware used by WebpiecesServer:\n * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page\n * 2. logNextLayer - Request/response logging\n * 3. jsonTranslator - JSON Content-Type validation and error translation\n *\n * The middleware is injected into WebpiecesServerImpl and registered with Express\n * in the start() method.\n *\n * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via\n * Express's registered route handlers (created by RouteBuilder.createHandler()).\n * jsonTranslator only validates Content-Type and translates errors to JSON.\n *\n * NEW: ExpressWrapper simplified - no longer handles JSON or headers\n * - JSON parsing/serialization moved to JsonFilter\n * - Header transfer moved to ContextFilter (injects PlatformHeadersExtension directly)\n * - ExpressWrapper just creates RouterReqResp and invokes filter chain\n *\n * Extension vs Plugin pattern:\n * - Extensions (DI-level): Contribute capabilities to framework (headers, converters, etc.)\n * - Plugins (App-level): Provide complete features with modules + routes (Hibernate, Jackson, etc.)\n */\n@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 log.info('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Global error handler IS the top-level catch-all\n try {\n // await next() catches BOTH:\n // 1. Synchronous throws from next() itself\n // 2. Rejected promises from downstream async middleware\n await next();\n log.info(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (success):',\n req.method,\n req.path,\n );\n } catch (err: unknown) {\n const error = toError(err);\n log.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonTranslator handles JSON errors)\n res.status(500).send(`\n <!DOCTYPE html>\n <html>\n <head><title>Server Error</title></head>\n <body>\n <h1>You hit a server error</h1>\n <p>An unexpected error occurred while processing your request.</p>\n <pre>${error.message}</pre>\n </body>\n </html>\n `);\n }\n log.info(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (error):',\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 log.info('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n await next();\n log.info('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\n }\n\n /**\n * CORS middleware for localhost development.\n * Only enables CORS when request origin is localhost:*.\n *\n * Wide open for all headers/methods in dev mode.\n * Non-localhost origins are blocked.\n *\n * @returns Express middleware handler for CORS\n */\n corsForLocalhost(): RequestHandler {\n log.info('[WebpiecesMiddleware] CORS enabled for localhost:* origins');\n\n return cors({\n origin: function (origin, callback) {\n // Allow requests with no origin (same-origin, Postman, curl)\n if (!origin) {\n callback(null, true);\n return;\n }\n\n // Only allow localhost origins\n if (origin.startsWith('http://localhost:') || origin.startsWith('https://localhost:')) {\n callback(null, true);\n } else {\n log.info(`[CORS] Blocked origin: ${origin} (only localhost:* allowed)`);\n callback(new Error(`CORS not allowed for origin: ${origin}`));\n }\n },\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: '*', // Wide open for dev\n exposedHeaders: '*', // Expose all response headers to browser JS\n maxAge: 3600,\n });\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\n *\n * NEW: Simplified - no longer passes headers (ContextFilter handles it now)\n *\n * @param service - The service wrapping the filter chain and controller\n * @param routeMeta - Route metadata for MethodMeta and DTO type\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n service: Service<MethodMeta, WpResponse<unknown>>,\n routeMeta: RouteMetadata,\n ): ExpressWrapper {\n return new ExpressWrapper(service, routeMeta);\n }\n}\n"]}
|
|
@@ -2,44 +2,6 @@ import { Express } from 'express';
|
|
|
2
2
|
import { Container } from 'inversify';
|
|
3
3
|
import { ClassType, FilterDefinition, RouteBuilderImpl } from '@webpieces/http-routing';
|
|
4
4
|
import { WebpiecesMiddleware } from './WebpiecesMiddleware';
|
|
5
|
-
/**
|
|
6
|
-
* WebpiecesRouteCreator - Embeddable adapter that mounts the webpieces
|
|
7
|
-
* api -> filters -> controller pipeline onto ANY existing Express app.
|
|
8
|
-
*
|
|
9
|
-
* Legacy Express apps can adopt webpieces incrementally: existing routes and
|
|
10
|
-
* middleware keep working untouched; each wired webpieces route is fully
|
|
11
|
-
* self-contained (own body parsing, own RequestContext, own error->JSON mapping).
|
|
12
|
-
* This class never calls app.use() - it only registers per-route handlers.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* ```typescript
|
|
16
|
-
* const app = express(); // your existing legacy app
|
|
17
|
-
* const container = new Container();
|
|
18
|
-
* await container.load(buildProviderModule()); // picks up @provideSingleton classes
|
|
19
|
-
* await container.load(WebpiecesModule); // required if you use ContextFilter
|
|
20
|
-
*
|
|
21
|
-
* const creator = new WebpiecesRouteCreator(app, container);
|
|
22
|
-
* creator.wireFilters(
|
|
23
|
-
* new FilterDefinition(2000, ContextFilter, '*'),
|
|
24
|
-
* new FilterDefinition(1900, AuthFilter, 'src/controllers/admin/**'),
|
|
25
|
-
* );
|
|
26
|
-
* creator.wireApi(SaveApi, SaveController); // controller resolved from container
|
|
27
|
-
* creator.wireApi(PublicApi, PublicController);
|
|
28
|
-
* app.listen(8080);
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* Notes:
|
|
32
|
-
* - ALL wireFilters() calls must come BEFORE the first wireApi() call. Filter
|
|
33
|
-
* chains are composed per-route at wireApi time, so late filters would be
|
|
34
|
-
* silently ignored - we throw instead.
|
|
35
|
-
* - Scoped filter glob patterns match the controller filepath from the
|
|
36
|
-
* @SourceFile decorator, falling back to the pattern `**\/{ClassName}.ts`.
|
|
37
|
-
* - Want webpieces' localhost CORS? Opt in yourself:
|
|
38
|
-
* `app.use(new WebpiecesMiddleware().corsForLocalhost())`.
|
|
39
|
-
*
|
|
40
|
-
* This same class is used internally by WebpiecesServerImpl.start(), so the
|
|
41
|
-
* full server and the embeddable adapter share one code path.
|
|
42
|
-
*/
|
|
43
5
|
export declare class WebpiecesRouteCreator {
|
|
44
6
|
private app;
|
|
45
7
|
private routeBuilder;
|
|
@@ -4,6 +4,7 @@ exports.WebpiecesRouteCreator = void 0;
|
|
|
4
4
|
const http_routing_1 = require("@webpieces/http-routing");
|
|
5
5
|
const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
|
|
6
6
|
const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
7
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
7
8
|
/**
|
|
8
9
|
* WebpiecesRouteCreator - Embeddable adapter that mounts the webpieces
|
|
9
10
|
* api -> filters -> controller pipeline onto ANY existing Express app.
|
|
@@ -42,6 +43,7 @@ const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
|
42
43
|
* This same class is used internally by WebpiecesServerImpl.start(), so the
|
|
43
44
|
* full server and the embeddable adapter share one code path.
|
|
44
45
|
*/
|
|
46
|
+
const log = wp_logging_1.LogManager.getLogger('WebpiecesRouteCreator');
|
|
45
47
|
class WebpiecesRouteCreator {
|
|
46
48
|
app;
|
|
47
49
|
routeBuilder;
|
|
@@ -149,7 +151,7 @@ class WebpiecesRouteCreator {
|
|
|
149
151
|
this.app.patch(path, expressHandler);
|
|
150
152
|
break;
|
|
151
153
|
default:
|
|
152
|
-
|
|
154
|
+
log.warn(`[WebpiecesRouteCreator] Unknown HTTP method: ${httpMethod}`);
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesRouteCreator.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesRouteCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { Express } from 'express';\nimport { Container } from 'inversify';\nimport {\n ApiRoutingFactory,\n ClassType,\n ExpressRouteHandler,\n FilterDefinition,\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n} from '@webpieces/http-routing';\nimport { WebpiecesMiddleware } from './WebpiecesMiddleware';\nimport { InProcessApiClientFactory } from './InProcessApiClientFactory';\n\n/**\n * WebpiecesRouteCreator - Embeddable adapter that mounts the webpieces\n * api -> filters -> controller pipeline onto ANY existing Express app.\n *\n * Legacy Express apps can adopt webpieces incrementally: existing routes and\n * middleware keep working untouched; each wired webpieces route is fully\n * self-contained (own body parsing, own RequestContext, own error->JSON mapping).\n * This class never calls app.use() - it only registers per-route handlers.\n *\n * Usage:\n * ```typescript\n * const app = express(); // your existing legacy app\n * const container = new Container();\n * await container.load(buildProviderModule()); // picks up @provideSingleton classes\n * await container.load(WebpiecesModule); // required if you use ContextFilter\n *\n * const creator = new WebpiecesRouteCreator(app, container);\n * creator.wireFilters(\n * new FilterDefinition(2000, ContextFilter, '*'),\n * new FilterDefinition(1900, AuthFilter, 'src/controllers/admin/**'),\n * );\n * creator.wireApi(SaveApi, SaveController); // controller resolved from container\n * creator.wireApi(PublicApi, PublicController);\n * app.listen(8080);\n * ```\n *\n * Notes:\n * - ALL wireFilters() calls must come BEFORE the first wireApi() call. Filter\n * chains are composed per-route at wireApi time, so late filters would be\n * silently ignored - we throw instead.\n * - Scoped filter glob patterns match the controller filepath from the\n * @SourceFile decorator, falling back to the pattern `**\\/{ClassName}.ts`.\n * - Want webpieces' localhost CORS? Opt in yourself:\n * `app.use(new WebpiecesMiddleware().corsForLocalhost())`.\n *\n * This same class is used internally by WebpiecesServerImpl.start(), so the\n * full server and the embeddable adapter share one code path.\n */\nexport class WebpiecesRouteCreator {\n private routeBuilder: RouteBuilderImpl;\n private middleware: WebpiecesMiddleware;\n private clientFactory: InProcessApiClientFactory;\n\n /** Locks wireFilters() once the first wireApi() has composed a filter chain. */\n private apisWired = false;\n\n /**\n * @param app - The Express app to mount routes on (yours - never taken over)\n * @param container - Inversify container used to resolve controllers and filters\n * @param routeBuilder - Internal: WebpiecesServerImpl passes its DI singleton; standalone users omit\n * @param middleware - Internal: WebpiecesServerImpl passes its DI singleton; standalone users omit\n */\n constructor(\n private app: Express,\n container: Container,\n routeBuilder?: RouteBuilderImpl,\n middleware?: WebpiecesMiddleware,\n ) {\n this.routeBuilder = routeBuilder ?? new RouteBuilderImpl();\n this.routeBuilder.setContainer(container);\n this.middleware = middleware ?? new WebpiecesMiddleware();\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n\n /**\n * Register filters that wrap every matching route (glob pattern vs controller filepath).\n * Must be called before the first wireApi() - filter chains are composed per-route.\n */\n wireFilters(...defs: FilterDefinition[]): void {\n if (this.apisWired) {\n throw new Error(\n 'wireFilters() must be called before wireApi() - filter chains are composed per-route at wireApi time, so filters added later would never run.',\n );\n }\n for (const def of defs) {\n this.routeBuilder.addFilter(def);\n }\n }\n\n /**\n * Wire an API prototype class (with @ApiPath/@Endpoint decorators) to its\n * controller, mounting one Express route per endpoint with the full filter\n * chain. The controller is resolved from the Inversify container.\n */\n wireApi<TApi, TController extends TApi>(\n apiPrototype: ClassType<TApi>,\n controllerClass: ClassType<TController>,\n ): void {\n this.apisWired = true;\n\n // Reuses all existing validation: @ApiPath present, controller extends\n // api prototype, every endpoint implemented + has @Authentication.\n const factory = new ApiRoutingFactory(apiPrototype, controllerClass);\n\n // Mount only the routes added by THIS call\n const routesBefore = this.routeBuilder.getRoutes().length;\n factory.configure(this.routeBuilder);\n const routes = this.routeBuilder.getRoutes();\n\n for (let i = routesBefore; i < routes.length; i++) {\n this.mountRoute(routes[i]);\n }\n }\n\n /**\n * Mount every route currently registered on the RouteBuilder.\n * Used by WebpiecesServerImpl.start() where routes were registered up front\n * from WebAppMeta.getRoutes().\n *\n * @returns Number of routes mounted\n */\n mountRegisteredRoutes(): number {\n const routes = this.routeBuilder.getRoutes();\n for (const routeWithMeta of routes) {\n this.mountRoute(routeWithMeta);\n }\n return routes.length;\n }\n\n /**\n * Create an in-process API client (full filter chain + controller, no HTTP).\n * Same testing story as WebpiecesServer.createApiClient().\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n /**\n * Escape hatch for advanced wiring (e.g. addRoute with a hand-built RouteDefinition).\n */\n getRouteBuilder(): RouteBuilderImpl {\n return this.routeBuilder;\n }\n\n /**\n * Compose the filter chain for one route and register it on the Express app.\n */\n private mountRoute(routeWithMeta: RouteHandlerWithMeta): void {\n const service = this.routeBuilder.createRouteHandler(routeWithMeta);\n const routeMeta = routeWithMeta.definition.routeMeta;\n\n // ExpressWrapper handles the full request/response cycle per route:\n // RequestContext.run, header read, manual JSON body parse, error->ProtocolError\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 }\n\n private registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler): void {\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(`[WebpiecesRouteCreator] Unknown HTTP method: ${httpMethod}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesRouteCreator.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesRouteCreator.ts"],"names":[],"mappings":"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","sourcesContent":["import { Express } from 'express';\nimport { Container } from 'inversify';\nimport {\n ApiRoutingFactory,\n ClassType,\n ExpressRouteHandler,\n FilterDefinition,\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n} from '@webpieces/http-routing';\nimport { WebpiecesMiddleware } from './WebpiecesMiddleware';\nimport { InProcessApiClientFactory } from './InProcessApiClientFactory';\nimport { LogManager } from '@webpieces/wp-logging';\n\n/**\n * WebpiecesRouteCreator - Embeddable adapter that mounts the webpieces\n * api -> filters -> controller pipeline onto ANY existing Express app.\n *\n * Legacy Express apps can adopt webpieces incrementally: existing routes and\n * middleware keep working untouched; each wired webpieces route is fully\n * self-contained (own body parsing, own RequestContext, own error->JSON mapping).\n * This class never calls app.use() - it only registers per-route handlers.\n *\n * Usage:\n * ```typescript\n * const app = express(); // your existing legacy app\n * const container = new Container();\n * await container.load(buildProviderModule()); // picks up @provideSingleton classes\n * await container.load(WebpiecesModule); // required if you use ContextFilter\n *\n * const creator = new WebpiecesRouteCreator(app, container);\n * creator.wireFilters(\n * new FilterDefinition(2000, ContextFilter, '*'),\n * new FilterDefinition(1900, AuthFilter, 'src/controllers/admin/**'),\n * );\n * creator.wireApi(SaveApi, SaveController); // controller resolved from container\n * creator.wireApi(PublicApi, PublicController);\n * app.listen(8080);\n * ```\n *\n * Notes:\n * - ALL wireFilters() calls must come BEFORE the first wireApi() call. Filter\n * chains are composed per-route at wireApi time, so late filters would be\n * silently ignored - we throw instead.\n * - Scoped filter glob patterns match the controller filepath from the\n * @SourceFile decorator, falling back to the pattern `**\\/{ClassName}.ts`.\n * - Want webpieces' localhost CORS? Opt in yourself:\n * `app.use(new WebpiecesMiddleware().corsForLocalhost())`.\n *\n * This same class is used internally by WebpiecesServerImpl.start(), so the\n * full server and the embeddable adapter share one code path.\n */\nconst log = LogManager.getLogger('WebpiecesRouteCreator');\n\nexport class WebpiecesRouteCreator {\n private routeBuilder: RouteBuilderImpl;\n private middleware: WebpiecesMiddleware;\n private clientFactory: InProcessApiClientFactory;\n\n /** Locks wireFilters() once the first wireApi() has composed a filter chain. */\n private apisWired = false;\n\n /**\n * @param app - The Express app to mount routes on (yours - never taken over)\n * @param container - Inversify container used to resolve controllers and filters\n * @param routeBuilder - Internal: WebpiecesServerImpl passes its DI singleton; standalone users omit\n * @param middleware - Internal: WebpiecesServerImpl passes its DI singleton; standalone users omit\n */\n constructor(\n private app: Express,\n container: Container,\n routeBuilder?: RouteBuilderImpl,\n middleware?: WebpiecesMiddleware,\n ) {\n this.routeBuilder = routeBuilder ?? new RouteBuilderImpl();\n this.routeBuilder.setContainer(container);\n this.middleware = middleware ?? new WebpiecesMiddleware();\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n\n /**\n * Register filters that wrap every matching route (glob pattern vs controller filepath).\n * Must be called before the first wireApi() - filter chains are composed per-route.\n */\n wireFilters(...defs: FilterDefinition[]): void {\n if (this.apisWired) {\n throw new Error(\n 'wireFilters() must be called before wireApi() - filter chains are composed per-route at wireApi time, so filters added later would never run.',\n );\n }\n for (const def of defs) {\n this.routeBuilder.addFilter(def);\n }\n }\n\n /**\n * Wire an API prototype class (with @ApiPath/@Endpoint decorators) to its\n * controller, mounting one Express route per endpoint with the full filter\n * chain. The controller is resolved from the Inversify container.\n */\n wireApi<TApi, TController extends TApi>(\n apiPrototype: ClassType<TApi>,\n controllerClass: ClassType<TController>,\n ): void {\n this.apisWired = true;\n\n // Reuses all existing validation: @ApiPath present, controller extends\n // api prototype, every endpoint implemented + has @Authentication.\n const factory = new ApiRoutingFactory(apiPrototype, controllerClass);\n\n // Mount only the routes added by THIS call\n const routesBefore = this.routeBuilder.getRoutes().length;\n factory.configure(this.routeBuilder);\n const routes = this.routeBuilder.getRoutes();\n\n for (let i = routesBefore; i < routes.length; i++) {\n this.mountRoute(routes[i]);\n }\n }\n\n /**\n * Mount every route currently registered on the RouteBuilder.\n * Used by WebpiecesServerImpl.start() where routes were registered up front\n * from WebAppMeta.getRoutes().\n *\n * @returns Number of routes mounted\n */\n mountRegisteredRoutes(): number {\n const routes = this.routeBuilder.getRoutes();\n for (const routeWithMeta of routes) {\n this.mountRoute(routeWithMeta);\n }\n return routes.length;\n }\n\n /**\n * Create an in-process API client (full filter chain + controller, no HTTP).\n * Same testing story as WebpiecesServer.createApiClient().\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n /**\n * Escape hatch for advanced wiring (e.g. addRoute with a hand-built RouteDefinition).\n */\n getRouteBuilder(): RouteBuilderImpl {\n return this.routeBuilder;\n }\n\n /**\n * Compose the filter chain for one route and register it on the Express app.\n */\n private mountRoute(routeWithMeta: RouteHandlerWithMeta): void {\n const service = this.routeBuilder.createRouteHandler(routeWithMeta);\n const routeMeta = routeWithMeta.definition.routeMeta;\n\n // ExpressWrapper handles the full request/response cycle per route:\n // RequestContext.run, header read, manual JSON body parse, error->ProtocolError\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 }\n\n private registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler): void {\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 log.warn(`[WebpiecesRouteCreator] Unknown HTTP method: ${httpMethod}`);\n }\n }\n}\n"]}
|
|
@@ -2,30 +2,6 @@ import { Container, ContainerModule } from 'inversify';
|
|
|
2
2
|
import { RouteBuilderImpl, WebAppMeta } from '@webpieces/http-routing';
|
|
3
3
|
import { WebpiecesServer } from './WebpiecesServer';
|
|
4
4
|
import { WebpiecesMiddleware } from './WebpiecesMiddleware';
|
|
5
|
-
/**
|
|
6
|
-
* WebpiecesServerImpl - Internal server implementation.
|
|
7
|
-
*
|
|
8
|
-
* This class implements the WebpiecesServer interface and contains
|
|
9
|
-
* all the actual server logic. It is created by WebpiecesFactory.create().
|
|
10
|
-
*
|
|
11
|
-
* This class uses a two-container pattern similar to Java WebPieces:
|
|
12
|
-
* 1. webpiecesContainer: Core WebPieces framework bindings
|
|
13
|
-
* 2. appContainer: User's application bindings (child of webpiecesContainer)
|
|
14
|
-
*
|
|
15
|
-
* This separation allows:
|
|
16
|
-
* - Clean separation of concerns
|
|
17
|
-
* - Better testability
|
|
18
|
-
* - Ability to override framework bindings in tests
|
|
19
|
-
*
|
|
20
|
-
* The server:
|
|
21
|
-
* 1. Initializes both DI containers from WebAppMeta.getDIModules()
|
|
22
|
-
* 2. Registers routes using explicit RouteBuilderImpl
|
|
23
|
-
* 3. Creates filter chains
|
|
24
|
-
* 4. Supports both HTTP server mode and testing mode (no HTTP)
|
|
25
|
-
*
|
|
26
|
-
* DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()
|
|
27
|
-
* and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
|
|
28
|
-
*/
|
|
29
5
|
export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
30
6
|
private meta;
|
|
31
7
|
private routeBuilder;
|
|
@@ -113,7 +89,7 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
113
89
|
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
114
90
|
* so subsequent calls reuse the same filter chain (efficient!).
|
|
115
91
|
*
|
|
116
|
-
* @param apiPrototype - The API prototype
|
|
92
|
+
* @param apiPrototype - The abstract API prototype whose routing decorators declare the routes
|
|
117
93
|
* @returns A proxy that implements the API interface
|
|
118
94
|
*
|
|
119
95
|
* Example:
|
|
@@ -9,6 +9,7 @@ const http_routing_1 = require("@webpieces/http-routing");
|
|
|
9
9
|
const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
|
|
10
10
|
const WebpiecesRouteCreator_1 = require("./WebpiecesRouteCreator");
|
|
11
11
|
const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
12
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
12
13
|
/**
|
|
13
14
|
* WebpiecesServerImpl - Internal server implementation.
|
|
14
15
|
*
|
|
@@ -33,6 +34,7 @@ const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
|
33
34
|
* DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()
|
|
34
35
|
* and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
|
|
35
36
|
*/
|
|
37
|
+
const log = wp_logging_1.LogManager.getLogger('WebpiecesServer');
|
|
36
38
|
let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
37
39
|
meta;
|
|
38
40
|
routeBuilder;
|
|
@@ -154,12 +156,12 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
154
156
|
const promise = new Promise((resolve, reject) => {
|
|
155
157
|
this.server = this.app.listen(this.port, (error) => {
|
|
156
158
|
if (error) {
|
|
157
|
-
|
|
159
|
+
log.error(`[WebpiecesServer] Failed to start server:`, error);
|
|
158
160
|
reject(error);
|
|
159
161
|
return;
|
|
160
162
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
log.info(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
|
|
164
|
+
log.info(`[WebpiecesServer] Registered ${routeCount} routes`);
|
|
163
165
|
resolve();
|
|
164
166
|
});
|
|
165
167
|
});
|
|
@@ -179,11 +181,11 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
179
181
|
return new Promise((resolve, reject) => {
|
|
180
182
|
this.server.close((err) => {
|
|
181
183
|
if (err) {
|
|
182
|
-
|
|
184
|
+
log.error('[WebpiecesServer] Error stopping server:', err);
|
|
183
185
|
reject(err);
|
|
184
186
|
return;
|
|
185
187
|
}
|
|
186
|
-
|
|
188
|
+
log.info('[WebpiecesServer] Server stopped');
|
|
187
189
|
resolve();
|
|
188
190
|
});
|
|
189
191
|
});
|
|
@@ -211,7 +213,7 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
211
213
|
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
212
214
|
* so subsequent calls reuse the same filter chain (efficient!).
|
|
213
215
|
*
|
|
214
|
-
* @param apiPrototype - The API prototype
|
|
216
|
+
* @param apiPrototype - The abstract API prototype whose routing decorators declare the routes
|
|
215
217
|
* @returns A proxy that implements the API interface
|
|
216
218
|
*
|
|
217
219
|
* Example:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesServerImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServerImpl.ts"],"names":[],"mappings":";;;;AAAA,8DAAyC;AACzC,yCAAyE;AACzE,wEAAoE;AACpE,0DAKiC;AAEjC,+DAA0D;AAC1D,mEAA8D;AAC9D,2EAAsE;AAEtE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAgBW;IACD;IACG;IAjBjC,kBAAkB,CAAa;IAEvC;;;;OAIG;IACK,YAAY,CAAa;IAEzB,WAAW,GAAG,KAAK,CAAC;IACpB,GAAG,CAAW;IACd,MAAM,CAAiC;IACvC,IAAI,GAAW,IAAI,CAAC;IAE5B,YACuC,IAAgB,EACjB,YAA8B,EAC3B,UAA+B;QAFjC,SAAI,GAAJ,IAAI,CAAY;QACjB,iBAAY,GAAZ,YAAY,CAAkB;QAC3B,eAAU,GAAV,UAAU,CAAqB;IACrE,CAAC;IAEJ;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,CACZ,kBAA6B,EAC7B,YAA8B;QAE9B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAE7C,+DAA+D;QAC/D,IAAI,CAAC,YAAY,GAAG,IAAI,qBAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;QAEvE,0FAA0F;QAC1F,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAElD,oCAAoC;QACpC,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAEvC,iCAAiC;QACjC,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,aAAa,CAAC,YAA8B;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QAEzC,yEAAyE;QACzE,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAEpD,8CAA8C;QAC9C,kFAAkF;QAClF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;QAED,gEAAgE;QAChE,IAAI,YAAY,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACK,cAAc;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAE3C,mDAAmD;QACnD,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACrC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,CAAC,OAAe,IAAI,EAAE,QAAkB;QAC/C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;QACjF,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAEjB,IAAG,QAAQ,EAAE,CAAC;YACV,oDAAoD;YACpD,uEAAuE;YACvE,OAAO;QACX,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;QAErB,yDAAyD;QACzD,yDAAyD;QACzD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAEvE,0CAA0C;QAC1C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAEjD,oCAAoC;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAEjE,gEAAgE;QAChE,gEAAgE;QAChE,MAAM,YAAY,GAAG,IAAI,6CAAqB,CAC1C,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAClB,CAAC;QACF,MAAM,UAAU,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC;QAExD,oCAAoC;QACpC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAa,EAAE,EAAE;gBACxD,IAAI,KAAK,EAAE,CAAC;oBACR,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;oBAClE,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACX,CAAC;gBACD,OAAO,CAAC,GAAG,CAAC,0DAA0D,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACnF,OAAO,CAAC,GAAG,CAAC,gCAAgC,UAAU,SAAS,CAAC,CAAC;gBACjE,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC;IAClB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI;QACN,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,OAAO;QACX,CAAC;QAED,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzC,IAAI,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,GAAW,EAAE,EAAE;gBAC/B,IAAI,GAAG,EAAE,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;oBAC/D,MAAM,CAAC,GAAG,CAAC,CAAC;oBACZ,OAAO;gBACX,CAAC;gBACD,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;gBAChD,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;OAMG;IACH,YAAY;QACR,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QAC3F,CAAC;QAED,4FAA4F;QAC5F,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,GAAG,IAAI,qDAAyB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IAC5D,CAAC;IAEO,aAAa,CAA6B;CACrD,CAAA;AA5OY,kDAAmB;8BAAnB,mBAAmB;IAF/B,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IAiBJ,mBAAA,IAAA,kBAAM,EAAC,gCAAiB,CAAC,CAAA;IACzB,mBAAA,IAAA,kBAAM,EAAC,+BAAgB,CAAC,CAAA;IACxB,mBAAA,IAAA,kBAAM,EAAC,yCAAmB,CAAC,CAAA;qDADoB,+BAAgB;QACf,yCAAmB;GAlB/D,mBAAmB,CA4O/B","sourcesContent":["import express, {Express} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n WEBAPP_META_TOKEN,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {WebpiecesRouteCreator} from './WebpiecesRouteCreator';\nimport {InProcessApiClientFactory} from './InProcessApiClientFactory';\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 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(WEBAPP_META_TOKEN) private meta: WebAppMeta,\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server asynchronously.\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n appOverrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\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(appOverrides);\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(appOverrides?: 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 appOverrides LAST so they can override existing bindings\n if (appOverrides) {\n await this.appContainer.load(appOverrides);\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: CORS for localhost development\n this.app.use(this.middleware.corsForLocalhost());\n\n // Layer 3: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes via the shared adapter (same code path as the\n // embeddable WebpiecesRouteCreator used by legacy Express apps)\n const routeCreator = new WebpiecesRouteCreator(\n this.app,\n this.appContainer,\n this.routeBuilder,\n this.middleware,\n );\n const routeCount = routeCreator.mountRegisteredRoutes();\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 * 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>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before createApiClient().');\n }\n\n // Delegates to the shared factory (same code path as WebpiecesRouteCreator.createApiClient)\n if (!this.clientFactory) {\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n private clientFactory?: InProcessApiClientFactory;\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} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n WEBAPP_META_TOKEN,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {WebpiecesRouteCreator} from './WebpiecesRouteCreator';\nimport {InProcessApiClientFactory} from './InProcessApiClientFactory';\nimport {LogManager} from '@webpieces/wp-logging';\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 */\nconst log = LogManager.getLogger('WebpiecesServer');\n\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\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(WEBAPP_META_TOKEN) private meta: WebAppMeta,\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server asynchronously.\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n appOverrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\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(appOverrides);\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(appOverrides?: 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 appOverrides LAST so they can override existing bindings\n if (appOverrides) {\n await this.appContainer.load(appOverrides);\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: CORS for localhost development\n this.app.use(this.middleware.corsForLocalhost());\n\n // Layer 3: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes via the shared adapter (same code path as the\n // embeddable WebpiecesRouteCreator used by legacy Express apps)\n const routeCreator = new WebpiecesRouteCreator(\n this.app,\n this.appContainer,\n this.routeBuilder,\n this.middleware,\n );\n const routeCount = routeCreator.mountRegisteredRoutes();\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 log.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n log.info(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n log.info(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\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 log.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n log.info('[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 abstract API prototype whose routing decorators declare the routes\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before createApiClient().');\n }\n\n // Delegates to the shared factory (same code path as WebpiecesRouteCreator.createApiClient)\n if (!this.clientFactory) {\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n private clientFactory?: InProcessApiClientFactory;\n}\n"]}
|
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { MethodMeta } from '@webpieces/http-routing';
|
|
2
2
|
import { Filter, WpResponse, Service } from '@webpieces/http-filters';
|
|
3
3
|
import { HeaderRegistry } from '@webpieces/http-api';
|
|
4
|
-
/**
|
|
5
|
-
* ContextFilter - Transfers platform headers and stores request metadata in RequestContext.
|
|
6
|
-
* Priority: 2000 (executes first in filter chain)
|
|
7
|
-
*
|
|
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
|
|
22
|
-
*/
|
|
23
4
|
export declare class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
24
5
|
private transferredHeaders;
|
|
25
6
|
constructor(registry: HeaderRegistry);
|
|
@@ -9,6 +9,7 @@ const http_filters_1 = require("@webpieces/http-filters");
|
|
|
9
9
|
const http_api_1 = require("@webpieces/http-api");
|
|
10
10
|
const WebpiecesCoreHeaders_1 = require("../headers/WebpiecesCoreHeaders");
|
|
11
11
|
const ContextKeys_1 = require("../headers/ContextKeys");
|
|
12
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
12
13
|
/**
|
|
13
14
|
* ContextFilter - Transfers platform headers and stores request metadata in RequestContext.
|
|
14
15
|
* Priority: 2000 (executes first in filter chain)
|
|
@@ -28,6 +29,7 @@ const ContextKeys_1 = require("../headers/ContextKeys");
|
|
|
28
29
|
* 6. Downstream filters and controller can access headers + metadata
|
|
29
30
|
* 7. Context auto-clears when RequestContext.run() completes
|
|
30
31
|
*/
|
|
32
|
+
const log = wp_logging_1.LogManager.getLogger('ContextFilter');
|
|
31
33
|
let ContextFilter = class ContextFilter extends http_filters_1.Filter {
|
|
32
34
|
transferredHeaders;
|
|
33
35
|
constructor(registry) {
|
|
@@ -35,7 +37,7 @@ let ContextFilter = class ContextFilter extends http_filters_1.Filter {
|
|
|
35
37
|
// The registry is the single source of truth (all modules' extensions,
|
|
36
38
|
// duplicate-validated at startup)
|
|
37
39
|
this.transferredHeaders = registry.getTransferredHeaders();
|
|
38
|
-
|
|
40
|
+
log.info(`[ContextFilter] Using ${registry.getHeaders().length} platform headers from HeaderRegistry (${this.transferredHeaders.length} transferred)`);
|
|
39
41
|
}
|
|
40
42
|
async filter(meta, nextFilter) {
|
|
41
43
|
// Transfer platform headers from MethodMeta.requestHeaders to RequestContext
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,0DAAuE;AACvE,0DAAyD;AACzD,0DAAsE;AACtE,kDAAqE;AACrE,0EAAqE;AACrE,wDAAmD;AAEnD;;;;;;;;;;;;;;;;;;GAkBG;
|
|
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} 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, HeaderRegistry } from '@webpieces/http-api';\nimport {WebpiecesCoreHeaders} from \"../headers/WebpiecesCoreHeaders\";\nimport {ContextKeys} from \"../headers/ContextKeys\";\nimport { LogManager } from '@webpieces/wp-logging';\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 */\nconst log = LogManager.getLogger('ContextFilter');\n\n@provideSingleton()\n@injectable()\nexport class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private transferredHeaders: PlatformHeader[];\n\n constructor(\n @inject(HeaderRegistry) registry: HeaderRegistry\n ) {\n super();\n\n // The registry is the single source of truth (all modules' extensions,\n // duplicate-validated at startup)\n this.transferredHeaders = registry.getTransferredHeaders();\n\n log.info(`[ContextFilter] Using ${registry.getHeaders().length} platform headers from HeaderRegistry (${this.transferredHeaders.length} transferred)`);\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.putHeader(ContextKeys.METHOD_META, meta);\n RequestContext.putHeader(ContextKeys.REQUEST_PATH, meta.path);\n RequestContext.putHeader(ContextKeys.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.transferredHeaders) {\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,22 +1,6 @@
|
|
|
1
1
|
import { MethodMeta } from '@webpieces/http-routing';
|
|
2
2
|
import { Filter, WpResponse, Service } from '@webpieces/http-filters';
|
|
3
3
|
import { HeaderMethods, HeaderRegistry } from '@webpieces/http-api';
|
|
4
|
-
/**
|
|
5
|
-
* LogApiFilter - Structured API logging for all requests/responses.
|
|
6
|
-
* Priority: 1800 (after ContextFilter at 2000, before custom filters)
|
|
7
|
-
*
|
|
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).
|
|
16
|
-
*
|
|
17
|
-
* User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
|
|
18
|
-
* because they are expected behavior from the server's perspective.
|
|
19
|
-
*/
|
|
20
4
|
export declare class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
21
5
|
private headerMethods;
|
|
22
6
|
private logApiCall;
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const inversify_1 = require("inversify");
|
|
6
6
|
const http_routing_1 = require("@webpieces/http-routing");
|
|
7
7
|
const http_filters_1 = require("@webpieces/http-filters");
|
|
8
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
8
9
|
const http_api_1 = require("@webpieces/http-api");
|
|
9
10
|
/**
|
|
10
11
|
* LogApiFilter - Structured API logging for all requests/responses.
|
|
@@ -22,6 +23,7 @@ const http_api_1 = require("@webpieces/http-api");
|
|
|
22
23
|
* User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
|
|
23
24
|
* because they are expected behavior from the server's perspective.
|
|
24
25
|
*/
|
|
26
|
+
const log = wp_logging_1.LogManager.getLogger('LogApiFilter');
|
|
25
27
|
let LogApiFilter = class LogApiFilter extends http_filters_1.Filter {
|
|
26
28
|
headerMethods;
|
|
27
29
|
logApiCall;
|
|
@@ -32,7 +34,7 @@ let LogApiFilter = class LogApiFilter extends http_filters_1.Filter {
|
|
|
32
34
|
// The registry is the single source of truth (all modules' extensions,
|
|
33
35
|
// duplicate-validated at startup). Log map keys use loggerMdcKey when set.
|
|
34
36
|
this.allHeaders = registry.getHeaders();
|
|
35
|
-
|
|
37
|
+
log.info(`[LogApiFilter] Using ${this.allHeaders.length} platform headers from HeaderRegistry`);
|
|
36
38
|
this.logApiCall = new http_api_1.LogApiCall();
|
|
37
39
|
}
|
|
38
40
|
async filter(meta, nextFilter) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,0DAA2F;AAC3F,0DAAsE;AACtE,kDAK6B;AAE7B;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAA6C;AAC7C,0DAA2F;AAC3F,0DAAsE;AACtE,sDAAmD;AACnD,kDAK6B;AAE7B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,GAAG,GAAG,uBAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;AAI1C,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,qBAAuC;IAMlC;IAL3B,UAAU,CAAa;IACvB,UAAU,CAAmB;IAErC,YAC4B,QAAwB,EACjB,aAA4B;QAE3D,KAAK,EAAE,CAAC;QAFuB,kBAAa,GAAb,aAAa,CAAe;QAI3D,uEAAuE;QACvE,2EAA2E;QAC3E,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;QAExC,GAAG,CAAC,IAAI,CAAC,wBAAwB,IAAI,CAAC,UAAU,CAAC,MAAM,uCAAuC,CAAC,CAAC;QAEhG,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;AApCY,oCAAY;uBAAZ,YAAY;IAFxB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IAMJ,mBAAA,IAAA,kBAAM,EAAC,yBAAc,CAAC,CAAA;IACtB,mBAAA,IAAA,kBAAM,EAAC,wBAAa,CAAC,CAAA;6CADY,yBAAc;QACF,wBAAa;GANtD,YAAY,CAoCxB","sourcesContent":["import {inject, injectable} from 'inversify';\nimport {provideSingleton, MethodMeta, RequestContextReader} from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport { LogManager } from '@webpieces/wp-logging';\nimport {\n PlatformHeader,\n HeaderMethods,\n HeaderRegistry,\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 */\nconst log = LogManager.getLogger('LogApiFilter');\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 @inject(HeaderRegistry) registry: HeaderRegistry,\n @inject(HeaderMethods) private headerMethods: HeaderMethods\n ) {\n super();\n\n // The registry is the single source of truth (all modules' extensions,\n // duplicate-validated at startup). Log map keys use loggerMdcKey when set.\n this.allHeaders = registry.getHeaders();\n\n log.info(`[LogApiFilter] Using ${this.allHeaders.length} platform headers from HeaderRegistry`);\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"]}
|
|
@@ -4,6 +4,8 @@ exports.WebpiecesModule = void 0;
|
|
|
4
4
|
const inversify_1 = require("inversify");
|
|
5
5
|
const http_api_1 = require("@webpieces/http-api");
|
|
6
6
|
const WebpiecesCoreHeaders_1 = require("../headers/WebpiecesCoreHeaders");
|
|
7
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
8
|
+
const log = wp_logging_1.LogManager.getLogger('WebpiecesModule');
|
|
7
9
|
/**
|
|
8
10
|
* WebpiecesModule - Framework-level DI bindings.
|
|
9
11
|
*
|
|
@@ -40,6 +42,6 @@ exports.WebpiecesModule = new inversify_1.ContainerModule((options) => {
|
|
|
40
42
|
return new http_api_1.HeaderRegistry(extensions);
|
|
41
43
|
})
|
|
42
44
|
.inSingletonScope();
|
|
43
|
-
|
|
45
|
+
log.info(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);
|
|
44
46
|
});
|
|
45
47
|
//# sourceMappingURL=WebpiecesModule.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesModule.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/modules/WebpiecesModule.ts"],"names":[],"mappings":";;;AAAA,yCAA+D;AAC/D,kDAA4G;AAC5G,0EAAuE;
|
|
1
|
+
{"version":3,"file":"WebpiecesModule.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/modules/WebpiecesModule.ts"],"names":[],"mappings":";;;AAAA,yCAA+D;AAC/D,kDAA4G;AAC5G,0EAAuE;AACvE,sDAAmD;AAEnD,MAAM,GAAG,GAAG,uBAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;;;;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,uEAAuE;IACvE,2EAA2E;IAC3E,6EAA6E;IAC7E,IAAI,CAAiB,yBAAc,CAAC;SAC/B,cAAc,CAAC,CAAC,GAAsB,EAAE,EAAE;QACvC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAA2B,uBAAY,CAAC,wBAAwB,CAAC,CAAC;QAC/F,OAAO,IAAI,yBAAc,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC,CAAC;SACD,gBAAgB,EAAE,CAAC;IAExB,GAAG,CAAC,IAAI,CAAC,qEAAqE,aAAa,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;AAC1H,CAAC,CAAC,CAAC","sourcesContent":["import { ContainerModule, ResolutionContext } from 'inversify';\nimport { HEADER_TYPES, PlatformHeadersExtension, HeaderMethods, HeaderRegistry } from '@webpieces/http-api';\nimport { WebpiecesCoreHeaders } from '../headers/WebpiecesCoreHeaders';\nimport { LogManager } from '@webpieces/wp-logging';\n\nconst log = LogManager.getLogger('WebpiecesModule');\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 // HeaderRegistry: the single source of truth for all platform headers.\n // Collects EVERY module's PlatformHeadersExtension at first resolution and\n // fail-fast validates duplicates/conflicts (port of Java HeaderTranslation).\n bind<HeaderRegistry>(HeaderRegistry)\n .toDynamicValue((ctx: ResolutionContext) => {\n const extensions = ctx.getAll<PlatformHeadersExtension>(HEADER_TYPES.PlatformHeadersExtension);\n return new HeaderRegistry(extensions);\n })\n .inSingletonScope();\n\n log.info(`[WebpiecesModule] Registered core platform headers extension with ${coreExtension.headers.length} headers`);\n});\n"]}
|
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
import { RecordedEndpoint, RecordedTestCase, TestCaseRecorder } from '@webpieces/http-api';
|
|
2
|
-
/**
|
|
3
|
-
* TestCaseRecorderImpl - Server-side recorder (port of Java
|
|
4
|
-
* TestCaseRecorderImpl, minus the fragile bean-reflection codegen).
|
|
5
|
-
*
|
|
6
|
-
* One instance per recorded request (created by RecordingFilter and placed in
|
|
7
|
-
* the RequestContext under RecorderKeys.RECORDER). Downstream hooks - the
|
|
8
|
-
* http-client proxy and recordable() wrappers - add every call they make.
|
|
9
|
-
*
|
|
10
|
-
* spitOutTestCase() emits:
|
|
11
|
-
* - a diffable JSON FIXTURE (the stable artifact: request, ctx snapshot,
|
|
12
|
-
* response, all downstream calls) - also perfect input for an AI to write
|
|
13
|
-
* a richer spec from
|
|
14
|
-
* - a small deterministic .spec.ts from SpecGenerator
|
|
15
|
-
* Both are always logged; written to recordingDir when configured.
|
|
16
|
-
* NEVER breaks production - the whole body is caught and logged.
|
|
17
|
-
*/
|
|
18
2
|
export declare class TestCaseRecorderImpl implements TestCaseRecorder {
|
|
19
3
|
private downstreamCalls;
|
|
20
4
|
private serializer;
|
|
@@ -7,6 +7,7 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
7
7
|
const http_api_1 = require("@webpieces/http-api");
|
|
8
8
|
const core_util_1 = require("@webpieces/core-util");
|
|
9
9
|
const SpecGenerator_1 = require("./SpecGenerator");
|
|
10
|
+
const wp_logging_1 = require("@webpieces/wp-logging");
|
|
10
11
|
/**
|
|
11
12
|
* TestCaseRecorderImpl - Server-side recorder (port of Java
|
|
12
13
|
* TestCaseRecorderImpl, minus the fragile bean-reflection codegen).
|
|
@@ -23,6 +24,7 @@ const SpecGenerator_1 = require("./SpecGenerator");
|
|
|
23
24
|
* Both are always logged; written to recordingDir when configured.
|
|
24
25
|
* NEVER breaks production - the whole body is caught and logged.
|
|
25
26
|
*/
|
|
27
|
+
const log = wp_logging_1.LogManager.getLogger('TestCaseRecorder');
|
|
26
28
|
class TestCaseRecorderImpl {
|
|
27
29
|
downstreamCalls = [];
|
|
28
30
|
serializer = new http_api_1.RecordSerializer();
|
|
@@ -47,7 +49,7 @@ class TestCaseRecorderImpl {
|
|
|
47
49
|
const fixtureJson = this.serializer.serialize(testCase);
|
|
48
50
|
const baseName = this.buildBaseName(serverEndpoint, testCase.recordedAt);
|
|
49
51
|
const specSource = this.specGenerator.generate(testCase, `${baseName}.fixture.json`);
|
|
50
|
-
|
|
52
|
+
log.info(`[TestCaseRecorder] Recorded ${serverEndpoint.apiName}.${serverEndpoint.methodName} ` +
|
|
51
53
|
`(${this.downstreamCalls.length} downstream calls)\n` +
|
|
52
54
|
`--- fixture (${baseName}.fixture.json) ---\n${fixtureJson}\n` +
|
|
53
55
|
`--- generated spec (${baseName}.spec.ts) ---\n${specSource}`);
|
|
@@ -55,13 +57,13 @@ class TestCaseRecorderImpl {
|
|
|
55
57
|
fs.mkdirSync(recordingDir, { recursive: true });
|
|
56
58
|
fs.writeFileSync(path.join(recordingDir, `${baseName}.fixture.json`), fixtureJson);
|
|
57
59
|
fs.writeFileSync(path.join(recordingDir, `${baseName}.spec.ts`), specSource);
|
|
58
|
-
|
|
60
|
+
log.info(`[TestCaseRecorder] Wrote fixture + spec to ${recordingDir}/${baseName}.*`);
|
|
59
61
|
}
|
|
60
62
|
return testCase;
|
|
61
63
|
}
|
|
62
64
|
catch (err) {
|
|
63
65
|
const error = (0, core_util_1.toError)(err);
|
|
64
|
-
|
|
66
|
+
log.error('[TestCaseRecorder] Failed to emit test case (request unaffected):', error.message);
|
|
65
67
|
return undefined;
|
|
66
68
|
}
|
|
67
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestCaseRecorderImpl.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/TestCaseRecorderImpl.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,kDAK6B;AAC7B,oDAA+C;AAC/C,mDAAgD;
|
|
1
|
+
{"version":3,"file":"TestCaseRecorderImpl.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/recorder/TestCaseRecorderImpl.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAC7B,kDAK6B;AAC7B,oDAA+C;AAC/C,mDAAgD;AAChD,sDAAmD;AAEnD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,GAAG,GAAG,uBAAU,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;AAErD,MAAa,oBAAoB;IACrB,eAAe,GAAuB,EAAE,CAAC;IACzC,UAAU,GAAG,IAAI,2BAAgB,EAAE,CAAC;IACpC,aAAa,GAAG,IAAI,6BAAa,EAAE,CAAC;IAE5C,eAAe,CAAC,IAAsB;QAClC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,mBAAmB;QACf,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC;IAED;;;;;;OAMG;IACH,eAAe,CAAC,cAAgC,EAAE,YAAqB;QACnE,gHAAgH;QAChH,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,2BAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACtG,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YACzE,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,QAAQ,eAAe,CAAC,CAAC;YAErF,GAAG,CAAC,IAAI,CAAC,+BAA+B,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,UAAU,GAAG;gBAC1F,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,sBAAsB;gBACrD,gBAAgB,QAAQ,uBAAuB,WAAW,IAAI;gBAC9D,uBAAuB,QAAQ,kBAAkB,UAAU,EAAE,CAAC,CAAC;YAEnE,IAAI,YAAY,EAAE,CAAC;gBACf,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,eAAe,CAAC,EAAE,WAAW,CAAC,CAAC;gBACnF,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC;gBAC7E,GAAG,CAAC,IAAI,CAAC,8CAA8C,YAAY,IAAI,QAAQ,IAAI,CAAC,CAAC;YACzF,CAAC;YAED,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,GAAG,CAAC,KAAK,CAAC,mEAAmE,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC9F,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;IAEO,aAAa,CAAC,cAAgC,EAAE,UAAkB;QACtE,oEAAoE;QACpE,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjE,OAAO,GAAG,cAAc,CAAC,OAAO,IAAI,cAAc,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC;IAC7E,CAAC;CACJ;AAtDD,oDAsDC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n RecordedEndpoint,\n RecordedTestCase,\n RecordSerializer,\n TestCaseRecorder,\n} from '@webpieces/http-api';\nimport { toError } from '@webpieces/core-util';\nimport { SpecGenerator } from './SpecGenerator';\nimport { LogManager } from '@webpieces/wp-logging';\n\n/**\n * TestCaseRecorderImpl - Server-side recorder (port of Java\n * TestCaseRecorderImpl, minus the fragile bean-reflection codegen).\n *\n * One instance per recorded request (created by RecordingFilter and placed in\n * the RequestContext under RecorderKeys.RECORDER). Downstream hooks - the\n * http-client proxy and recordable() wrappers - add every call they make.\n *\n * spitOutTestCase() emits:\n * - a diffable JSON FIXTURE (the stable artifact: request, ctx snapshot,\n * response, all downstream calls) - also perfect input for an AI to write\n * a richer spec from\n * - a small deterministic .spec.ts from SpecGenerator\n * Both are always logged; written to recordingDir when configured.\n * NEVER breaks production - the whole body is caught and logged.\n */\nconst log = LogManager.getLogger('TestCaseRecorder');\n\nexport class TestCaseRecorderImpl implements TestCaseRecorder {\n private downstreamCalls: RecordedEndpoint[] = [];\n private serializer = new RecordSerializer();\n private specGenerator = new SpecGenerator();\n\n addEndpointInfo(info: RecordedEndpoint): void {\n this.downstreamCalls.push(info);\n }\n\n getLastEndpointInfo(): RecordedEndpoint | undefined {\n return this.downstreamCalls[this.downstreamCalls.length - 1];\n }\n\n /**\n * Emit the recorded test case. Called by RecordingFilter in its finally.\n *\n * @param serverEndpoint - The inbound endpoint capture (request + response)\n * @param recordingDir - Directory to write fixture + spec files (optional)\n * @returns The built RecordedTestCase, or undefined if emission failed\n */\n spitOutTestCase(serverEndpoint: RecordedEndpoint, recordingDir?: string): RecordedTestCase | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- recording must NEVER break production requests\n try {\n const testCase = new RecordedTestCase(serverEndpoint, this.downstreamCalls, new Date().toISOString());\n const fixtureJson = this.serializer.serialize(testCase);\n\n const baseName = this.buildBaseName(serverEndpoint, testCase.recordedAt);\n const specSource = this.specGenerator.generate(testCase, `${baseName}.fixture.json`);\n\n log.info(`[TestCaseRecorder] Recorded ${serverEndpoint.apiName}.${serverEndpoint.methodName} ` +\n `(${this.downstreamCalls.length} downstream calls)\\n` +\n `--- fixture (${baseName}.fixture.json) ---\\n${fixtureJson}\\n` +\n `--- generated spec (${baseName}.spec.ts) ---\\n${specSource}`);\n\n if (recordingDir) {\n fs.mkdirSync(recordingDir, { recursive: true });\n fs.writeFileSync(path.join(recordingDir, `${baseName}.fixture.json`), fixtureJson);\n fs.writeFileSync(path.join(recordingDir, `${baseName}.spec.ts`), specSource);\n log.info(`[TestCaseRecorder] Wrote fixture + spec to ${recordingDir}/${baseName}.*`);\n }\n\n return testCase;\n } catch (err: unknown) {\n const error = toError(err);\n log.error('[TestCaseRecorder] Failed to emit test case (request unaffected):', error.message);\n return undefined;\n }\n }\n\n private buildBaseName(serverEndpoint: RecordedEndpoint, recordedAt: string): string {\n // 2026-07-04T10:22:33.123Z -> 2026-07-04T10-22-33 (filesystem-safe)\n const stamp = recordedAt.replace(/:/g, '-').replace(/\\..*$/, '');\n return `${serverEndpoint.apiName}.${serverEndpoint.methodName}.${stamp}`;\n }\n}\n"]}
|