@webpieces/http-server 0.2.12 → 0.2.14

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.
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebpiecesFactory = void 0;
4
+ const inversify_1 = require("inversify");
5
+ const binding_decorators_1 = require("@inversifyjs/binding-decorators");
6
+ const WebpiecesServerImpl_1 = require("./WebpiecesServerImpl");
7
+ /**
8
+ * WebpiecesFactory - Factory for creating WebPieces server instances.
9
+ *
10
+ * This factory encapsulates the server creation and initialization logic:
11
+ * 1. Creates the WebPieces DI container
12
+ * 2. Loads the provider module for @provideSingleton decorators
13
+ * 3. Resolves WebpiecesServerImpl from DI
14
+ * 4. Calls initialize() with the container and meta
15
+ * 5. Returns the server as the WebpiecesServer interface
16
+ *
17
+ * The returned WebpiecesServer interface only exposes start() and stop(),
18
+ * hiding the internal initialize() method from consumers.
19
+ *
20
+ * Usage:
21
+ * ```typescript
22
+ * const server = WebpiecesFactory.create(new ProdServerMeta());
23
+ * server.start(8080);
24
+ * // ... later
25
+ * server.stop();
26
+ * ```
27
+ *
28
+ * This pattern:
29
+ * - Enforces proper initialization order
30
+ * - Hides implementation details from consumers
31
+ * - Makes the API simpler and harder to misuse
32
+ * - Follows the principle of least privilege
33
+ */
34
+ class WebpiecesFactory {
35
+ /**
36
+ * Create a new WebPieces server instance.
37
+ *
38
+ * This method:
39
+ * 1. Creates the WebPieces framework DI container
40
+ * 2. Loads framework bindings via buildProviderModule()
41
+ * 3. Resolves the server implementation from DI
42
+ * 4. Initializes the server with the container and meta
43
+ *
44
+ * @param meta - User-provided WebAppMeta with DI modules and routes
45
+ * @returns A fully initialized WebpiecesServer ready to start()
46
+ */
47
+ static create(meta) {
48
+ // Create WebPieces container for framework-level bindings
49
+ const webpiecesContainer = new inversify_1.Container();
50
+ // 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'!)
54
+ const serverImpl = webpiecesContainer.get(WebpiecesServerImpl_1.WebpiecesServerImpl);
55
+ // Initialize the server (loads app DI modules, registers routes)
56
+ serverImpl.initialize(webpiecesContainer, meta);
57
+ // Return as interface to hide initialize() from consumers
58
+ return serverImpl;
59
+ }
60
+ }
61
+ exports.WebpiecesFactory = WebpiecesFactory;
62
+ //# sourceMappingURL=WebpiecesFactory.js.map
@@ -0,0 +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,151 +1,34 @@
1
- import { Container } from 'inversify';
2
- import { WebAppMeta } from '@webpieces/core-meta';
3
1
  /**
4
- * WebpiecesServer - Main bootstrap class for WebPieces applications.
2
+ * WebpiecesServer - Public interface for WebPieces server.
5
3
  *
6
- * This class uses a two-container pattern similar to Java WebPieces:
7
- * 1. webpiecesContainer: Core WebPieces framework bindings
8
- * 2. appContainer: User's application bindings (child of webpiecesContainer)
4
+ * This interface exposes only the methods needed by application code:
5
+ * - start(): Start the HTTP server
6
+ * - stop(): Stop the HTTP server
9
7
  *
10
- * This separation allows:
11
- * - Clean separation of concerns
12
- * - Better testability
13
- * - Ability to override framework bindings in tests
8
+ * The initialization logic is hidden inside WebpiecesFactory.create().
9
+ * This provides a clean API and prevents accidental re-initialization.
14
10
  *
15
- * The server:
16
- * 1. Initializes both DI containers from WebAppMeta.getDIModules()
17
- * 2. Registers routes using explicit RouteBuilderImpl
18
- * 3. Creates filter chains
19
- * 4. Supports both HTTP server mode and testing mode (no HTTP)
20
- *
21
- * Usage for testing (no HTTP):
11
+ * Usage:
22
12
  * ```typescript
23
- * const server = new WebpiecesServer(new ProdServerMeta());
24
- * server.initialize();
25
- * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
26
- * const response = await saveApi.save(request);
27
- * ```
28
- *
29
- * Usage for production (HTTP server):
30
- * ```typescript
31
- * const server = new WebpiecesServer(new ProdServerMeta());
32
- * server.start(); // Starts Express server
13
+ * const server = WebpiecesFactory.create(new ProdServerMeta());
14
+ * await server.start(8080);
15
+ * console.log('Server is now listening!');
16
+ * // ... later
17
+ * server.stop();
33
18
  * ```
34
19
  */
35
- export declare class WebpiecesServer {
36
- private meta;
37
- /**
38
- * WebPieces container: Core WebPieces framework bindings.
39
- * This includes framework-level services like filters, routing infrastructure,
40
- * logging, metrics, etc. Similar to Java WebPieces platform container.
41
- */
42
- private webpiecesContainer;
43
- /**
44
- * Application container: User's application bindings.
45
- * This is a child container of webpiecesContainer, so it can access
46
- * framework bindings while keeping app bindings separate.
47
- */
48
- private appContainer;
49
- /**
50
- * Routes registry: Maps "METHOD:path" -> RegisteredRoute
51
- * Example: "POST:/search/item" -> { method: "POST", path: "/search/item", handler: ... }
52
- *
53
- * We use unknown instead of any for type safety - each route has its own return type,
54
- * but we can't have different generic types in the same Map.
55
- */
56
- private routes;
57
- /**
58
- * Registered filters with their definitions.
59
- * Used by FilterMatcher to match filters to routes based on filepath patterns.
60
- */
61
- private filterRegistry;
62
- private initialized;
63
- private app?;
64
- private server?;
65
- private port;
66
- constructor(meta: WebAppMeta);
67
- /**
68
- * Initialize the server (DI container, routes, filters).
69
- * This is called automatically by start() or can be called manually for testing.
70
- */
71
- initialize(): void;
72
- /**
73
- * Load DI modules from WebAppMeta.
74
- *
75
- * Currently, all user modules are loaded into the application container.
76
- * In the future, we could separate:
77
- * - WebPieces framework modules -> webpiecesContainer
78
- * - Application modules -> appContainer
79
- *
80
- * For now, everything goes into appContainer which has access to webpiecesContainer.
81
- */
82
- private loadDIModules;
83
- /**
84
- * Register routes from WebAppMeta.
85
- *
86
- * Creates an explicit RouteBuilderImpl instead of an anonymous object.
87
- * This improves:
88
- * - Traceability: Can Cmd+Click on addRoute to see implementation
89
- * - Debugging: Explicit class shows up in stack traces
90
- * - Understanding: Clear class name vs anonymous object
91
- */
92
- private registerRoutes;
20
+ export interface WebpiecesServer {
93
21
  /**
94
22
  * Start the HTTP server with Express.
95
- */
96
- start(port?: number): void;
97
- /**
98
- * Handle an incoming HTTP request through the filter chain and controller.
99
- * This is the main request processing logic.
100
- *
101
- * NO try-catch here - errors are handled by:
102
- * 1. JsonFilter - catches and returns JSON error responses
103
- * 2. GlobalErrorHandler middleware - catches any unhandled errors and returns HTML 500
104
- *
105
- * @param req - Express request
106
- * @param res - Express response
107
- * @param route - The registered route to execute
108
- * @param matchingFilters - Filters that apply to this route
109
- * @param key - Route key (method:path)
110
- */
111
- private handleRequest;
112
- /**
113
- * Register all routes with Express.
114
- */
115
- private registerExpressRoutes;
116
- /**
117
- * Setup a single route with Express.
118
- * Finds matching filters, creates handler, and registers with Express.
23
+ * Returns a Promise that resolves when the server is listening,
24
+ * or rejects if the server fails to start.
119
25
  *
120
- * @param key - Route key (method:path)
121
- * @param route - The registered route definition
26
+ * @param port - The port to listen on (default: 8080)
27
+ * @returns Promise that resolves when server is ready
122
28
  */
123
- private setupRoute;
29
+ start(port?: number): Promise<void>;
124
30
  /**
125
31
  * Stop the HTTP server.
126
32
  */
127
33
  stop(): void;
128
- /**
129
- * Create an API client proxy for testing (no HTTP).
130
- *
131
- * This creates a proxy object that implements the API interface
132
- * and routes method calls through the full filter chain to the controller.
133
- *
134
- * @param apiMetaClass - The API interface class with decorators
135
- * @returns Proxy object implementing the API interface
136
- */
137
- createApiClient<T>(apiMetaClass: any): T;
138
- /**
139
- * Invoke a route through the filter chain.
140
- */
141
- private invokeRoute;
142
- /**
143
- * Get the application DI container (for testing).
144
- * Returns appContainer which has access to both app and framework bindings.
145
- */
146
- getContainer(): Container;
147
- /**
148
- * Get the WebPieces framework container (for advanced testing/debugging).
149
- */
150
- getWebpiecesContainer(): Container;
151
34
  }
@@ -1,355 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebpiecesServer = void 0;
4
- const tslib_1 = require("tslib");
5
- const express_1 = tslib_1.__importDefault(require("express"));
6
- const inversify_1 = require("inversify");
7
- const binding_decorators_1 = require("@inversifyjs/binding-decorators");
8
- const core_meta_1 = require("@webpieces/core-meta");
9
- const http_filters_1 = require("@webpieces/http-filters");
10
- const http_routing_1 = require("@webpieces/http-routing");
11
- const RouteBuilderImpl_1 = require("./RouteBuilderImpl");
12
- const FilterMatcher_1 = require("./FilterMatcher");
13
- const core_util_1 = require("@webpieces/core-util");
14
- /**
15
- * WebpiecesServer - Main bootstrap class for WebPieces applications.
16
- *
17
- * This class uses a two-container pattern similar to Java WebPieces:
18
- * 1. webpiecesContainer: Core WebPieces framework bindings
19
- * 2. appContainer: User's application bindings (child of webpiecesContainer)
20
- *
21
- * This separation allows:
22
- * - Clean separation of concerns
23
- * - Better testability
24
- * - Ability to override framework bindings in tests
25
- *
26
- * The server:
27
- * 1. Initializes both DI containers from WebAppMeta.getDIModules()
28
- * 2. Registers routes using explicit RouteBuilderImpl
29
- * 3. Creates filter chains
30
- * 4. Supports both HTTP server mode and testing mode (no HTTP)
31
- *
32
- * Usage for testing (no HTTP):
33
- * ```typescript
34
- * const server = new WebpiecesServer(new ProdServerMeta());
35
- * server.initialize();
36
- * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
37
- * const response = await saveApi.save(request);
38
- * ```
39
- *
40
- * Usage for production (HTTP server):
41
- * ```typescript
42
- * const server = new WebpiecesServer(new ProdServerMeta());
43
- * server.start(); // Starts Express server
44
- * ```
45
- */
46
- class WebpiecesServer {
47
- constructor(meta) {
48
- /**
49
- * Routes registry: Maps "METHOD:path" -> RegisteredRoute
50
- * Example: "POST:/search/item" -> { method: "POST", path: "/search/item", handler: ... }
51
- *
52
- * We use unknown instead of any for type safety - each route has its own return type,
53
- * but we can't have different generic types in the same Map.
54
- */
55
- this.routes = new Map();
56
- /**
57
- * Registered filters with their definitions.
58
- * Used by FilterMatcher to match filters to routes based on filepath patterns.
59
- */
60
- this.filterRegistry = [];
61
- this.initialized = false;
62
- this.port = 8080;
63
- this.meta = meta;
64
- // Create WebPieces container for framework-level bindings
65
- this.webpiecesContainer = new inversify_1.Container();
66
- // Create application container as a child of WebPieces container
67
- // This allows app container to access framework bindings
68
- this.appContainer = new inversify_1.Container({ parent: this.webpiecesContainer });
69
- }
70
- /**
71
- * Initialize the server (DI container, routes, filters).
72
- * This is called automatically by start() or can be called manually for testing.
73
- */
74
- initialize() {
75
- if (this.initialized) {
76
- return;
77
- }
78
- // 1. Load DI modules
79
- this.loadDIModules();
80
- // 2. Register routes and filters
81
- this.registerRoutes();
82
- this.initialized = true;
83
- }
84
- /**
85
- * Load DI modules from WebAppMeta.
86
- *
87
- * Currently, all user modules are loaded into the application container.
88
- * In the future, we could separate:
89
- * - WebPieces framework modules -> webpiecesContainer
90
- * - Application modules -> appContainer
91
- *
92
- * For now, everything goes into appContainer which has access to webpiecesContainer.
93
- */
94
- loadDIModules() {
95
- const modules = this.meta.getDIModules();
96
- // Load buildProviderModule to auto-scan for @provideSingleton decorators
97
- this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
98
- // Load all modules into application container
99
- // (webpiecesContainer is currently empty, reserved for future framework bindings)
100
- for (const module of modules) {
101
- this.appContainer.load(module);
102
- }
103
- }
104
- /**
105
- * Register routes from WebAppMeta.
106
- *
107
- * Creates an explicit RouteBuilderImpl instead of an anonymous object.
108
- * This improves:
109
- * - Traceability: Can Cmd+Click on addRoute to see implementation
110
- * - Debugging: Explicit class shows up in stack traces
111
- * - Understanding: Clear class name vs anonymous object
112
- */
113
- registerRoutes() {
114
- const routeConfigs = this.meta.getRoutes();
115
- // Create explicit RouteBuilder implementation
116
- // Filters are resolved from appContainer (which has access to platformContainer too)
117
- const routeBuilder = new RouteBuilderImpl_1.RouteBuilderImpl(this.routes, this.filterRegistry, this.appContainer);
118
- // Configure routes using the explicit RouteBuilder
119
- for (const routeConfig of routeConfigs) {
120
- routeConfig.configure(routeBuilder);
121
- }
122
- }
123
- /**
124
- * Start the HTTP server with Express.
125
- */
126
- start(port = 8080) {
127
- this.port = port;
128
- this.initialize();
129
- // Create Express app
130
- this.app = (0, express_1.default)();
131
- // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)
132
- // Wraps all subsequent middleware with try-catch
133
- // IMPORTANT: Use async/await to catch BOTH synchronous throws AND async rejections
134
- this.app.use(async (req, res, next) => {
135
- console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
136
- try {
137
- // await next() catches BOTH:
138
- // 1. Synchronous throws from next() itself
139
- // 2. Rejected promises from downstream async middleware
140
- await next();
141
- console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
142
- }
143
- catch (err) {
144
- const error = (0, core_util_1.toError)(err);
145
- console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
146
- if (!res.headersSent) {
147
- // Return HTML error page (not JSON - JsonFilter handles JSON errors)
148
- res.status(500).send(`
149
- <!DOCTYPE html>
150
- <html>
151
- <head><title>Server Error</title></head>
152
- <body>
153
- <h1>You hit a server error</h1>
154
- <p>An unexpected error occurred while processing your request.</p>
155
- <pre>${error.message}</pre>
156
- </body>
157
- </html>
158
- `);
159
- }
160
- console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
161
- }
162
- });
163
- // Layer 2: Log Next Layer (runs SECOND)
164
- this.app.use((req, res, next) => {
165
- console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
166
- next();
167
- console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
168
- });
169
- // Layer 3+: Standard Express middleware
170
- this.app.use(express_1.default.json());
171
- this.app.use(express_1.default.urlencoded({ extended: true }));
172
- // Register routes (these become the innermost handlers)
173
- this.registerExpressRoutes();
174
- // Start listening
175
- this.server = this.app.listen(this.port, () => {
176
- console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
177
- console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);
178
- console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);
179
- });
180
- }
181
- /**
182
- * Handle an incoming HTTP request through the filter chain and controller.
183
- * This is the main request processing logic.
184
- *
185
- * NO try-catch here - errors are handled by:
186
- * 1. JsonFilter - catches and returns JSON error responses
187
- * 2. GlobalErrorHandler middleware - catches any unhandled errors and returns HTML 500
188
- *
189
- * @param req - Express request
190
- * @param res - Express response
191
- * @param route - The registered route to execute
192
- * @param matchingFilters - Filters that apply to this route
193
- * @param key - Route key (method:path)
194
- */
195
- async handleRequest(req, res, route, matchingFilters, key) {
196
- // Create method metadata
197
- const meta = new http_filters_1.MethodMeta(route.method, route.path, key, [req.body], new core_meta_1.RouteRequest(req.body, req.query, req.params, req.headers), undefined, new Map());
198
- // Create filter chain with matched filters
199
- const filterChain = new http_filters_1.FilterChain(matchingFilters);
200
- // Execute the filter chain
201
- // Errors thrown here are caught by JsonFilter or bubble to GlobalErrorHandler
202
- const action = await filterChain.execute(meta, async () => {
203
- // Create typed route context
204
- // Use appContainer which has access to both app and framework bindings
205
- const routeContext = new core_meta_1.RouteContext(this.appContainer, [req.body], meta.request);
206
- // Final handler: invoke the controller method via route handler
207
- const result = await route.handler.execute(routeContext);
208
- // Wrap result in a JSON action
209
- return (0, http_filters_1.jsonAction)(result);
210
- });
211
- // Send response
212
- if (action.type === 'json') {
213
- res.json(action.data);
214
- }
215
- else if (action.type === 'error') {
216
- res.status(500).json({ error: action.data });
217
- }
218
- }
219
- /**
220
- * Register all routes with Express.
221
- */
222
- registerExpressRoutes() {
223
- if (!this.app) {
224
- throw new Error('Express app not initialized');
225
- }
226
- for (const [key, route] of this.routes.entries()) {
227
- this.setupRoute(key, route);
228
- }
229
- }
230
- /**
231
- * Setup a single route with Express.
232
- * Finds matching filters, creates handler, and registers with Express.
233
- *
234
- * @param key - Route key (method:path)
235
- * @param route - The registered route definition
236
- */
237
- setupRoute(key, route) {
238
- if (!this.app) {
239
- throw new Error('Express app not initialized');
240
- }
241
- const method = route.method.toLowerCase();
242
- const path = route.path;
243
- console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);
244
- // Find matching filters for this route
245
- const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, this.filterRegistry);
246
- // Create Express route handler - delegates to handleRequest
247
- const handler = async (req, res, next) => {
248
- await this.handleRequest(req, res, route, matchingFilters, key);
249
- };
250
- // Register with Express
251
- switch (method) {
252
- case 'get':
253
- this.app.get(path, handler);
254
- break;
255
- case 'post':
256
- this.app.post(path, handler);
257
- break;
258
- case 'put':
259
- this.app.put(path, handler);
260
- break;
261
- case 'delete':
262
- this.app.delete(path, handler);
263
- break;
264
- case 'patch':
265
- this.app.patch(path, handler);
266
- break;
267
- default:
268
- console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);
269
- }
270
- }
271
- /**
272
- * Stop the HTTP server.
273
- */
274
- stop() {
275
- if (this.server) {
276
- this.server.close(() => {
277
- console.log('[WebpiecesServer] Server stopped');
278
- });
279
- }
280
- }
281
- /**
282
- * Create an API client proxy for testing (no HTTP).
283
- *
284
- * This creates a proxy object that implements the API interface
285
- * and routes method calls through the full filter chain to the controller.
286
- *
287
- * @param apiMetaClass - The API interface class with decorators
288
- * @returns Proxy object implementing the API interface
289
- */
290
- createApiClient(apiMetaClass) {
291
- this.initialize();
292
- // Get routes from the API metadata
293
- const routes = (0, http_routing_1.getRoutes)(apiMetaClass);
294
- // Create a proxy object
295
- const proxy = {};
296
- for (const route of routes) {
297
- const methodName = route.methodName;
298
- // Create a function that routes through the filter chain
299
- proxy[methodName] = async (...args) => {
300
- return this.invokeRoute(route, args);
301
- };
302
- }
303
- return proxy;
304
- }
305
- /**
306
- * Invoke a route through the filter chain.
307
- */
308
- async invokeRoute(route, args) {
309
- // Find the registered route
310
- const key = `${route.httpMethod}:${route.path}`;
311
- const registeredRoute = this.routes.get(key);
312
- if (!registeredRoute) {
313
- throw new Error(`Route not found: ${key}`);
314
- }
315
- // Find matching filters for this route
316
- const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(registeredRoute.controllerFilepath, this.filterRegistry);
317
- // Create method metadata
318
- const meta = new http_filters_1.MethodMeta(route.httpMethod, route.path, route.methodName, [...args], new core_meta_1.RouteRequest(args[0]), // Assume first arg is the request body
319
- undefined, new Map());
320
- // Create filter chain with matched filters
321
- const filterChain = new http_filters_1.FilterChain(matchingFilters);
322
- // Execute the filter chain
323
- const action = await filterChain.execute(meta, async () => {
324
- // Create typed route context
325
- // Use appContainer which has access to both app and framework bindings
326
- const routeContext = new core_meta_1.RouteContext(this.appContainer, meta.params, meta.request);
327
- // Final handler: invoke the controller method via route handler
328
- const result = await registeredRoute.handler.execute(routeContext);
329
- // Wrap result in a JSON action
330
- return (0, http_filters_1.jsonAction)(result);
331
- });
332
- // Return the data from the action
333
- if (action.type === 'error') {
334
- throw new Error(JSON.stringify(action.data));
335
- }
336
- return action.data;
337
- }
338
- /**
339
- * Get the application DI container (for testing).
340
- * Returns appContainer which has access to both app and framework bindings.
341
- */
342
- getContainer() {
343
- this.initialize();
344
- return this.appContainer;
345
- }
346
- /**
347
- * Get the WebPieces framework container (for advanced testing/debugging).
348
- */
349
- getWebpiecesContainer() {
350
- this.initialize();
351
- return this.webpiecesContainer;
352
- }
353
- }
354
- exports.WebpiecesServer = WebpiecesServer;
355
3
  //# sourceMappingURL=WebpiecesServer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Express, Request, Response, NextFunction } from 'express';\nimport { Container } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta, RouteContext, RouteRequest, FilterDefinition } from '@webpieces/core-meta';\nimport { FilterChain, Filter, MethodMeta, jsonAction } from '@webpieces/http-filters';\nimport { getRoutes, RouteMetadata } from '@webpieces/http-routing';\nimport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\nimport { FilterMatcher } from './FilterMatcher';\nimport { toError } from '@webpieces/core-util';\n\n/**\n * WebpiecesServer - Main bootstrap class for WebPieces applications.\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 * Usage for testing (no HTTP):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.initialize();\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n *\n * Usage for production (HTTP server):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.start(); // Starts Express server\n * ```\n */\nexport class WebpiecesServer {\n private meta: WebAppMeta;\n\n /**\n * WebPieces container: Core WebPieces framework bindings.\n * This includes framework-level services like filters, routing infrastructure,\n * logging, metrics, etc. Similar to Java WebPieces platform container.\n */\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 /**\n * Routes registry: Maps \"METHOD:path\" -> RegisteredRoute\n * Example: \"POST:/search/item\" -> { method: \"POST\", path: \"/search/item\", handler: ... }\n *\n * We use unknown instead of any for type safety - each route has its own return type,\n * but we can't have different generic types in the same Map.\n */\n private routes: Map<string, RegisteredRoute<unknown>> = new Map();\n\n /**\n * Registered filters with their definitions.\n * Used by FilterMatcher to match filters to routes based on filepath patterns.\n */\n private filterRegistry: Array<{ filter: Filter; definition: FilterDefinition }> = [];\n\n private initialized = false;\n private app?: Express;\n private server?: any;\n private port: number = 8080;\n\n constructor(meta: WebAppMeta) {\n this.meta = meta;\n\n // Create WebPieces container for framework-level bindings\n this.webpiecesContainer = new Container();\n\n // Create application container as a child of WebPieces container\n // This allows app container to access framework bindings\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called automatically by start() or can be called manually for testing.\n */\n initialize(): void {\n if (this.initialized) {\n return;\n }\n\n // 1. Load DI modules\n this.loadDIModules();\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 private loadDIModules(): void {\n const modules = this.meta.getDIModules();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n 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 this.appContainer.load(module);\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 // Create explicit RouteBuilder implementation\n // Filters are resolved from appContainer (which has access to platformContainer too)\n const routeBuilder = new RouteBuilderImpl(\n this.routes,\n this.filterRegistry,\n this.appContainer\n );\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n */\n start(port: number = 8080): void {\n this.port = port;\n this.initialize();\n\n // Create Express app\n this.app = express();\n\n // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)\n // Wraps all subsequent middleware with try-catch\n // IMPORTANT: Use async/await to catch BOTH synchronous throws AND async rejections\n this.app.use(async (req: Request, res: Response, next: NextFunction) => {\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('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);\n } catch (err: any) {\n const error = toError(err);\n console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonFilter 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('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);\n }\n });\n\n // Layer 2: Log Next Layer (runs SECOND)\n this.app.use((req: Request, res: Response, next: NextFunction) => {\n console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n next();\n console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\n });\n\n // Layer 3+: Standard Express middleware\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // Register routes (these become the innermost handlers)\n this.registerExpressRoutes();\n\n // Start listening\n this.server = this.app.listen(this.port, () => {\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);\n console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);\n });\n }\n\n /**\n * Handle an incoming HTTP request through the filter chain and controller.\n * This is the main request processing logic.\n *\n * NO try-catch here - errors are handled by:\n * 1. JsonFilter - catches and returns JSON error responses\n * 2. GlobalErrorHandler middleware - catches any unhandled errors and returns HTML 500\n *\n * @param req - Express request\n * @param res - Express response\n * @param route - The registered route to execute\n * @param matchingFilters - Filters that apply to this route\n * @param key - Route key (method:path)\n */\n private async handleRequest(\n req: Request,\n res: Response,\n route: RegisteredRoute<unknown>,\n matchingFilters: Filter[],\n key: string\n ): Promise<void> {\n // Create method metadata\n const meta = new MethodMeta(\n route.method,\n route.path,\n key,\n [req.body],\n new RouteRequest(req.body, req.query, req.params, req.headers),\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n // Errors thrown here are caught by JsonFilter or bubble to GlobalErrorHandler\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n [req.body],\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await route.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Send response\n if (action.type === 'json') {\n res.json(action.data);\n } else if (action.type === 'error') {\n res.status(500).json({ error: action.data });\n }\n }\n\n /**\n * Register all routes with Express.\n */\n private registerExpressRoutes(): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n for (const [key, route] of this.routes.entries()) {\n this.setupRoute(key, route);\n }\n }\n\n /**\n * Setup a single route with Express.\n * Finds matching filters, creates handler, and registers with Express.\n *\n * @param key - Route key (method:path)\n * @param route - The registered route definition\n */\n private setupRoute(key: string, route: RegisteredRoute<unknown>): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n const method = route.method.toLowerCase();\n const path = route.path;\n\n console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n this.filterRegistry\n );\n\n // Create Express route handler - delegates to handleRequest\n const handler = async (req: Request, res: Response, next: NextFunction) => {\n await this.handleRequest(req, res, route, matchingFilters, key);\n };\n\n // Register with Express\n switch (method) {\n case 'get':\n this.app.get(path, handler);\n break;\n case 'post':\n this.app.post(path, handler);\n break;\n case 'put':\n this.app.put(path, handler);\n break;\n case 'delete':\n this.app.delete(path, handler);\n break;\n case 'patch':\n this.app.patch(path, handler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);\n }\n }\n\n /**\n * Stop the HTTP server.\n */\n stop(): void {\n if (this.server) {\n this.server.close(() => {\n console.log('[WebpiecesServer] Server stopped');\n });\n }\n }\n\n /**\n * Create an API client proxy for testing (no HTTP).\n *\n * This creates a proxy object that implements the API interface\n * and routes method calls through the full filter chain to the controller.\n *\n * @param apiMetaClass - The API interface class with decorators\n * @returns Proxy object implementing the API interface\n */\n createApiClient<T>(apiMetaClass: any): T {\n this.initialize();\n\n // Get routes from the API metadata\n const routes = getRoutes(apiMetaClass);\n\n // Create a proxy object\n const proxy: any = {};\n\n for (const route of routes) {\n const methodName = route.methodName;\n\n // Create a function that routes through the filter chain\n proxy[methodName] = async (...args: any[]) => {\n return this.invokeRoute(route, args);\n };\n }\n\n return proxy as T;\n }\n\n /**\n * Invoke a route through the filter chain.\n */\n private async invokeRoute(route: RouteMetadata, args: any[]): Promise<any> {\n // Find the registered route\n const key = `${route.httpMethod}:${route.path}`;\n const registeredRoute = this.routes.get(key);\n\n if (!registeredRoute) {\n throw new Error(`Route not found: ${key}`);\n }\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n registeredRoute.controllerFilepath,\n this.filterRegistry\n );\n\n // Create method metadata\n const meta = new MethodMeta(\n route.httpMethod,\n route.path,\n route.methodName,\n [...args],\n new RouteRequest(args[0]), // Assume first arg is the request body\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n meta.params,\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await registeredRoute.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Return the data from the action\n if (action.type === 'error') {\n throw new Error(JSON.stringify(action.data));\n }\n\n return action.data;\n }\n\n /**\n * Get the application DI container (for testing).\n * Returns appContainer which has access to both app and framework bindings.\n */\n getContainer(): Container {\n this.initialize();\n return this.appContainer;\n }\n\n /**\n * Get the WebPieces framework container (for advanced testing/debugging).\n */\n getWebpiecesContainer(): Container {\n this.initialize();\n return this.webpiecesContainer;\n }\n\n\n}\n"]}
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"]}