@webpieces/http-server 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-server",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "WebPieces server with filter chain and dependency injection",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,9 +22,8 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-meta": "0.2.15",
26
- "@webpieces/core-util": "0.2.15",
27
- "@webpieces/http-routing": "0.2.15",
28
- "@webpieces/http-filters": "0.2.15"
25
+ "@webpieces/core-util": "0.2.17",
26
+ "@webpieces/http-routing": "0.2.17",
27
+ "@webpieces/http-filters": "0.2.17"
29
28
  }
30
29
  }
@@ -1,4 +1,5 @@
1
- import { WebAppMeta } from '@webpieces/core-meta';
1
+ import { ContainerModule } from 'inversify';
2
+ import { WebAppMeta } from '@webpieces/http-routing';
2
3
  import { WebpiecesServer } from './WebpiecesServer';
3
4
  /**
4
5
  * WebpiecesFactory - Factory for creating WebPieces server instances.
@@ -15,10 +16,16 @@ import { WebpiecesServer } from './WebpiecesServer';
15
16
  *
16
17
  * Usage:
17
18
  * ```typescript
19
+ * // Production
18
20
  * const server = WebpiecesFactory.create(new ProdServerMeta());
19
21
  * server.start(8080);
20
- * // ... later
21
- * server.stop();
22
+ *
23
+ * // Testing with overrides
24
+ * const overrides = new ContainerModule((bind) => {
25
+ * bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);
26
+ * });
27
+ * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
28
+ * const api = server.createApiClient(SaveApiPrototype);
22
29
  * ```
23
30
  *
24
31
  * This pattern:
@@ -36,9 +43,21 @@ export declare class WebpiecesFactory {
36
43
  * 2. Loads framework bindings via buildProviderModule()
37
44
  * 3. Resolves the server implementation from DI
38
45
  * 4. Initializes the server with the container and meta
46
+ * 5. Loads optional override module (for testing)
39
47
  *
40
48
  * @param meta - User-provided WebAppMeta with DI modules and routes
49
+ * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
41
50
  * @returns A fully initialized WebpiecesServer ready to start()
42
51
  */
43
- static create(meta: WebAppMeta): WebpiecesServer;
52
+ /**
53
+ * Create a new WebPieces server instance asynchronously.
54
+ *
55
+ * Use this method when you need async operations in your override modules
56
+ * (e.g., rebind() in new Inversify versions).
57
+ *
58
+ * @param meta - User-provided WebAppMeta with DI modules and routes
59
+ * @param overrides - Optional ContainerModule for test overrides (can use async operations)
60
+ * @returns Promise of a fully initialized WebpiecesServer ready to start()
61
+ */
62
+ static create(meta: WebAppMeta, overrides?: ContainerModule, testMode?: boolean): Promise<WebpiecesServer>;
44
63
  }
@@ -19,10 +19,16 @@ const WebpiecesServerImpl_1 = require("./WebpiecesServerImpl");
19
19
  *
20
20
  * Usage:
21
21
  * ```typescript
22
+ * // Production
22
23
  * const server = WebpiecesFactory.create(new ProdServerMeta());
23
24
  * server.start(8080);
24
- * // ... later
25
- * server.stop();
25
+ *
26
+ * // Testing with overrides
27
+ * const overrides = new ContainerModule((bind) => {
28
+ * bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);
29
+ * });
30
+ * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
31
+ * const api = server.createApiClient(SaveApiPrototype);
26
32
  * ```
27
33
  *
28
34
  * This pattern:
@@ -40,20 +46,31 @@ class WebpiecesFactory {
40
46
  * 2. Loads framework bindings via buildProviderModule()
41
47
  * 3. Resolves the server implementation from DI
42
48
  * 4. Initializes the server with the container and meta
49
+ * 5. Loads optional override module (for testing)
43
50
  *
44
51
  * @param meta - User-provided WebAppMeta with DI modules and routes
52
+ * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
45
53
  * @returns A fully initialized WebpiecesServer ready to start()
46
54
  */
47
- static create(meta) {
55
+ /**
56
+ * Create a new WebPieces server instance asynchronously.
57
+ *
58
+ * Use this method when you need async operations in your override modules
59
+ * (e.g., rebind() in new Inversify versions).
60
+ *
61
+ * @param meta - User-provided WebAppMeta with DI modules and routes
62
+ * @param overrides - Optional ContainerModule for test overrides (can use async operations)
63
+ * @returns Promise of a fully initialized WebpiecesServer ready to start()
64
+ */
65
+ static async create(meta, overrides, testMode) {
48
66
  // Create WebPieces container for framework-level bindings
49
67
  const webpiecesContainer = new inversify_1.Container();
50
68
  // Load buildProviderModule to auto-scan for @provideSingleton decorators
51
- // This registers framework classes (WebpiecesServerImpl, RouteBuilderImpl)
52
- webpiecesContainer.load((0, binding_decorators_1.buildProviderModule)());
53
- // Resolve WebpiecesServerImpl from DI container (proper DI - no 'new'!)
69
+ await webpiecesContainer.load((0, binding_decorators_1.buildProviderModule)());
70
+ // Resolve WebpiecesServerImpl from DI container
54
71
  const serverImpl = webpiecesContainer.get(WebpiecesServerImpl_1.WebpiecesServerImpl);
55
- // Initialize the server (loads app DI modules, registers routes)
56
- serverImpl.initialize(webpiecesContainer, meta);
72
+ // Initialize the server asynchronously (loads app DI modules, registers routes)
73
+ await serverImpl.initialize(webpiecesContainer, meta, overrides);
57
74
  // Return as interface to hide initialize() from consumers
58
75
  return serverImpl;
59
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesFactory.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AACtC,wEAAsE;AAGtE,+DAA4D;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAa,gBAAgB;IACzB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,MAAM,CAAC,IAAgB;QAC1B,0DAA0D;QAC1D,MAAM,kBAAkB,GAAG,IAAI,qBAAS,EAAE,CAAC;QAE3C,yEAAyE;QACzE,2EAA2E;QAC3E,kBAAkB,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAE/C,wEAAwE;QACxE,MAAM,UAAU,GAAG,kBAAkB,CAAC,GAAG,CAAC,yCAAmB,CAAC,CAAC;QAE/D,iEAAiE;QACjE,UAAU,CAAC,UAAU,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QAEhD,0DAA0D;QAC1D,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA9BD,4CA8BC","sourcesContent":["import { Container } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta } from '@webpieces/core-meta';\nimport { WebpiecesServer } from './WebpiecesServer';\nimport { WebpiecesServerImpl } from './WebpiecesServerImpl';\n\n/**\n * WebpiecesFactory - Factory for creating WebPieces server instances.\n *\n * This factory encapsulates the server creation and initialization logic:\n * 1. Creates the WebPieces DI container\n * 2. Loads the provider module for @provideSingleton decorators\n * 3. Resolves WebpiecesServerImpl from DI\n * 4. Calls initialize() with the container and meta\n * 5. Returns the server as the WebpiecesServer interface\n *\n * The returned WebpiecesServer interface only exposes start() and stop(),\n * hiding the internal initialize() method from consumers.\n *\n * Usage:\n * ```typescript\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * server.start(8080);\n * // ... later\n * server.stop();\n * ```\n *\n * This pattern:\n * - Enforces proper initialization order\n * - Hides implementation details from consumers\n * - Makes the API simpler and harder to misuse\n * - Follows the principle of least privilege\n */\nexport class WebpiecesFactory {\n /**\n * Create a new WebPieces server instance.\n *\n * This method:\n * 1. Creates the WebPieces framework DI container\n * 2. Loads framework bindings via buildProviderModule()\n * 3. Resolves the server implementation from DI\n * 4. Initializes the server with the container and meta\n *\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @returns A fully initialized WebpiecesServer ready to start()\n */\n static create(meta: WebAppMeta): WebpiecesServer {\n // Create WebPieces container for framework-level bindings\n const webpiecesContainer = new Container();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n // This registers framework classes (WebpiecesServerImpl, RouteBuilderImpl)\n webpiecesContainer.load(buildProviderModule());\n\n // Resolve WebpiecesServerImpl from DI container (proper DI - no 'new'!)\n const serverImpl = webpiecesContainer.get(WebpiecesServerImpl);\n\n // Initialize the server (loads app DI modules, registers routes)\n serverImpl.initialize(webpiecesContainer, meta);\n\n // Return as interface to hide initialize() from consumers\n return serverImpl;\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesFactory.ts"],"names":[],"mappings":";;;AAAA,yCAAuD;AACvD,wEAAsE;AAGtE,+DAA4D;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAa,gBAAgB;IACzB;;;;;;;;;;;;;OAaG;IAEH;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CACf,IAAgB,EAChB,SAA2B,EAC3B,QAAkB;QAElB,0DAA0D;QAC1D,MAAM,kBAAkB,GAAG,IAAI,qBAAS,EAAE,CAAC;QAE3C,yEAAyE;QACzE,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAErD,gDAAgD;QAChD,MAAM,UAAU,GAAG,kBAAkB,CAAC,GAAG,CAAC,yCAAmB,CAAC,CAAC;QAE/D,gFAAgF;QAChF,MAAM,UAAU,CAAC,UAAU,CAAC,kBAAkB,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAEjE,0DAA0D;QAC1D,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA9CD,4CA8CC","sourcesContent":["import { Container, ContainerModule } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta } from '@webpieces/http-routing';\nimport { WebpiecesServer } from './WebpiecesServer';\nimport { WebpiecesServerImpl } from './WebpiecesServerImpl';\n\n/**\n * WebpiecesFactory - Factory for creating WebPieces server instances.\n *\n * This factory encapsulates the server creation and initialization logic:\n * 1. Creates the WebPieces DI container\n * 2. Loads the provider module for @provideSingleton decorators\n * 3. Resolves WebpiecesServerImpl from DI\n * 4. Calls initialize() with the container and meta\n * 5. Returns the server as the WebpiecesServer interface\n *\n * The returned WebpiecesServer interface only exposes start() and stop(),\n * hiding the internal initialize() method from consumers.\n *\n * Usage:\n * ```typescript\n * // Production\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * server.start(8080);\n *\n * // Testing with overrides\n * const overrides = new ContainerModule((bind) => {\n * bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);\n * });\n * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);\n * const api = server.createApiClient(SaveApiPrototype);\n * ```\n *\n * This pattern:\n * - Enforces proper initialization order\n * - Hides implementation details from consumers\n * - Makes the API simpler and harder to misuse\n * - Follows the principle of least privilege\n */\nexport class WebpiecesFactory {\n /**\n * Create a new WebPieces server instance.\n *\n * This method:\n * 1. Creates the WebPieces framework DI container\n * 2. Loads framework bindings via buildProviderModule()\n * 3. Resolves the server implementation from DI\n * 4. Initializes the server with the container and meta\n * 5. Loads optional override module (for testing)\n *\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)\n * @returns A fully initialized WebpiecesServer ready to start()\n */\n\n /**\n * Create a new WebPieces server instance asynchronously.\n *\n * Use this method when you need async operations in your override modules\n * (e.g., rebind() in new Inversify versions).\n *\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (can use async operations)\n * @returns Promise of a fully initialized WebpiecesServer ready to start()\n */\n static async create(\n meta: WebAppMeta,\n overrides?: ContainerModule,\n testMode?: boolean\n ): Promise<WebpiecesServer> {\n // Create WebPieces container for framework-level bindings\n const webpiecesContainer = new Container();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n await webpiecesContainer.load(buildProviderModule());\n\n // Resolve WebpiecesServerImpl from DI container\n const serverImpl = webpiecesContainer.get(WebpiecesServerImpl);\n\n // Initialize the server asynchronously (loads app DI modules, registers routes)\n await serverImpl.initialize(webpiecesContainer, meta, overrides);\n\n // Return as interface to hide initialize() from consumers\n return serverImpl;\n }\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { MethodMeta } from '@webpieces/http-routing';
3
+ import { RouteMetadata } from '@webpieces/http-api';
4
+ import { Service, WpResponse } from '@webpieces/http-filters';
5
+ export declare class ExpressWrapper {
6
+ private service;
7
+ private routeMeta;
8
+ constructor(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata);
9
+ execute(req: Request, res: Response, next: NextFunction): Promise<void>;
10
+ /**
11
+ * Read raw request body as text.
12
+ * Used to manually parse JSON (instead of express.json() middleware).
13
+ */
14
+ private readRequestBody;
15
+ /**
16
+ * Handle errors - translate to JSON ProtocolError (SYMMETRIC with ClientErrorTranslator).
17
+ * PUBLIC so wrapExpress can call it for symmetric error handling.
18
+ * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
19
+ *
20
+ * Maps all HttpError types (must match ClientErrorTranslator.translateError()):
21
+ * - HttpUserError → 266 (with errorCode)
22
+ * - HttpBadRequestError → 400 (with field, guiAlertMessage)
23
+ * - HttpUnauthorizedError → 401
24
+ * - HttpForbiddenError → 403
25
+ * - HttpNotFoundError → 404
26
+ * - HttpTimeoutError → 408
27
+ * - HttpInternalServerError → 500
28
+ * - HttpBadGatewayError → 502
29
+ * - HttpGatewayTimeoutError → 504
30
+ * - HttpVendorError → 598 (with waitSeconds)
31
+ */
32
+ handleError(res: Response, error: unknown): void;
33
+ }
34
+ /**
35
+ * WebpiecesMiddleware - Express middleware for WebPieces server.
36
+ *
37
+ * This class contains all Express middleware used by WebpiecesServer:
38
+ * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
39
+ * 2. logNextLayer - Request/response logging
40
+ * 3. jsonTranslator - JSON Content-Type validation and error translation
41
+ *
42
+ * The middleware is injected into WebpiecesServerImpl and registered with Express
43
+ * in the start() method.
44
+ *
45
+ * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
46
+ * Express's registered route handlers (created by RouteBuilder.createHandler()).
47
+ * jsonTranslator only validates Content-Type and translates errors to JSON.
48
+ *
49
+ * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
50
+ */
51
+ export declare class WebpiecesMiddleware {
52
+ /**
53
+ * Global error handler middleware - catches ALL unhandled errors.
54
+ * Returns HTML 500 error page for any errors that escape the filter chain.
55
+ *
56
+ * This is the outermost safety net - JsonTranslator catches JSON API errors,
57
+ * this catches everything else.
58
+ */
59
+ globalErrorHandler(req: Request, res: Response, next: NextFunction): Promise<void>;
60
+ /**
61
+ * Logging middleware - logs request/response flow.
62
+ * Demonstrates middleware execution order.
63
+ * IMPORTANT: Must be async and await next() to properly chain with async middleware.
64
+ */
65
+ logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void>;
66
+ /**
67
+ * Create an ExpressWrapper for a route.
68
+ * The wrapper handles the full request/response cycle (symmetric design).
69
+ *
70
+ * @param service - The service wrapping the filter chain and controller
71
+ * @param routeMeta - Route metadata for MethodMeta and DTO type
72
+ * @returns ExpressWrapper instance
73
+ */
74
+ createExpressWrapper(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata): ExpressWrapper;
75
+ }
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebpiecesMiddleware = exports.ExpressWrapper = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const http_routing_1 = require("@webpieces/http-routing");
7
+ const http_api_1 = require("@webpieces/http-api");
8
+ const core_util_1 = require("@webpieces/core-util");
9
+ class ExpressWrapper {
10
+ constructor(service, routeMeta) {
11
+ this.service = service;
12
+ this.routeMeta = routeMeta;
13
+ }
14
+ async execute(req, res, next) {
15
+ try {
16
+ // 1. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)
17
+ let requestDto = {};
18
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
19
+ // Read raw body as text
20
+ const bodyText = await this.readRequestBody(req);
21
+ // Parse JSON
22
+ requestDto = bodyText ? JSON.parse(bodyText) : {};
23
+ }
24
+ // 2. Create MethodMeta with request DTO
25
+ const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestDto);
26
+ // 3. Invoke the service (filter chain + controller)
27
+ const wpResponse = await this.service.invoke(methodMeta);
28
+ if (!wpResponse.response)
29
+ throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);
30
+ // 4. Serialize response DTO to JSON (SYMMETRIC with client's response.json())
31
+ const responseJson = JSON.stringify(wpResponse.response);
32
+ res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);
33
+ }
34
+ catch (err) {
35
+ // 5. Handle errors
36
+ this.handleError(res, err);
37
+ }
38
+ }
39
+ /**
40
+ * Read raw request body as text.
41
+ * Used to manually parse JSON (instead of express.json() middleware).
42
+ */
43
+ async readRequestBody(req) {
44
+ return new Promise((resolve, reject) => {
45
+ let body = '';
46
+ req.on('data', (chunk) => {
47
+ body += chunk.toString();
48
+ });
49
+ req.on('end', () => {
50
+ resolve(body);
51
+ });
52
+ req.on('error', (err) => {
53
+ reject(err);
54
+ });
55
+ });
56
+ }
57
+ /**
58
+ * Handle errors - translate to JSON ProtocolError (SYMMETRIC with ClientErrorTranslator).
59
+ * PUBLIC so wrapExpress can call it for symmetric error handling.
60
+ * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
61
+ *
62
+ * Maps all HttpError types (must match ClientErrorTranslator.translateError()):
63
+ * - HttpUserError → 266 (with errorCode)
64
+ * - HttpBadRequestError → 400 (with field, guiAlertMessage)
65
+ * - HttpUnauthorizedError → 401
66
+ * - HttpForbiddenError → 403
67
+ * - HttpNotFoundError → 404
68
+ * - HttpTimeoutError → 408
69
+ * - HttpInternalServerError → 500
70
+ * - HttpBadGatewayError → 502
71
+ * - HttpGatewayTimeoutError → 504
72
+ * - HttpVendorError → 598 (with waitSeconds)
73
+ */
74
+ handleError(res, error) {
75
+ if (res.headersSent) {
76
+ return;
77
+ }
78
+ const protocolError = new http_api_1.ProtocolError();
79
+ if (error instanceof http_api_1.HttpError) {
80
+ // Set common fields for all HttpError types
81
+ protocolError.message = error.message;
82
+ protocolError.subType = error.subType;
83
+ protocolError.name = error.name;
84
+ // Set type-specific fields (MUST match ClientErrorTranslator)
85
+ if (error instanceof http_api_1.HttpUserError) {
86
+ console.log('[ExpressWrapper] User Error:', error.message);
87
+ protocolError.errorCode = error.errorCode;
88
+ }
89
+ else if (error instanceof http_api_1.HttpBadRequestError) {
90
+ console.log('[ExpressWrapper] Bad Request:', error.message);
91
+ protocolError.field = error.field;
92
+ protocolError.guiAlertMessage = error.guiMessage;
93
+ }
94
+ else if (error instanceof http_api_1.HttpNotFoundError) {
95
+ console.log('[ExpressWrapper] Not Found:', error.message);
96
+ }
97
+ else if (error instanceof http_api_1.HttpTimeoutError) {
98
+ console.error('[ExpressWrapper] Timeout Error:', error.message);
99
+ }
100
+ else if (error instanceof http_api_1.HttpVendorError) {
101
+ console.error('[ExpressWrapper] Vendor Error:', error.message);
102
+ protocolError.waitSeconds = error.waitSeconds;
103
+ }
104
+ else if (error instanceof http_api_1.HttpUnauthorizedError) {
105
+ console.log('[ExpressWrapper] Unauthorized:', error.message);
106
+ }
107
+ else if (error instanceof http_api_1.HttpForbiddenError) {
108
+ console.log('[ExpressWrapper] Forbidden:', error.message);
109
+ }
110
+ else if (error instanceof http_api_1.HttpInternalServerError) {
111
+ console.error('[ExpressWrapper] Internal Server Error:', error.message);
112
+ }
113
+ else if (error instanceof http_api_1.HttpBadGatewayError) {
114
+ console.error('[ExpressWrapper] Bad Gateway:', error.message);
115
+ }
116
+ else if (error instanceof http_api_1.HttpGatewayTimeoutError) {
117
+ console.error('[ExpressWrapper] Gateway Timeout:', error.message);
118
+ }
119
+ else {
120
+ console.log('[ExpressWrapper] Generic HttpError:', error.message);
121
+ }
122
+ // Serialize ProtocolError to JSON (SYMMETRIC with client)
123
+ const responseJson = JSON.stringify(protocolError);
124
+ res.status(error.code).setHeader('Content-Type', 'application/json').send(responseJson);
125
+ }
126
+ else {
127
+ // Unknown error - 500
128
+ const err = (0, core_util_1.toError)(error);
129
+ protocolError.message = 'Internal Server Error';
130
+ console.error('[ExpressWrapper] Unexpected error:', err);
131
+ const responseJson = JSON.stringify(protocolError);
132
+ res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);
133
+ }
134
+ }
135
+ }
136
+ exports.ExpressWrapper = ExpressWrapper;
137
+ /**
138
+ * WebpiecesMiddleware - Express middleware for WebPieces server.
139
+ *
140
+ * This class contains all Express middleware used by WebpiecesServer:
141
+ * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
142
+ * 2. logNextLayer - Request/response logging
143
+ * 3. jsonTranslator - JSON Content-Type validation and error translation
144
+ *
145
+ * The middleware is injected into WebpiecesServerImpl and registered with Express
146
+ * in the start() method.
147
+ *
148
+ * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
149
+ * Express's registered route handlers (created by RouteBuilder.createHandler()).
150
+ * jsonTranslator only validates Content-Type and translates errors to JSON.
151
+ *
152
+ * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
153
+ */
154
+ let WebpiecesMiddleware = class WebpiecesMiddleware {
155
+ /**
156
+ * Global error handler middleware - catches ALL unhandled errors.
157
+ * Returns HTML 500 error page for any errors that escape the filter chain.
158
+ *
159
+ * This is the outermost safety net - JsonTranslator catches JSON API errors,
160
+ * this catches everything else.
161
+ */
162
+ async globalErrorHandler(req, res, next) {
163
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
164
+ try {
165
+ // await next() catches BOTH:
166
+ // 1. Synchronous throws from next() itself
167
+ // 2. Rejected promises from downstream async middleware
168
+ await next();
169
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
170
+ }
171
+ catch (err) {
172
+ const error = (0, core_util_1.toError)(err);
173
+ console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
174
+ if (!res.headersSent) {
175
+ // Return HTML error page (not JSON - JsonTranslator handles JSON errors)
176
+ res.status(500).send(`
177
+ <!DOCTYPE html>
178
+ <html>
179
+ <head><title>Server Error</title></head>
180
+ <body>
181
+ <h1>You hit a server error</h1>
182
+ <p>An unexpected error occurred while processing your request.</p>
183
+ <pre>${error.message}</pre>
184
+ </body>
185
+ </html>
186
+ `);
187
+ }
188
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
189
+ }
190
+ }
191
+ /**
192
+ * Logging middleware - logs request/response flow.
193
+ * Demonstrates middleware execution order.
194
+ * IMPORTANT: Must be async and await next() to properly chain with async middleware.
195
+ */
196
+ async logNextLayer(req, res, next) {
197
+ console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
198
+ await next();
199
+ console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
200
+ }
201
+ /**
202
+ * Create an ExpressWrapper for a route.
203
+ * The wrapper handles the full request/response cycle (symmetric design).
204
+ *
205
+ * @param service - The service wrapping the filter chain and controller
206
+ * @param routeMeta - Route metadata for MethodMeta and DTO type
207
+ * @returns ExpressWrapper instance
208
+ */
209
+ createExpressWrapper(service, routeMeta) {
210
+ return new ExpressWrapper(service, routeMeta);
211
+ }
212
+ };
213
+ exports.WebpiecesMiddleware = WebpiecesMiddleware;
214
+ exports.WebpiecesMiddleware = WebpiecesMiddleware = tslib_1.__decorate([
215
+ (0, http_routing_1.provideSingleton)(),
216
+ (0, inversify_1.injectable)()
217
+ ], WebpiecesMiddleware);
218
+ //# sourceMappingURL=WebpiecesMiddleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta, ExpressRouteHandler } from '@webpieces/http-routing';\nimport {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpVendorError,\n HttpUserError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n RouteMetadata,\n} from '@webpieces/http-api';\nimport { Service, WpResponse } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\n\nexport class ExpressWrapper {\n constructor(\n private service: Service<MethodMeta, WpResponse<unknown>>,\n private routeMeta: RouteMetadata\n ) {\n }\n\n public async execute(req: Request, res: Response, next: NextFunction) {\n try {\n // 1. Parse JSON request body manually (SYMMETRIC with client's JSON.stringify)\n let requestDto: unknown = {};\n if (['POST', 'PUT', 'PATCH'].includes(req.method)) {\n // Read raw body as text\n const bodyText = await this.readRequestBody(req);\n // Parse JSON\n requestDto = bodyText ? JSON.parse(bodyText) : {};\n }\n\n // 2. Create MethodMeta with request DTO\n const methodMeta = new MethodMeta(this.routeMeta, requestDto);\n\n // 3. Invoke the service (filter chain + controller)\n const wpResponse = await this.service.invoke(methodMeta);\n if(!wpResponse.response)\n throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);\n\n // 4. Serialize response DTO to JSON (SYMMETRIC with client's response.json())\n const responseJson = JSON.stringify(wpResponse.response);\n res.status(200).setHeader('Content-Type', 'application/json').send(responseJson);\n } catch (err: unknown) {\n // 5. Handle errors\n this.handleError(res, err);\n }\n }\n\n /**\n * Read raw request body as text.\n * Used to manually parse JSON (instead of express.json() middleware).\n */\n private async readRequestBody(req: Request): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n resolve(body);\n });\n req.on('error', (err) => {\n reject(err);\n });\n });\n }\n\n /**\n * Handle errors - translate to JSON ProtocolError (SYMMETRIC with ClientErrorTranslator).\n * PUBLIC so wrapExpress can call it for symmetric error handling.\n * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.\n *\n * Maps all HttpError types (must match ClientErrorTranslator.translateError()):\n * - HttpUserError → 266 (with errorCode)\n * - HttpBadRequestError → 400 (with field, guiAlertMessage)\n * - HttpUnauthorizedError → 401\n * - HttpForbiddenError → 403\n * - HttpNotFoundError → 404\n * - HttpTimeoutError → 408\n * - HttpInternalServerError → 500\n * - HttpBadGatewayError → 502\n * - HttpGatewayTimeoutError → 504\n * - HttpVendorError → 598 (with waitSeconds)\n */\n public handleError(res: Response, error: unknown): void {\n if (res.headersSent) {\n return;\n }\n\n const protocolError = new ProtocolError();\n\n if (error instanceof HttpError) {\n // Set common fields for all HttpError types\n protocolError.message = error.message;\n protocolError.subType = error.subType;\n protocolError.name = error.name;\n\n // Set type-specific fields (MUST match ClientErrorTranslator)\n if (error instanceof HttpUserError) {\n console.log('[ExpressWrapper] User Error:', error.message);\n protocolError.errorCode = error.errorCode;\n } else if (error instanceof HttpBadRequestError) {\n console.log('[ExpressWrapper] Bad Request:', error.message);\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n } else if (error instanceof HttpNotFoundError) {\n console.log('[ExpressWrapper] Not Found:', error.message);\n } else if (error instanceof HttpTimeoutError) {\n console.error('[ExpressWrapper] Timeout Error:', error.message);\n } else if (error instanceof HttpVendorError) {\n console.error('[ExpressWrapper] Vendor Error:', error.message);\n protocolError.waitSeconds = error.waitSeconds;\n } else if (error instanceof HttpUnauthorizedError) {\n console.log('[ExpressWrapper] Unauthorized:', error.message);\n } else if (error instanceof HttpForbiddenError) {\n console.log('[ExpressWrapper] Forbidden:', error.message);\n } else if (error instanceof HttpInternalServerError) {\n console.error('[ExpressWrapper] Internal Server Error:', error.message);\n } else if (error instanceof HttpBadGatewayError) {\n console.error('[ExpressWrapper] Bad Gateway:', error.message);\n } else if (error instanceof HttpGatewayTimeoutError) {\n console.error('[ExpressWrapper] Gateway Timeout:', error.message);\n } else {\n console.log('[ExpressWrapper] Generic HttpError:', error.message);\n }\n\n // Serialize ProtocolError to JSON (SYMMETRIC with client)\n const responseJson = JSON.stringify(protocolError);\n res.status(error.code).setHeader('Content-Type', 'application/json').send(responseJson);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n console.error('[ExpressWrapper] Unexpected error:', err);\n const responseJson = JSON.stringify(protocolError);\n res.status(500).setHeader('Content-Type', 'application/json').send(responseJson);\n }\n }\n}\n/**\n * WebpiecesMiddleware - Express middleware for WebPieces server.\n *\n * This class contains all Express middleware used by WebpiecesServer:\n * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page\n * 2. logNextLayer - Request/response logging\n * 3. jsonTranslator - JSON Content-Type validation and error translation\n *\n * The middleware is injected into WebpiecesServerImpl and registered with Express\n * in the start() method.\n *\n * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via\n * Express's registered route handlers (created by RouteBuilder.createHandler()).\n * jsonTranslator only validates Content-Type and translates errors to JSON.\n *\n * DI Pattern: This class is registered via @provideSingleton() with no dependencies.\n */\n@provideSingleton()\n@injectable()\nexport class WebpiecesMiddleware {\n\n /**\n * Global error handler middleware - catches ALL unhandled errors.\n * Returns HTML 500 error page for any errors that escape the filter chain.\n *\n * This is the outermost safety net - JsonTranslator catches JSON API errors,\n * this catches everything else.\n */\n async globalErrorHandler(\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> {\n console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);\n\n try {\n // await next() catches BOTH:\n // 1. Synchronous throws from next() itself\n // 2. Rejected promises from downstream async middleware\n await next();\n console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (success):',\n req.method,\n req.path,\n );\n } catch (err: unknown) {\n const error = toError(err);\n console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonTranslator handles JSON errors)\n res.status(500).send(`\n <!DOCTYPE html>\n <html>\n <head><title>Server Error</title></head>\n <body>\n <h1>You hit a server error</h1>\n <p>An unexpected error occurred while processing your request.</p>\n <pre>${error.message}</pre>\n </body>\n </html>\n `);\n }\n console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (error):',\n req.method,\n req.path,\n );\n }\n }\n\n /**\n * Logging middleware - logs request/response flow.\n * Demonstrates middleware execution order.\n * IMPORTANT: Must be async and await next() to properly chain with async middleware.\n */\n async logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void> {\n console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n await next();\n console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\n *\n * @param service - The service wrapping the filter chain and controller\n * @param routeMeta - Route metadata for MethodMeta and DTO type\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n service: Service<MethodMeta, WpResponse<unknown>>,\n routeMeta: RouteMetadata,\n ): ExpressWrapper {\n return new ExpressWrapper(service, routeMeta);\n }\n}\n"]}
@@ -1,20 +1,26 @@
1
+ import { Container } from 'inversify';
1
2
  /**
2
3
  * WebpiecesServer - Public interface for WebPieces server.
3
4
  *
4
- * This interface exposes only the methods needed by application code:
5
+ * This interface exposes the methods needed by application code:
5
6
  * - start(): Start the HTTP server
6
7
  * - stop(): Stop the HTTP server
8
+ * - createApiClient(): Create API client proxy for testing (no HTTP!)
9
+ * - getContainer(): Access DI container for verification
7
10
  *
8
11
  * The initialization logic is hidden inside WebpiecesFactory.create().
9
12
  * This provides a clean API and prevents accidental re-initialization.
10
13
  *
11
14
  * Usage:
12
15
  * ```typescript
16
+ * // Production
13
17
  * const server = WebpiecesFactory.create(new ProdServerMeta());
14
18
  * await server.start(8080);
15
- * console.log('Server is now listening!');
16
- * // ... later
17
- * server.stop();
19
+ *
20
+ * // Testing (no HTTP needed!)
21
+ * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
22
+ * const api = server.createApiClient(SaveApiPrototype);
23
+ * const response = await api.save(request);
18
24
  * ```
19
25
  */
20
26
  export interface WebpiecesServer {
@@ -29,6 +35,37 @@ export interface WebpiecesServer {
29
35
  start(port?: number): Promise<void>;
30
36
  /**
31
37
  * Stop the HTTP server.
38
+ * Returns a Promise that resolves when the server is stopped,
39
+ * or rejects if there's an error stopping the server.
40
+ *
41
+ * @returns Promise that resolves when server is stopped
42
+ */
43
+ stop(): Promise<void>;
44
+ /**
45
+ * Create an API client proxy for testing.
46
+ *
47
+ * This creates a client that routes calls through the full filter chain
48
+ * and controller, but WITHOUT any HTTP overhead. Perfect for testing!
49
+ *
50
+ * The client uses the ApiPrototype class to discover routes via decorators,
51
+ * then invokes the corresponding controller method through the filter chain.
52
+ *
53
+ * @param apiPrototype - The API prototype class with routing decorators (can be abstract)
54
+ * @returns A proxy that implements the API interface
55
+ *
56
+ * Example:
57
+ * ```typescript
58
+ * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
59
+ * const response = await saveApi.save(request);
60
+ * ```
61
+ */
62
+ createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
63
+ /**
64
+ * Get the application DI container.
65
+ *
66
+ * Useful for testing to verify state or access services directly.
67
+ *
68
+ * @returns The application Container
32
69
  */
33
- stop(): void;
70
+ getContainer(): Container;
34
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * WebpiecesServer - Public interface for WebPieces server.\n *\n * This interface exposes only the methods needed by application code:\n * - start(): Start the HTTP server\n * - stop(): Stop the HTTP server\n *\n * The initialization logic is hidden inside WebpiecesFactory.create().\n * This provides a clean API and prevents accidental re-initialization.\n *\n * Usage:\n * ```typescript\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * await server.start(8080);\n * console.log('Server is now listening!');\n * // ... later\n * server.stop();\n * ```\n */\nexport interface WebpiecesServer {\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 start(port?: number): Promise<void>;\n\n /**\n * Stop the HTTP server.\n */\n stop(): void;\n}\n"]}
1
+ {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"","sourcesContent":["import { Container } from 'inversify';\n\n/**\n * WebpiecesServer - Public interface for WebPieces server.\n *\n * This interface exposes the methods needed by application code:\n * - start(): Start the HTTP server\n * - stop(): Stop the HTTP server\n * - createApiClient(): Create API client proxy for testing (no HTTP!)\n * - getContainer(): Access DI container for verification\n *\n * The initialization logic is hidden inside WebpiecesFactory.create().\n * This provides a clean API and prevents accidental re-initialization.\n *\n * Usage:\n * ```typescript\n * // Production\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * await server.start(8080);\n *\n * // Testing (no HTTP needed!)\n * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);\n * const api = server.createApiClient(SaveApiPrototype);\n * const response = await api.save(request);\n * ```\n */\nexport interface WebpiecesServer {\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 start(port?: number): Promise<void>;\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 stop(): Promise<void>;\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 invokes the corresponding controller method through the filter chain.\n *\n * @param apiPrototype - The API prototype class with routing decorators (can be abstract)\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n */\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;\n\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}\n"]}