@webpieces/http-server 0.2.14 → 0.2.16

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.14",
3
+ "version": "0.2.16",
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.14",
26
- "@webpieces/core-util": "0.2.14",
27
- "@webpieces/http-routing": "0.2.14",
28
- "@webpieces/http-filters": "0.2.14"
25
+ "@webpieces/core-util": "0.2.16",
26
+ "@webpieces/http-routing": "0.2.16",
27
+ "@webpieces/http-filters": "0.2.16"
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;IAC3B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,MAAM,CAAC,IAAgB;QAC5B,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;IACpB,CAAC;CACF;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,59 @@
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
+ private jsonSerializer;
9
+ constructor(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata);
10
+ execute(req: Request, res: Response, next: NextFunction): Promise<void>;
11
+ /**
12
+ * Handle errors - translate to JSON ProtocolError.
13
+ * PUBLIC so wrapExpress can call it for symmetric error handling.
14
+ * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
15
+ */
16
+ handleError(res: Response, error: unknown): void;
17
+ }
18
+ /**
19
+ * WebpiecesMiddleware - Express middleware for WebPieces server.
20
+ *
21
+ * This class contains all Express middleware used by WebpiecesServer:
22
+ * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
23
+ * 2. logNextLayer - Request/response logging
24
+ * 3. jsonTranslator - JSON Content-Type validation and error translation
25
+ *
26
+ * The middleware is injected into WebpiecesServerImpl and registered with Express
27
+ * in the start() method.
28
+ *
29
+ * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
30
+ * Express's registered route handlers (created by RouteBuilder.createHandler()).
31
+ * jsonTranslator only validates Content-Type and translates errors to JSON.
32
+ *
33
+ * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
34
+ */
35
+ export declare class WebpiecesMiddleware {
36
+ /**
37
+ * Global error handler middleware - catches ALL unhandled errors.
38
+ * Returns HTML 500 error page for any errors that escape the filter chain.
39
+ *
40
+ * This is the outermost safety net - JsonTranslator catches JSON API errors,
41
+ * this catches everything else.
42
+ */
43
+ globalErrorHandler(req: Request, res: Response, next: NextFunction): Promise<void>;
44
+ /**
45
+ * Logging middleware - logs request/response flow.
46
+ * Demonstrates middleware execution order.
47
+ * IMPORTANT: Must be async and await next() to properly chain with async middleware.
48
+ */
49
+ logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void>;
50
+ /**
51
+ * Create an ExpressWrapper for a route.
52
+ * The wrapper handles the full request/response cycle (symmetric design).
53
+ *
54
+ * @param service - The service wrapping the filter chain and controller
55
+ * @param routeMeta - Route metadata for MethodMeta and DTO type
56
+ * @returns ExpressWrapper instance
57
+ */
58
+ createExpressWrapper(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata): ExpressWrapper;
59
+ }
@@ -0,0 +1,159 @@
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
+ const typescript_json_serializer_1 = require("typescript-json-serializer");
10
+ class ExpressWrapper {
11
+ constructor(service, routeMeta) {
12
+ this.service = service;
13
+ this.routeMeta = routeMeta;
14
+ this.jsonSerializer = new typescript_json_serializer_1.JsonSerializer();
15
+ }
16
+ async execute(req, res, next) {
17
+ try {
18
+ // 1. Get request DTO class from routeMeta
19
+ const requestDtoClass = this.routeMeta.parameterTypes?.[0];
20
+ if (!requestDtoClass)
21
+ throw new Error('No request DTO class found for route');
22
+ // 2. Deserialize req.body → DTO instance
23
+ const requestDto = this.jsonSerializer.deserializeObject(req.body, requestDtoClass);
24
+ // 3. Create MethodMeta with deserialized DTO
25
+ const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestDto);
26
+ // 4. 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
+ // 6. Serialize response → plain object
31
+ const responseDtoStr = this.jsonSerializer.serializeObject(wpResponse.response);
32
+ // 7. WRITE response as JSON (SYMMETRIC with reading request!)
33
+ res.status(200);
34
+ res.setHeader('Content-Type', 'application/json');
35
+ res.json(responseDtoStr);
36
+ }
37
+ catch (err) {
38
+ // 8. Handle errors (SYMMETRIC - wrapExpress owns error handling!)
39
+ this.handleError(res, err);
40
+ }
41
+ }
42
+ /**
43
+ * Handle errors - translate to JSON ProtocolError.
44
+ * PUBLIC so wrapExpress can call it for symmetric error handling.
45
+ * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
46
+ */
47
+ handleError(res, error) {
48
+ if (res.headersSent) {
49
+ return;
50
+ }
51
+ const protocolError = new http_api_1.ProtocolError();
52
+ if (error instanceof http_api_1.HttpError) {
53
+ protocolError.message = error.message;
54
+ protocolError.subType = error.subType;
55
+ protocolError.name = error.name;
56
+ if (error instanceof http_api_1.HttpBadRequestError) {
57
+ protocolError.field = error.field;
58
+ protocolError.guiAlertMessage = error.guiMessage;
59
+ }
60
+ if (error instanceof http_api_1.HttpVendorError) {
61
+ protocolError.waitSeconds = error.waitSeconds;
62
+ }
63
+ if (error instanceof http_api_1.HttpUserError) {
64
+ protocolError.errorCode = error.errorCode;
65
+ }
66
+ res.status(error.code).json(protocolError);
67
+ }
68
+ else {
69
+ // Unknown error - 500
70
+ const err = (0, core_util_1.toError)(error);
71
+ protocolError.message = 'Internal Server Error';
72
+ console.error('[JsonTranslator] Unexpected error:', err);
73
+ res.status(500).json(protocolError);
74
+ }
75
+ }
76
+ }
77
+ exports.ExpressWrapper = ExpressWrapper;
78
+ /**
79
+ * WebpiecesMiddleware - Express middleware for WebPieces server.
80
+ *
81
+ * This class contains all Express middleware used by WebpiecesServer:
82
+ * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
83
+ * 2. logNextLayer - Request/response logging
84
+ * 3. jsonTranslator - JSON Content-Type validation and error translation
85
+ *
86
+ * The middleware is injected into WebpiecesServerImpl and registered with Express
87
+ * in the start() method.
88
+ *
89
+ * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
90
+ * Express's registered route handlers (created by RouteBuilder.createHandler()).
91
+ * jsonTranslator only validates Content-Type and translates errors to JSON.
92
+ *
93
+ * DI Pattern: This class is registered via @provideSingleton() with no dependencies.
94
+ */
95
+ let WebpiecesMiddleware = class WebpiecesMiddleware {
96
+ /**
97
+ * Global error handler middleware - catches ALL unhandled errors.
98
+ * Returns HTML 500 error page for any errors that escape the filter chain.
99
+ *
100
+ * This is the outermost safety net - JsonTranslator catches JSON API errors,
101
+ * this catches everything else.
102
+ */
103
+ async globalErrorHandler(req, res, next) {
104
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
105
+ try {
106
+ // await next() catches BOTH:
107
+ // 1. Synchronous throws from next() itself
108
+ // 2. Rejected promises from downstream async middleware
109
+ await next();
110
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
111
+ }
112
+ catch (err) {
113
+ const error = (0, core_util_1.toError)(err);
114
+ console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
115
+ if (!res.headersSent) {
116
+ // Return HTML error page (not JSON - JsonTranslator handles JSON errors)
117
+ res.status(500).send(`
118
+ <!DOCTYPE html>
119
+ <html>
120
+ <head><title>Server Error</title></head>
121
+ <body>
122
+ <h1>You hit a server error</h1>
123
+ <p>An unexpected error occurred while processing your request.</p>
124
+ <pre>${error.message}</pre>
125
+ </body>
126
+ </html>
127
+ `);
128
+ }
129
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
130
+ }
131
+ }
132
+ /**
133
+ * Logging middleware - logs request/response flow.
134
+ * Demonstrates middleware execution order.
135
+ * IMPORTANT: Must be async and await next() to properly chain with async middleware.
136
+ */
137
+ async logNextLayer(req, res, next) {
138
+ console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
139
+ await next();
140
+ console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
141
+ }
142
+ /**
143
+ * Create an ExpressWrapper for a route.
144
+ * The wrapper handles the full request/response cycle (symmetric design).
145
+ *
146
+ * @param service - The service wrapping the filter chain and controller
147
+ * @param routeMeta - Route metadata for MethodMeta and DTO type
148
+ * @returns ExpressWrapper instance
149
+ */
150
+ createExpressWrapper(service, routeMeta) {
151
+ return new ExpressWrapper(service, routeMeta);
152
+ }
153
+ };
154
+ exports.WebpiecesMiddleware = WebpiecesMiddleware;
155
+ exports.WebpiecesMiddleware = WebpiecesMiddleware = tslib_1.__decorate([
156
+ (0, http_routing_1.provideSingleton)(),
157
+ (0, inversify_1.injectable)()
158
+ ], WebpiecesMiddleware);
159
+ //# 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 RouteMetadata,\n} from '@webpieces/http-api';\nimport { Service, WpResponse } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\nimport { JsonSerializer } from 'typescript-json-serializer';\n\nexport class ExpressWrapper {\n private jsonSerializer = new JsonSerializer();\n\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. Get request DTO class from routeMeta\n const requestDtoClass = this.routeMeta.parameterTypes?.[0];\n if(!requestDtoClass)\n throw new Error('No request DTO class found for route');\n\n // 2. Deserialize req.body → DTO instance\n const requestDto = this.jsonSerializer.deserializeObject(req.body, requestDtoClass);\n // 3. Create MethodMeta with deserialized DTO\n const methodMeta = new MethodMeta(this.routeMeta, requestDto);\n // 4. 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 // 6. Serialize response → plain object\n const responseDtoStr = this.jsonSerializer.serializeObject(wpResponse.response);\n\n // 7. WRITE response as JSON (SYMMETRIC with reading request!)\n res.status(200);\n res.setHeader('Content-Type', 'application/json');\n res.json(responseDtoStr);\n } catch (err: unknown) {\n // 8. Handle errors (SYMMETRIC - wrapExpress owns error handling!)\n this.handleError(res, err);\n }\n }\n\n /**\n * Handle errors - translate to JSON ProtocolError.\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 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 protocolError.message = error.message;\n protocolError.subType = error.subType;\n protocolError.name = error.name;\n\n if (error instanceof HttpBadRequestError) {\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n }\n if (error instanceof HttpVendorError) {\n protocolError.waitSeconds = error.waitSeconds;\n }\n if (error instanceof HttpUserError) {\n protocolError.errorCode = error.errorCode;\n }\n\n res.status(error.code).json(protocolError);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n console.error('[JsonTranslator] Unexpected error:', err);\n res.status(500).json(protocolError);\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"]}
@@ -1,7 +1,7 @@
1
- import { Container } from 'inversify';
2
- import { WebAppMeta } from '@webpieces/core-meta';
3
- import { RouteBuilderImpl } from './RouteBuilderImpl';
1
+ import { Container, ContainerModule } from 'inversify';
2
+ import { ExpressRouteHandler, RouteBuilderImpl, WebAppMeta } from '@webpieces/http-routing';
4
3
  import { WebpiecesServer } from './WebpiecesServer';
4
+ import { WebpiecesMiddleware } from './WebpiecesMiddleware';
5
5
  /**
6
6
  * WebpiecesServerImpl - Internal server implementation.
7
7
  *
@@ -28,6 +28,7 @@ import { WebpiecesServer } from './WebpiecesServer';
28
28
  */
29
29
  export declare class WebpiecesServerImpl implements WebpiecesServer {
30
30
  private routeBuilder;
31
+ private middleware;
31
32
  private meta;
32
33
  private webpiecesContainer;
33
34
  /**
@@ -40,7 +41,7 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
40
41
  private app?;
41
42
  private server?;
42
43
  private port;
43
- constructor(routeBuilder: RouteBuilderImpl);
44
+ constructor(routeBuilder: RouteBuilderImpl, middleware: WebpiecesMiddleware);
44
45
  /**
45
46
  * Initialize the server (DI container, routes, filters).
46
47
  * This is called by WebpiecesFactory.create() after resolving this class from DI.
@@ -48,8 +49,17 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
48
49
  *
49
50
  * @param webpiecesContainer - The framework container
50
51
  * @param meta - User-provided WebAppMeta with DI modules and routes
52
+ * @param overrides - Optional ContainerModule for test overrides (loaded LAST)
51
53
  */
52
- initialize(webpiecesContainer: Container, meta: WebAppMeta): void;
54
+ /**
55
+ * Initialize the server asynchronously.
56
+ * Use this when overrides module contains async operations (e.g., rebind() in new Inversify).
57
+ *
58
+ * @param webpiecesContainer - The framework container
59
+ * @param meta - User-provided WebAppMeta with DI modules and routes
60
+ * @param overrides - Optional ContainerModule for test overrides (loaded LAST)
61
+ */
62
+ initialize(webpiecesContainer: Container, meta: WebAppMeta, overrides?: ContainerModule): Promise<void>;
53
63
  /**
54
64
  * Load DI modules from WebAppMeta.
55
65
  *
@@ -59,6 +69,8 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
59
69
  * - Application modules -> appContainer
60
70
  *
61
71
  * For now, everything goes into appContainer which has access to webpiecesContainer.
72
+ *
73
+ * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
62
74
  */
63
75
  private loadDIModules;
64
76
  /**
@@ -71,19 +83,6 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
71
83
  * - Understanding: Clear class name vs anonymous object
72
84
  */
73
85
  private registerRoutes;
74
- /**
75
- * Global error handler middleware - catches ALL unhandled errors.
76
- * Returns HTML 500 error page for any errors that escape the filter chain.
77
- *
78
- * This is the outermost safety net - JsonFilter catches JSON API errors,
79
- * this catches everything else.
80
- */
81
- private globalErrorHandler;
82
- /**
83
- * Logging middleware - logs request/response flow.
84
- * Demonstrates middleware execution order.
85
- */
86
- private logNextLayer;
87
86
  /**
88
87
  * Start the HTTP server with Express.
89
88
  * Returns a Promise that resolves when the server is listening,
@@ -92,22 +91,52 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
92
91
  * @param port - The port to listen on (default: 8080)
93
92
  * @returns Promise that resolves when server is ready
94
93
  */
95
- start(port?: number): Promise<void>;
94
+ start(port?: number, testMode?: boolean): Promise<void>;
96
95
  /**
97
- * Register all routes with Express.
96
+ * Register Express routes - the SINGLE loop over routes.
97
+ * For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.
98
+ *
99
+ * @returns Number of routes registered
98
100
  */
99
101
  private registerExpressRoutes;
102
+ registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler): void;
100
103
  /**
101
- * Setup a single route with Express.
102
- * Finds matching filters, creates handler, and registers with Express.
104
+ * Stop the HTTP server.
105
+ * Returns a Promise that resolves when the server is stopped,
106
+ * or rejects if there's an error stopping the server.
103
107
  *
104
- * @param key - Route key (method:path)
105
- * @param routeWithMeta - The route handler paired with its definition
106
- * @param filtersWithMeta - All filters with their definitions
108
+ * @returns Promise that resolves when server is stopped
107
109
  */
108
- private setupRoute;
110
+ stop(): Promise<void>;
109
111
  /**
110
- * Stop the HTTP server.
112
+ * Get the application DI container.
113
+ *
114
+ * Useful for testing to verify state or access services directly.
115
+ *
116
+ * @returns The application Container
117
+ */
118
+ getContainer(): Container;
119
+ /**
120
+ * Create an API client proxy for testing.
121
+ *
122
+ * This creates a client that routes calls through the full filter chain
123
+ * and controller, but WITHOUT any HTTP overhead. Perfect for testing!
124
+ *
125
+ * The client uses the ApiPrototype class to discover routes via decorators,
126
+ * then creates pre-configured invoker functions for each API method.
127
+ *
128
+ * IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
129
+ * For each API method, it sets up the filter chain ONCE during proxy creation,
130
+ * so subsequent calls reuse the same filter chain (efficient!).
131
+ *
132
+ * @param apiPrototype - The API prototype class with routing decorators (can be abstract)
133
+ * @returns A proxy that implements the API interface
134
+ *
135
+ * Example:
136
+ * ```typescript
137
+ * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
138
+ * const response = await saveApi.save(request);
139
+ * ```
111
140
  */
112
- stop(): void;
141
+ createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
113
142
  }