@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,113 @@
1
+ import { Container } from 'inversify';
2
+ import { WebAppMeta } from '@webpieces/core-meta';
3
+ import { RouteBuilderImpl } from './RouteBuilderImpl';
4
+ import { WebpiecesServer } from './WebpiecesServer';
5
+ /**
6
+ * WebpiecesServerImpl - Internal server implementation.
7
+ *
8
+ * This class implements the WebpiecesServer interface and contains
9
+ * all the actual server logic. It is created by WebpiecesFactory.create().
10
+ *
11
+ * This class uses a two-container pattern similar to Java WebPieces:
12
+ * 1. webpiecesContainer: Core WebPieces framework bindings
13
+ * 2. appContainer: User's application bindings (child of webpiecesContainer)
14
+ *
15
+ * This separation allows:
16
+ * - Clean separation of concerns
17
+ * - Better testability
18
+ * - Ability to override framework bindings in tests
19
+ *
20
+ * The server:
21
+ * 1. Initializes both DI containers from WebAppMeta.getDIModules()
22
+ * 2. Registers routes using explicit RouteBuilderImpl
23
+ * 3. Creates filter chains
24
+ * 4. Supports both HTTP server mode and testing mode (no HTTP)
25
+ *
26
+ * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()
27
+ * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
28
+ */
29
+ export declare class WebpiecesServerImpl implements WebpiecesServer {
30
+ private routeBuilder;
31
+ private meta;
32
+ private webpiecesContainer;
33
+ /**
34
+ * Application container: User's application bindings.
35
+ * This is a child container of webpiecesContainer, so it can access
36
+ * framework bindings while keeping app bindings separate.
37
+ */
38
+ private appContainer;
39
+ private initialized;
40
+ private app?;
41
+ private server?;
42
+ private port;
43
+ constructor(routeBuilder: RouteBuilderImpl);
44
+ /**
45
+ * Initialize the server (DI container, routes, filters).
46
+ * This is called by WebpiecesFactory.create() after resolving this class from DI.
47
+ * This method is internal and not exposed on the WebpiecesServer interface.
48
+ *
49
+ * @param webpiecesContainer - The framework container
50
+ * @param meta - User-provided WebAppMeta with DI modules and routes
51
+ */
52
+ initialize(webpiecesContainer: Container, meta: WebAppMeta): void;
53
+ /**
54
+ * Load DI modules from WebAppMeta.
55
+ *
56
+ * Currently, all user modules are loaded into the application container.
57
+ * In the future, we could separate:
58
+ * - WebPieces framework modules -> webpiecesContainer
59
+ * - Application modules -> appContainer
60
+ *
61
+ * For now, everything goes into appContainer which has access to webpiecesContainer.
62
+ */
63
+ private loadDIModules;
64
+ /**
65
+ * Register routes from WebAppMeta.
66
+ *
67
+ * Creates an explicit RouteBuilderImpl instead of an anonymous object.
68
+ * This improves:
69
+ * - Traceability: Can Cmd+Click on addRoute to see implementation
70
+ * - Debugging: Explicit class shows up in stack traces
71
+ * - Understanding: Clear class name vs anonymous object
72
+ */
73
+ 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
+ /**
88
+ * Start the HTTP server with Express.
89
+ * Returns a Promise that resolves when the server is listening,
90
+ * or rejects if the server fails to start.
91
+ *
92
+ * @param port - The port to listen on (default: 8080)
93
+ * @returns Promise that resolves when server is ready
94
+ */
95
+ start(port?: number): Promise<void>;
96
+ /**
97
+ * Register all routes with Express.
98
+ */
99
+ private registerExpressRoutes;
100
+ /**
101
+ * Setup a single route with Express.
102
+ * Finds matching filters, creates handler, and registers with Express.
103
+ *
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
107
+ */
108
+ private setupRoute;
109
+ /**
110
+ * Stop the HTTP server.
111
+ */
112
+ stop(): void;
113
+ }
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebpiecesServerImpl = 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
+ const MethodMeta_1 = require("./MethodMeta");
15
+ /**
16
+ * WebpiecesServerImpl - Internal server implementation.
17
+ *
18
+ * This class implements the WebpiecesServer interface and contains
19
+ * all the actual server logic. It is created by WebpiecesFactory.create().
20
+ *
21
+ * This class uses a two-container pattern similar to Java WebPieces:
22
+ * 1. webpiecesContainer: Core WebPieces framework bindings
23
+ * 2. appContainer: User's application bindings (child of webpiecesContainer)
24
+ *
25
+ * This separation allows:
26
+ * - Clean separation of concerns
27
+ * - Better testability
28
+ * - Ability to override framework bindings in tests
29
+ *
30
+ * The server:
31
+ * 1. Initializes both DI containers from WebAppMeta.getDIModules()
32
+ * 2. Registers routes using explicit RouteBuilderImpl
33
+ * 3. Creates filter chains
34
+ * 4. Supports both HTTP server mode and testing mode (no HTTP)
35
+ *
36
+ * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()
37
+ * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
38
+ */
39
+ let WebpiecesServerImpl = class WebpiecesServerImpl {
40
+ constructor(routeBuilder) {
41
+ this.routeBuilder = routeBuilder;
42
+ this.initialized = false;
43
+ this.port = 8080;
44
+ }
45
+ /**
46
+ * Initialize the server (DI container, routes, filters).
47
+ * This is called by WebpiecesFactory.create() after resolving this class from DI.
48
+ * This method is internal and not exposed on the WebpiecesServer interface.
49
+ *
50
+ * @param webpiecesContainer - The framework container
51
+ * @param meta - User-provided WebAppMeta with DI modules and routes
52
+ */
53
+ initialize(webpiecesContainer, meta) {
54
+ if (this.initialized) {
55
+ return;
56
+ }
57
+ this.webpiecesContainer = webpiecesContainer;
58
+ this.meta = meta;
59
+ // Create application container as child of framework container
60
+ this.appContainer = new inversify_1.Container({ parent: this.webpiecesContainer });
61
+ // Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)
62
+ this.routeBuilder.setContainer(this.appContainer);
63
+ // 1. Load DI modules
64
+ this.loadDIModules();
65
+ // 2. Register routes and filters
66
+ this.registerRoutes();
67
+ this.initialized = true;
68
+ }
69
+ /**
70
+ * Load DI modules from WebAppMeta.
71
+ *
72
+ * Currently, all user modules are loaded into the application container.
73
+ * In the future, we could separate:
74
+ * - WebPieces framework modules -> webpiecesContainer
75
+ * - Application modules -> appContainer
76
+ *
77
+ * For now, everything goes into appContainer which has access to webpiecesContainer.
78
+ */
79
+ loadDIModules() {
80
+ const modules = this.meta.getDIModules();
81
+ // Load buildProviderModule to auto-scan for @provideSingleton decorators
82
+ this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
83
+ // Load all modules into application container
84
+ // (webpiecesContainer is currently empty, reserved for future framework bindings)
85
+ for (const module of modules) {
86
+ this.appContainer.load(module);
87
+ }
88
+ }
89
+ /**
90
+ * Register routes from WebAppMeta.
91
+ *
92
+ * Creates an explicit RouteBuilderImpl instead of an anonymous object.
93
+ * This improves:
94
+ * - Traceability: Can Cmd+Click on addRoute to see implementation
95
+ * - Debugging: Explicit class shows up in stack traces
96
+ * - Understanding: Clear class name vs anonymous object
97
+ */
98
+ registerRoutes() {
99
+ const routeConfigs = this.meta.getRoutes();
100
+ // Configure routes using the explicit RouteBuilder
101
+ for (const routeConfig of routeConfigs) {
102
+ routeConfig.configure(this.routeBuilder);
103
+ }
104
+ }
105
+ /**
106
+ * Global error handler middleware - catches ALL unhandled errors.
107
+ * Returns HTML 500 error page for any errors that escape the filter chain.
108
+ *
109
+ * This is the outermost safety net - JsonFilter catches JSON API errors,
110
+ * this catches everything else.
111
+ */
112
+ async globalErrorHandler(req, res, next) {
113
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
114
+ try {
115
+ // await next() catches BOTH:
116
+ // 1. Synchronous throws from next() itself
117
+ // 2. Rejected promises from downstream async middleware
118
+ await next();
119
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
120
+ }
121
+ catch (err) {
122
+ const error = (0, core_util_1.toError)(err);
123
+ console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
124
+ if (!res.headersSent) {
125
+ // Return HTML error page (not JSON - JsonFilter handles JSON errors)
126
+ res.status(500).send(`
127
+ <!DOCTYPE html>
128
+ <html>
129
+ <head><title>Server Error</title></head>
130
+ <body>
131
+ <h1>You hit a server error</h1>
132
+ <p>An unexpected error occurred while processing your request.</p>
133
+ <pre>${error.message}</pre>
134
+ </body>
135
+ </html>
136
+ `);
137
+ }
138
+ console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
139
+ }
140
+ }
141
+ /**
142
+ * Logging middleware - logs request/response flow.
143
+ * Demonstrates middleware execution order.
144
+ */
145
+ logNextLayer(req, res, next) {
146
+ console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
147
+ next();
148
+ console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
149
+ }
150
+ /**
151
+ * Start the HTTP server with Express.
152
+ * Returns a Promise that resolves when the server is listening,
153
+ * or rejects if the server fails to start.
154
+ *
155
+ * @param port - The port to listen on (default: 8080)
156
+ * @returns Promise that resolves when server is ready
157
+ */
158
+ start(port = 8080) {
159
+ if (!this.initialized) {
160
+ return Promise.reject(new Error('Server not initialized. Call initialize() before start().'));
161
+ }
162
+ this.port = port;
163
+ // Create Express app
164
+ this.app = (0, express_1.default)();
165
+ // Parse JSON request bodies
166
+ this.app.use(express_1.default.json());
167
+ // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)
168
+ // Catches all unhandled errors and returns HTML 500 page
169
+ this.app.use(this.globalErrorHandler.bind(this));
170
+ // Layer 2: Request/Response Logging
171
+ this.app.use(this.logNextLayer.bind(this));
172
+ // Register routes (these become the innermost handlers)
173
+ this.registerExpressRoutes();
174
+ const routes = this.routeBuilder.getRoutes();
175
+ // Start listening - wrap in Promise
176
+ return new Promise((resolve, reject) => {
177
+ this.server = this.app.listen(this.port, (error) => {
178
+ if (error) {
179
+ console.error(`[WebpiecesServer] Failed to start server:`, error);
180
+ reject(error);
181
+ return;
182
+ }
183
+ console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
184
+ console.log(`[WebpiecesServer] Registered ${routes.size} routes`);
185
+ resolve();
186
+ });
187
+ });
188
+ }
189
+ /**
190
+ * Register all routes with Express.
191
+ */
192
+ registerExpressRoutes() {
193
+ if (!this.app) {
194
+ throw new Error('Express app not initialized');
195
+ }
196
+ const routes = this.routeBuilder.getRoutes();
197
+ const sortedFilters = this.routeBuilder.getSortedFilters();
198
+ for (const [key, routeWithMeta] of routes.entries()) {
199
+ this.setupRoute(key, routeWithMeta, sortedFilters);
200
+ }
201
+ }
202
+ /**
203
+ * Setup a single route with Express.
204
+ * Finds matching filters, creates handler, and registers with Express.
205
+ *
206
+ * @param key - Route key (method:path)
207
+ * @param routeWithMeta - The route handler paired with its definition
208
+ * @param filtersWithMeta - All filters with their definitions
209
+ */
210
+ setupRoute(key, routeWithMeta, filtersWithMeta) {
211
+ if (!this.app) {
212
+ throw new Error('Express app not initialized');
213
+ }
214
+ const route = routeWithMeta.definition;
215
+ const routeMeta = route.routeMeta;
216
+ const method = routeMeta.httpMethod.toLowerCase();
217
+ const path = routeMeta.path;
218
+ console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);
219
+ // Find matching filters for this route - FilterMatcher returns Filter[] not FilterWithMeta[]
220
+ // So we need to convert our FilterWithMeta[] to what FilterMatcher expects
221
+ const filterDefinitions = filtersWithMeta.map(fwm => {
222
+ // Set the filter instance on the definition for FilterMatcher
223
+ const def = fwm.definition;
224
+ def.filter = fwm.filter;
225
+ return def;
226
+ });
227
+ const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, filterDefinitions);
228
+ // Create service that wraps the controller execution
229
+ const controllerService = {
230
+ invoke: async (meta) => {
231
+ // Invoke the controller method via route handler
232
+ const result = await routeWithMeta.handler.execute(meta);
233
+ const responseWrapper = new http_filters_1.WpResponse(result);
234
+ return responseWrapper;
235
+ }
236
+ };
237
+ // Chain filters with the controller service (reverse order for correct execution)
238
+ // IMPORTANT: MUST USE Filter.chain(filter) and Filter.chainService(svc);
239
+ let filterChain = matchingFilters[matchingFilters.length - 1];
240
+ for (let i = matchingFilters.length - 2; i >= 0; i--) {
241
+ filterChain = filterChain.chain(matchingFilters[i]);
242
+ }
243
+ const svc = filterChain.chainService(controllerService);
244
+ // Create Express route handler - delegates to filter chain
245
+ const handler = async (req, res, next) => {
246
+ // Create RouteRequest with Express Request/Response
247
+ const routeRequest = new core_meta_1.RouteRequest(req, res);
248
+ // Create MethodMeta with route info and Express Request/Response
249
+ const meta = new MethodMeta_1.MethodMeta(routeMeta, routeRequest);
250
+ // Response is written by JsonFilter - we just await completion
251
+ await svc.invoke(meta);
252
+ };
253
+ // Register with Express
254
+ switch (method) {
255
+ case 'get':
256
+ this.app.get(path, handler);
257
+ break;
258
+ case 'post':
259
+ this.app.post(path, handler);
260
+ break;
261
+ case 'put':
262
+ this.app.put(path, handler);
263
+ break;
264
+ case 'delete':
265
+ this.app.delete(path, handler);
266
+ break;
267
+ case 'patch':
268
+ this.app.patch(path, handler);
269
+ break;
270
+ default:
271
+ console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);
272
+ }
273
+ }
274
+ /**
275
+ * Stop the HTTP server.
276
+ */
277
+ stop() {
278
+ if (this.server) {
279
+ this.server.close(() => {
280
+ console.log('[WebpiecesServer] Server stopped');
281
+ });
282
+ }
283
+ }
284
+ };
285
+ exports.WebpiecesServerImpl = WebpiecesServerImpl;
286
+ exports.WebpiecesServerImpl = WebpiecesServerImpl = tslib_1.__decorate([
287
+ (0, http_routing_1.provideSingleton)(),
288
+ (0, inversify_1.injectable)(),
289
+ tslib_1.__param(0, (0, inversify_1.inject)(RouteBuilderImpl_1.RouteBuilderImpl)),
290
+ tslib_1.__metadata("design:paramtypes", [RouteBuilderImpl_1.RouteBuilderImpl])
291
+ ], WebpiecesServerImpl);
292
+ //# sourceMappingURL=WebpiecesServerImpl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebpiecesServerImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServerImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import express, {Express, NextFunction, Request, Response} from 'express';\nimport {Container, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {RouteRequest, WebAppMeta} from '@webpieces/core-meta';\nimport {WpResponse, Service} from '@webpieces/http-filters';\nimport {provideSingleton} from '@webpieces/http-routing';\nimport {RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta} from './RouteBuilderImpl';\nimport {FilterMatcher} from './FilterMatcher';\nimport {toError} from '@webpieces/core-util';\nimport {MethodMeta} from './MethodMeta';\nimport {WebpiecesServer} from './WebpiecesServer';\n\n/**\n * WebpiecesServerImpl - Internal server implementation.\n *\n * This class implements the WebpiecesServer interface and contains\n * all the actual server logic. It is created by WebpiecesFactory.create().\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()\n * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.\n */\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\n private meta!: WebAppMeta;\n private webpiecesContainer!: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer!: Container;\n\n private initialized = false;\n private app?: Express;\n private server?: ReturnType<Express['listen']>;\n private port: number = 8080;\n\n constructor(\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl\n ) {\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called by WebpiecesFactory.create() after resolving this class from DI.\n * This method is internal and not exposed on the WebpiecesServer interface.\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n */\n initialize(webpiecesContainer: Container, meta: WebAppMeta): void {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\n this.meta = meta;\n\n // Create application container as child of framework container\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n\n // Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)\n this.routeBuilder.setContainer(this.appContainer);\n\n // 1. Load DI modules\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 // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(this.routeBuilder);\n }\n }\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 - JsonFilter catches JSON API errors,\n * this catches everything else.\n */\n private 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('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);\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 - 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 /**\n * Logging middleware - logs request/response flow.\n * Demonstrates middleware execution order.\n */\n private logNextLayer(req: Request, res: Response, next: NextFunction): void {\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 /**\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 = 8080): Promise<void> {\n if (!this.initialized) {\n return Promise.reject(new Error('Server not initialized. Call initialize() before start().'));\n }\n\n this.port = port;\n\n // Create Express app\n this.app = express();\n\n // Parse JSON request bodies\n this.app.use(express.json());\n\n // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)\n // Catches all unhandled errors and returns HTML 500 page\n this.app.use(this.globalErrorHandler.bind(this));\n\n // Layer 2: Request/Response Logging\n this.app.use(this.logNextLayer.bind(this));\n\n // Register routes (these become the innermost handlers)\n this.registerExpressRoutes();\n\n const routes = this.routeBuilder.getRoutes();\n\n // Start listening - wrap in Promise\n return new Promise((resolve, reject) => {\n this.server = this.app!.listen(this.port, (error?: Error) => {\n if (error) {\n console.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${routes.size} routes`);\n resolve();\n });\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 const routes = this.routeBuilder.getRoutes();\n const sortedFilters = this.routeBuilder.getSortedFilters();\n for (const [key, routeWithMeta] of routes.entries()) {\n this.setupRoute(key, routeWithMeta, sortedFilters);\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 routeWithMeta - The route handler paired with its definition\n * @param filtersWithMeta - All filters with their definitions\n */\n private setupRoute(key: string, routeWithMeta: RouteHandlerWithMeta, filtersWithMeta: Array<FilterWithMeta>): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n const method = routeMeta.httpMethod.toLowerCase();\n const path = routeMeta.path;\n\n console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);\n\n // Find matching filters for this route - FilterMatcher returns Filter[] not FilterWithMeta[]\n // So we need to convert our FilterWithMeta[] to what FilterMatcher expects\n const filterDefinitions = filtersWithMeta.map(fwm => {\n // Set the filter instance on the definition for FilterMatcher\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse> => {\n // Invoke the controller method via route handler\n const result = await routeWithMeta.handler.execute(meta);\n const responseWrapper = new WpResponse(result);\n return responseWrapper;\n }\n };\n\n // Chain filters with the controller service (reverse order for correct execution)\n // IMPORTANT: MUST USE Filter.chain(filter) and Filter.chainService(svc);\n let filterChain = matchingFilters[matchingFilters.length - 1];\n for (let i = matchingFilters.length - 2; i >= 0; i--) {\n filterChain = filterChain.chain(matchingFilters[i]);\n }\n const svc = filterChain.chainService(controllerService);\n\n // Create Express route handler - delegates to filter chain\n const handler = async (req: Request, res: Response, next: NextFunction) => {\n // Create RouteRequest with Express Request/Response\n const routeRequest = new RouteRequest(req, res);\n\n // Create MethodMeta with route info and Express Request/Response\n const meta = new MethodMeta(\n routeMeta,\n routeRequest\n );\n\n // Response is written by JsonFilter - we just await completion\n await svc.invoke(meta);\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"]}
@@ -0,0 +1,12 @@
1
+ import { Filter, WpResponse, Service } from '@webpieces/http-filters';
2
+ import { MethodMeta } from '../MethodMeta';
3
+ /**
4
+ * ContextFilter - Sets up AsyncLocalStorage context for each request.
5
+ * Priority: 140 (executes first)
6
+ *
7
+ * This filter ensures that all subsequent filters and the controller
8
+ * execute within a context that can store request-scoped data.
9
+ */
10
+ export declare class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {
11
+ filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
12
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContextFilter = 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 core_context_1 = require("@webpieces/core-context");
8
+ const http_filters_1 = require("@webpieces/http-filters");
9
+ /**
10
+ * ContextFilter - Sets up AsyncLocalStorage context for each request.
11
+ * Priority: 140 (executes first)
12
+ *
13
+ * This filter ensures that all subsequent filters and the controller
14
+ * execute within a context that can store request-scoped data.
15
+ */
16
+ let ContextFilter = class ContextFilter extends http_filters_1.Filter {
17
+ async filter(meta, nextFilter) {
18
+ // Run the rest of the filter chain within a new context
19
+ return core_context_1.RequestContext.run(async () => {
20
+ // Store request metadata in context for other filters to access
21
+ core_context_1.RequestContext.put('METHOD_META', meta);
22
+ core_context_1.RequestContext.put('REQUEST_PATH', meta.path);
23
+ core_context_1.RequestContext.put('HTTP_METHOD', meta.httpMethod);
24
+ return await nextFilter.invoke(meta);
25
+ //RequestContext is auto cleared when done.
26
+ });
27
+ }
28
+ };
29
+ exports.ContextFilter = ContextFilter;
30
+ exports.ContextFilter = ContextFilter = tslib_1.__decorate([
31
+ (0, http_routing_1.provideSingleton)(),
32
+ (0, inversify_1.injectable)()
33
+ ], ContextFilter);
34
+ //# sourceMappingURL=ContextFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAA2D;AAC3D,0DAAyD;AACzD,0DAAsE;AAGtE;;;;;;GAMG;AAGI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAuC;IAExE,KAAK,CAAC,MAAM,CACV,IAAgB,EAChB,UAAoD;QAEpD,wDAAwD;QACxD,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YACnC,gEAAgE;YAChE,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;YACxC,6BAAc,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,6BAAc,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAEnD,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,2CAA2C;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AAjBY,sCAAa;wBAAb,aAAa;IAFzB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,aAAa,CAiBzB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport { MethodMeta } from '../MethodMeta';\n\n/**\n * ContextFilter - Sets up AsyncLocalStorage context for each request.\n * Priority: 140 (executes first)\n *\n * This filter ensures that all subsequent filters and the controller\n * execute within a context that can store request-scoped data.\n */\n@provideSingleton()\n@injectable()\nexport class ContextFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>\n ): Promise<WpResponse<unknown>> {\n // Run the rest of the filter chain within a new context\n return RequestContext.run(async () => {\n // Store request metadata in context for other filters to access\n RequestContext.put('METHOD_META', meta);\n RequestContext.put('REQUEST_PATH', meta.path);\n RequestContext.put('HTTP_METHOD', meta.httpMethod);\n\n return await nextFilter.invoke(meta);\n //RequestContext is auto cleared when done.\n });\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { Filter, WpResponse, Service } from '@webpieces/http-filters';
2
+ import { MethodMeta } from '../MethodMeta';
3
+ /**
4
+ * DI tokens for JsonFilter.
5
+ */
6
+ export declare const FILTER_TYPES: {
7
+ JsonFilterConfig: symbol;
8
+ };
9
+ /**
10
+ * Configuration for JsonFilter.
11
+ * Register this in your DI container to customize JsonFilter behavior.
12
+ */
13
+ export declare class JsonFilterConfig {
14
+ }
15
+ /**
16
+ * JsonFilter - Handles JSON serialization/deserialization and writes response to HTTP body.
17
+ *
18
+ * Similar to Java WebPieces JacksonCatchAllFilter.
19
+ *
20
+ * Flow:
21
+ * 1. Log request
22
+ * 2. Deserialize request body to DTO and set on meta.requestDto
23
+ * 3. Call next filter/controller
24
+ * 4. Get response (WpResponse)
25
+ * 5. Write response to Express response
26
+ * 6. On ANY exception, send 500
27
+ */
28
+ export declare class JsonFilter extends Filter<MethodMeta, WpResponse<unknown>> {
29
+ private config;
30
+ constructor(config: JsonFilterConfig);
31
+ filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
32
+ /**
33
+ * Deserialize request body to DTO and set on meta.requestDto.
34
+ */
35
+ private deserializeRequest;
36
+ /**
37
+ * Write WpResponse to HTTP response body as JSON.
38
+ */
39
+ private writeResponse;
40
+ /**
41
+ * Log the incoming request.
42
+ */
43
+ private logRequest;
44
+ /**
45
+ * Log the outgoing response.
46
+ */
47
+ private logResponse;
48
+ }
49
+ /**
50
+ * Exception thrown when validation fails.
51
+ */
52
+ export declare class ValidationException extends Error {
53
+ violations: string[];
54
+ constructor(violations: string[]);
55
+ }
56
+ /**
57
+ * HTTP exception with status code.
58
+ */
59
+ export declare class HttpException extends Error {
60
+ statusCode: number;
61
+ constructor(message: string, statusCode: number);
62
+ }