@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 +4 -5
- package/src/WebpiecesFactory.d.ts +23 -4
- package/src/WebpiecesFactory.js +25 -8
- package/src/WebpiecesFactory.js.map +1 -1
- package/src/WebpiecesMiddleware.d.ts +59 -0
- package/src/WebpiecesMiddleware.js +159 -0
- package/src/WebpiecesMiddleware.js.map +1 -0
- package/src/WebpiecesServer.d.ts +42 -5
- package/src/WebpiecesServer.js.map +1 -1
- package/src/WebpiecesServerImpl.d.ts +57 -28
- package/src/WebpiecesServerImpl.js +136 -135
- package/src/WebpiecesServerImpl.js.map +1 -1
- package/src/filters/ContextFilter.d.ts +1 -1
- package/src/filters/ContextFilter.js.map +1 -1
- package/src/filters/LogApiFilter.d.ts +40 -0
- package/src/filters/LogApiFilter.js +92 -0
- package/src/filters/LogApiFilter.js.map +1 -0
- package/src/index.d.ts +3 -4
- package/src/index.js +13 -15
- package/src/index.js.map +1 -1
- package/src/FilterMatcher.d.ts +0 -39
- package/src/FilterMatcher.js +0 -69
- package/src/FilterMatcher.js.map +0 -1
- package/src/MethodMeta.d.ts +0 -42
- package/src/MethodMeta.js +0 -40
- package/src/MethodMeta.js.map +0 -1
- package/src/RouteBuilderImpl.d.ts +0 -90
- package/src/RouteBuilderImpl.js +0 -141
- package/src/RouteBuilderImpl.js.map +0 -1
- package/src/RouteHandler.d.ts +0 -22
- package/src/RouteHandler.js +0 -20
- package/src/RouteHandler.js.map +0 -1
- package/src/filters/JsonFilter.d.ts +0 -62
- package/src/filters/JsonFilter.js +0 -146
- package/src/filters/JsonFilter.js.map +0 -1
|
@@ -5,13 +5,8 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const express_1 = tslib_1.__importDefault(require("express"));
|
|
6
6
|
const inversify_1 = require("inversify");
|
|
7
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
8
|
const http_routing_1 = require("@webpieces/http-routing");
|
|
11
|
-
const
|
|
12
|
-
const FilterMatcher_1 = require("./FilterMatcher");
|
|
13
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
14
|
-
const MethodMeta_1 = require("./MethodMeta");
|
|
9
|
+
const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
|
|
15
10
|
/**
|
|
16
11
|
* WebpiecesServerImpl - Internal server implementation.
|
|
17
12
|
*
|
|
@@ -37,10 +32,11 @@ const MethodMeta_1 = require("./MethodMeta");
|
|
|
37
32
|
* and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
|
|
38
33
|
*/
|
|
39
34
|
let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
40
|
-
constructor(routeBuilder) {
|
|
35
|
+
constructor(routeBuilder, middleware) {
|
|
41
36
|
this.routeBuilder = routeBuilder;
|
|
37
|
+
this.middleware = middleware;
|
|
42
38
|
this.initialized = false;
|
|
43
|
-
this.port =
|
|
39
|
+
this.port = 8200;
|
|
44
40
|
}
|
|
45
41
|
/**
|
|
46
42
|
* Initialize the server (DI container, routes, filters).
|
|
@@ -49,8 +45,17 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
49
45
|
*
|
|
50
46
|
* @param webpiecesContainer - The framework container
|
|
51
47
|
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
48
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST)
|
|
52
49
|
*/
|
|
53
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the server asynchronously.
|
|
52
|
+
* Use this when overrides module contains async operations (e.g., rebind() in new Inversify).
|
|
53
|
+
*
|
|
54
|
+
* @param webpiecesContainer - The framework container
|
|
55
|
+
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
56
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST)
|
|
57
|
+
*/
|
|
58
|
+
async initialize(webpiecesContainer, meta, overrides) {
|
|
54
59
|
if (this.initialized) {
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
@@ -60,8 +65,8 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
60
65
|
this.appContainer = new inversify_1.Container({ parent: this.webpiecesContainer });
|
|
61
66
|
// Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)
|
|
62
67
|
this.routeBuilder.setContainer(this.appContainer);
|
|
63
|
-
// 1. Load DI modules
|
|
64
|
-
this.loadDIModules();
|
|
68
|
+
// 1. Load DI modules asynchronously
|
|
69
|
+
await this.loadDIModules(overrides);
|
|
65
70
|
// 2. Register routes and filters
|
|
66
71
|
this.registerRoutes();
|
|
67
72
|
this.initialized = true;
|
|
@@ -75,15 +80,21 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
75
80
|
* - Application modules -> appContainer
|
|
76
81
|
*
|
|
77
82
|
* For now, everything goes into appContainer which has access to webpiecesContainer.
|
|
83
|
+
*
|
|
84
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
|
|
78
85
|
*/
|
|
79
|
-
loadDIModules() {
|
|
86
|
+
async loadDIModules(overrides) {
|
|
80
87
|
const modules = this.meta.getDIModules();
|
|
81
88
|
// Load buildProviderModule to auto-scan for @provideSingleton decorators
|
|
82
|
-
this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
89
|
+
await this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
83
90
|
// Load all modules into application container
|
|
84
91
|
// (webpiecesContainer is currently empty, reserved for future framework bindings)
|
|
85
92
|
for (const module of modules) {
|
|
86
|
-
this.appContainer.load(module);
|
|
93
|
+
await this.appContainer.load(module);
|
|
94
|
+
}
|
|
95
|
+
// Load overrides LAST so they can override existing bindings
|
|
96
|
+
if (overrides) {
|
|
97
|
+
await this.appContainer.load(overrides);
|
|
87
98
|
}
|
|
88
99
|
}
|
|
89
100
|
/**
|
|
@@ -102,51 +113,6 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
102
113
|
routeConfig.configure(this.routeBuilder);
|
|
103
114
|
}
|
|
104
115
|
}
|
|
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
116
|
/**
|
|
151
117
|
* Start the HTTP server with Express.
|
|
152
118
|
* Returns a Promise that resolves when the server is listening,
|
|
@@ -155,25 +121,27 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
155
121
|
* @param port - The port to listen on (default: 8080)
|
|
156
122
|
* @returns Promise that resolves when server is ready
|
|
157
123
|
*/
|
|
158
|
-
start(port =
|
|
124
|
+
async start(port = 8200, testMode) {
|
|
159
125
|
if (!this.initialized) {
|
|
160
|
-
|
|
126
|
+
throw new Error('Server not initialized. Call initialize() before start().');
|
|
161
127
|
}
|
|
162
128
|
this.port = port;
|
|
129
|
+
if (testMode) {
|
|
130
|
+
//In testMode, we eliminate express ENTIRELY and use
|
|
131
|
+
//Router, method filters and controllers so that we can test full stack
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
163
134
|
// Create Express app
|
|
164
135
|
this.app = (0, express_1.default)();
|
|
165
|
-
// Parse JSON request bodies
|
|
166
|
-
this.app.use(express_1.default.json());
|
|
167
136
|
// Layer 1: Global Error Handler (OUTERMOST - runs FIRST)
|
|
168
137
|
// Catches all unhandled errors and returns HTML 500 page
|
|
169
|
-
this.app.use(this.globalErrorHandler.bind(this));
|
|
138
|
+
this.app.use(this.middleware.globalErrorHandler.bind(this.middleware));
|
|
170
139
|
// Layer 2: Request/Response Logging
|
|
171
|
-
this.app.use(this.logNextLayer.bind(this));
|
|
172
|
-
// Register routes
|
|
173
|
-
this.registerExpressRoutes();
|
|
174
|
-
const routes = this.routeBuilder.getRoutes();
|
|
140
|
+
this.app.use(this.middleware.logNextLayer.bind(this.middleware));
|
|
141
|
+
// Register routes
|
|
142
|
+
const routeCount = this.registerExpressRoutes();
|
|
175
143
|
// Start listening - wrap in Promise
|
|
176
|
-
|
|
144
|
+
const promise = new Promise((resolve, reject) => {
|
|
177
145
|
this.server = this.app.listen(this.port, (error) => {
|
|
178
146
|
if (error) {
|
|
179
147
|
console.error(`[WebpiecesServer] Failed to start server:`, error);
|
|
@@ -181,112 +149,145 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
181
149
|
return;
|
|
182
150
|
}
|
|
183
151
|
console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
|
|
184
|
-
console.log(`[WebpiecesServer] Registered ${
|
|
152
|
+
console.log(`[WebpiecesServer] Registered ${routeCount} routes`);
|
|
185
153
|
resolve();
|
|
186
154
|
});
|
|
187
155
|
});
|
|
156
|
+
await promise;
|
|
188
157
|
}
|
|
189
158
|
/**
|
|
190
|
-
* Register
|
|
159
|
+
* Register Express routes - the SINGLE loop over routes.
|
|
160
|
+
* For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.
|
|
161
|
+
*
|
|
162
|
+
* @returns Number of routes registered
|
|
191
163
|
*/
|
|
192
164
|
registerExpressRoutes() {
|
|
193
165
|
if (!this.app) {
|
|
194
166
|
throw new Error('Express app not initialized');
|
|
195
167
|
}
|
|
196
168
|
const routes = this.routeBuilder.getRoutes();
|
|
197
|
-
|
|
198
|
-
for (const
|
|
199
|
-
this.
|
|
169
|
+
let count = 0;
|
|
170
|
+
for (const routeWithMeta of routes) {
|
|
171
|
+
const service = this.routeBuilder.createRouteHandler(routeWithMeta);
|
|
172
|
+
const routeMeta = routeWithMeta.definition.routeMeta;
|
|
173
|
+
// Create ExpressWrapper directly (handles full request/response cycle)
|
|
174
|
+
const wrapper = this.middleware.createExpressWrapper(service, routeMeta);
|
|
175
|
+
this.registerHandler(routeMeta.httpMethod, routeMeta.path, wrapper.execute.bind(wrapper));
|
|
176
|
+
count++;
|
|
200
177
|
}
|
|
178
|
+
return count;
|
|
201
179
|
}
|
|
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) {
|
|
180
|
+
registerHandler(httpMethod, path, expressHandler) {
|
|
211
181
|
if (!this.app) {
|
|
212
182
|
throw new Error('Express app not initialized');
|
|
213
183
|
}
|
|
214
|
-
|
|
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) {
|
|
184
|
+
switch (httpMethod.toLowerCase()) {
|
|
255
185
|
case 'get':
|
|
256
|
-
this.app.get(path,
|
|
186
|
+
this.app.get(path, expressHandler);
|
|
257
187
|
break;
|
|
258
188
|
case 'post':
|
|
259
|
-
this.app.post(path,
|
|
189
|
+
this.app.post(path, expressHandler);
|
|
260
190
|
break;
|
|
261
191
|
case 'put':
|
|
262
|
-
this.app.put(path,
|
|
192
|
+
this.app.put(path, expressHandler);
|
|
263
193
|
break;
|
|
264
194
|
case 'delete':
|
|
265
|
-
this.app.delete(path,
|
|
195
|
+
this.app.delete(path, expressHandler);
|
|
266
196
|
break;
|
|
267
197
|
case 'patch':
|
|
268
|
-
this.app.patch(path,
|
|
198
|
+
this.app.patch(path, expressHandler);
|
|
269
199
|
break;
|
|
270
200
|
default:
|
|
271
|
-
console.warn(`[WebpiecesServer] Unknown HTTP method: ${
|
|
201
|
+
console.warn(`[WebpiecesServer] Unknown HTTP method: ${httpMethod}`);
|
|
272
202
|
}
|
|
273
203
|
}
|
|
274
204
|
/**
|
|
275
205
|
* Stop the HTTP server.
|
|
206
|
+
* Returns a Promise that resolves when the server is stopped,
|
|
207
|
+
* or rejects if there's an error stopping the server.
|
|
208
|
+
*
|
|
209
|
+
* @returns Promise that resolves when server is stopped
|
|
276
210
|
*/
|
|
277
|
-
stop() {
|
|
278
|
-
if (this.server) {
|
|
279
|
-
|
|
211
|
+
async stop() {
|
|
212
|
+
if (!this.server) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
this.server.close((err) => {
|
|
217
|
+
if (err) {
|
|
218
|
+
console.error('[WebpiecesServer] Error stopping server:', err);
|
|
219
|
+
reject(err);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
280
222
|
console.log('[WebpiecesServer] Server stopped');
|
|
223
|
+
resolve();
|
|
281
224
|
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get the application DI container.
|
|
229
|
+
*
|
|
230
|
+
* Useful for testing to verify state or access services directly.
|
|
231
|
+
*
|
|
232
|
+
* @returns The application Container
|
|
233
|
+
*/
|
|
234
|
+
getContainer() {
|
|
235
|
+
return this.appContainer;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create an API client proxy for testing.
|
|
239
|
+
*
|
|
240
|
+
* This creates a client that routes calls through the full filter chain
|
|
241
|
+
* and controller, but WITHOUT any HTTP overhead. Perfect for testing!
|
|
242
|
+
*
|
|
243
|
+
* The client uses the ApiPrototype class to discover routes via decorators,
|
|
244
|
+
* then creates pre-configured invoker functions for each API method.
|
|
245
|
+
*
|
|
246
|
+
* IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
|
|
247
|
+
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
248
|
+
* so subsequent calls reuse the same filter chain (efficient!).
|
|
249
|
+
*
|
|
250
|
+
* @param apiPrototype - The API prototype class with routing decorators (can be abstract)
|
|
251
|
+
* @returns A proxy that implements the API interface
|
|
252
|
+
*
|
|
253
|
+
* Example:
|
|
254
|
+
* ```typescript
|
|
255
|
+
* const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
|
|
256
|
+
* const response = await saveApi.save(request);
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
createApiClient(apiPrototype) {
|
|
260
|
+
if (!this.initialized) {
|
|
261
|
+
throw new Error('Server not initialized. Call initialize() before createApiClient().');
|
|
262
|
+
}
|
|
263
|
+
// Get routes from the API prototype using decorators (loops over API methods, NOT all routes)
|
|
264
|
+
const apiMethods = (0, http_routing_1.getRoutes)(apiPrototype);
|
|
265
|
+
// Create proxy object
|
|
266
|
+
const proxy = {};
|
|
267
|
+
// Loop over API methods and create proxy functions
|
|
268
|
+
for (const routeMeta of apiMethods) {
|
|
269
|
+
const methodName = routeMeta.methodName;
|
|
270
|
+
const httpMethod = routeMeta.httpMethod.toUpperCase();
|
|
271
|
+
const path = routeMeta.path;
|
|
272
|
+
// Create invoker service ONCE (sets up filter chain once, not on every call!)
|
|
273
|
+
const service = this.routeBuilder.createRouteInvoker(httpMethod, path);
|
|
274
|
+
// Proxy method creates MethodMeta and calls the pre-configured service
|
|
275
|
+
proxy[methodName] = async (requestDto) => {
|
|
276
|
+
const meta = new http_routing_1.MethodMeta(routeMeta, requestDto);
|
|
277
|
+
const responseWrapper = await service.invoke(meta);
|
|
278
|
+
return responseWrapper.response;
|
|
279
|
+
};
|
|
282
280
|
}
|
|
281
|
+
return proxy;
|
|
283
282
|
}
|
|
284
283
|
};
|
|
285
284
|
exports.WebpiecesServerImpl = WebpiecesServerImpl;
|
|
286
285
|
exports.WebpiecesServerImpl = WebpiecesServerImpl = tslib_1.__decorate([
|
|
287
286
|
(0, http_routing_1.provideSingleton)(),
|
|
288
287
|
(0, inversify_1.injectable)(),
|
|
289
|
-
tslib_1.__param(0, (0, inversify_1.inject)(
|
|
290
|
-
tslib_1.
|
|
288
|
+
tslib_1.__param(0, (0, inversify_1.inject)(http_routing_1.RouteBuilderImpl)),
|
|
289
|
+
tslib_1.__param(1, (0, inversify_1.inject)(WebpiecesMiddleware_1.WebpiecesMiddleware)),
|
|
290
|
+
tslib_1.__metadata("design:paramtypes", [http_routing_1.RouteBuilderImpl,
|
|
291
|
+
WebpiecesMiddleware_1.WebpiecesMiddleware])
|
|
291
292
|
], WebpiecesServerImpl);
|
|
292
293
|
//# sourceMappingURL=WebpiecesServerImpl.js.map
|
|
@@ -1 +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"]}
|
|
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, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n ExpressRouteHandler,\n getRoutes,\n MethodMeta,\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\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 = 8200;\n\n constructor(\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\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 * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n /**\n * Initialize the server asynchronously.\n * Use this when overrides module contains async operations (e.g., rebind() in new Inversify).\n *\n * @param webpiecesContainer - The framework container\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n meta: WebAppMeta,\n overrides?: ContainerModule\n ): Promise<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 asynchronously\n await this.loadDIModules(overrides);\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n *\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(overrides?: ContainerModule): Promise<void> {\n const modules = this.meta.getDIModules();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n await this.appContainer.load(module);\n }\n\n // Load overrides LAST so they can override existing bindings\n if (overrides) {\n await this.appContainer.load(overrides);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(this.routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n * Returns a Promise that resolves when the server is listening,\n * or rejects if the server fails to start.\n *\n * @param port - The port to listen on (default: 8080)\n * @returns Promise that resolves when server is ready\n */\n async start(port: number = 8200, testMode?: boolean): Promise<void> {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before start().');\n }\n\n this.port = port;\n\n if(testMode) {\n //In testMode, we eliminate express ENTIRELY and use\n //Router, method filters and controllers so that we can test full stack\n return;\n }\n\n // Create Express app\n this.app = express();\n\n // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)\n // Catches all unhandled errors and returns HTML 500 page\n this.app.use(this.middleware.globalErrorHandler.bind(this.middleware));\n\n // Layer 2: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes\n const routeCount = this.registerExpressRoutes();\n\n // Start listening - wrap in Promise\n const promise = new Promise<void>((resolve, reject) => {\n this.server = this.app!.listen(this.port, (error?: Error) => {\n if (error) {\n console.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\n }\n\n /**\n * Register Express routes - the SINGLE loop over routes.\n * For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.\n *\n * @returns Number of routes registered\n */\n private registerExpressRoutes(): number {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n const routes = this.routeBuilder.getRoutes();\n let count = 0;\n\n for (const routeWithMeta of routes) {\n const service = this.routeBuilder.createRouteHandler(routeWithMeta);\n const routeMeta = routeWithMeta.definition.routeMeta;\n\n // Create ExpressWrapper directly (handles full request/response cycle)\n const wrapper = this.middleware.createExpressWrapper(service, routeMeta);\n\n this.registerHandler(\n routeMeta.httpMethod,\n routeMeta.path,\n wrapper.execute.bind(wrapper),\n );\n count++;\n }\n\n return count;\n }\n\n registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler) {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n switch (httpMethod.toLowerCase()) {\n case 'get':\n this.app.get(path, expressHandler);\n break;\n case 'post':\n this.app.post(path, expressHandler);\n break;\n case 'put':\n this.app.put(path, expressHandler);\n break;\n case 'delete':\n this.app.delete(path, expressHandler);\n break;\n case 'patch':\n this.app.patch(path, expressHandler);\n break;\n default:\n console.warn(`[WebpiecesServer] Unknown HTTP method: ${httpMethod}`);\n }\n }\n\n /**\n * Stop the HTTP server.\n * Returns a Promise that resolves when the server is stopped,\n * or rejects if there's an error stopping the server.\n *\n * @returns Promise that resolves when server is stopped\n */\n async stop(): Promise<void> {\n if (!this.server) {\n return;\n }\n\n return new Promise<void>((resolve, reject) => {\n this.server!.close((err?: Error) => {\n if (err) {\n console.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n console.log('[WebpiecesServer] Server stopped');\n resolve();\n });\n });\n }\n\n /**\n * Get the application DI container.\n *\n * Useful for testing to verify state or access services directly.\n *\n * @returns The application Container\n */\n getContainer(): Container {\n return this.appContainer;\n }\n\n /**\n * Create an API client proxy for testing.\n *\n * This creates a client that routes calls through the full filter chain\n * and controller, but WITHOUT any HTTP overhead. Perfect for testing!\n *\n * The client uses the ApiPrototype class to discover routes via decorators,\n * then creates pre-configured invoker functions for each API method.\n *\n * IMPORTANT: This loops over the API methods (from decorators), NOT all routes.\n * For each API method, it sets up the filter chain ONCE during proxy creation,\n * so subsequent calls reuse the same filter chain (efficient!).\n *\n * @param apiPrototype - The API prototype class with routing decorators (can be abstract)\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n */\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n if (!this.initialized) {\n throw new Error('Server not initialized. Call initialize() before createApiClient().');\n }\n\n // Get routes from the API prototype using decorators (loops over API methods, NOT all routes)\n const apiMethods = getRoutes(apiPrototype);\n\n // Create proxy object\n const proxy: Record<string, unknown> = {};\n\n // Loop over API methods and create proxy functions\n for (const routeMeta of apiMethods) {\n const methodName = routeMeta.methodName;\n const httpMethod = routeMeta.httpMethod.toUpperCase();\n const path = routeMeta.path;\n\n // Create invoker service ONCE (sets up filter chain once, not on every call!)\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // Proxy method creates MethodMeta and calls the pre-configured service\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n const meta = new MethodMeta(routeMeta, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n };\n }\n\n return proxy as T;\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { MethodMeta } from '@webpieces/http-routing';
|
|
1
2
|
import { Filter, WpResponse, Service } from '@webpieces/http-filters';
|
|
2
|
-
import { MethodMeta } from '../MethodMeta';
|
|
3
3
|
/**
|
|
4
4
|
* ContextFilter - Sets up AsyncLocalStorage context for each request.
|
|
5
5
|
* Priority: 140 (executes first)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,
|
|
1
|
+
{"version":3,"file":"ContextFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/ContextFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAAuE;AACvE,0DAAyD;AACzD,0DAAsE;AAEtE;;;;;;GAMG;AAGI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAuC;IACtE,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,wDAAwD;QACxD,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;YACjC,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;QAC/C,CAAC,CAAC,CAAC;IACP,CAAC;CACJ,CAAA;AAhBY,sCAAa;wBAAb,aAAa;IAFzB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,aAAa,CAgBzB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta } from '@webpieces/http-routing';\nimport { RequestContext } from '@webpieces/core-context';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\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 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,40 @@
|
|
|
1
|
+
import { MethodMeta } from '@webpieces/http-routing';
|
|
2
|
+
import { Filter, WpResponse, Service } from '@webpieces/http-filters';
|
|
3
|
+
/**
|
|
4
|
+
* LogApiFilter - Structured API logging for all requests/responses.
|
|
5
|
+
* Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)
|
|
6
|
+
*
|
|
7
|
+
* Logging patterns:
|
|
8
|
+
* - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest
|
|
9
|
+
* - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse
|
|
10
|
+
* - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)
|
|
11
|
+
* - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)
|
|
12
|
+
*
|
|
13
|
+
* User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
|
|
14
|
+
* because they are expected behavior from the server's perspective.
|
|
15
|
+
*/
|
|
16
|
+
export declare class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
17
|
+
filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
|
|
18
|
+
/**
|
|
19
|
+
* Get formatted class.method string for logging.
|
|
20
|
+
*/
|
|
21
|
+
private getClassMethod;
|
|
22
|
+
/**
|
|
23
|
+
* Log incoming request.
|
|
24
|
+
*/
|
|
25
|
+
private logRequest;
|
|
26
|
+
/**
|
|
27
|
+
* Log successful response.
|
|
28
|
+
*/
|
|
29
|
+
private logSuccessResponse;
|
|
30
|
+
/**
|
|
31
|
+
* Log exception based on error type.
|
|
32
|
+
* User errors get OTHER (no stack trace), server errors get FAIL.
|
|
33
|
+
*/
|
|
34
|
+
private logException;
|
|
35
|
+
/**
|
|
36
|
+
* Check if error is a user error (expected behavior from server perspective).
|
|
37
|
+
* These are NOT failures - just users making mistakes or validation issues.
|
|
38
|
+
*/
|
|
39
|
+
private isUserError;
|
|
40
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogApiFilter = 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_filters_1 = require("@webpieces/http-filters");
|
|
8
|
+
const http_api_1 = require("@webpieces/http-api");
|
|
9
|
+
/**
|
|
10
|
+
* LogApiFilter - Structured API logging for all requests/responses.
|
|
11
|
+
* Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)
|
|
12
|
+
*
|
|
13
|
+
* Logging patterns:
|
|
14
|
+
* - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest
|
|
15
|
+
* - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse
|
|
16
|
+
* - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)
|
|
17
|
+
* - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)
|
|
18
|
+
*
|
|
19
|
+
* User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,
|
|
20
|
+
* because they are expected behavior from the server's perspective.
|
|
21
|
+
*/
|
|
22
|
+
let LogApiFilter = class LogApiFilter extends http_filters_1.Filter {
|
|
23
|
+
async filter(meta, nextFilter) {
|
|
24
|
+
const classMethod = this.getClassMethod(meta);
|
|
25
|
+
const url = meta.path;
|
|
26
|
+
// Log request
|
|
27
|
+
this.logRequest(classMethod, url, meta.requestDto);
|
|
28
|
+
try {
|
|
29
|
+
const response = await nextFilter.invoke(meta);
|
|
30
|
+
// Log success response
|
|
31
|
+
this.logSuccessResponse(classMethod, url, response);
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Log error and re-throw (jsonTranslator will handle serialization)
|
|
36
|
+
this.logException(classMethod, url, error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get formatted class.method string for logging.
|
|
42
|
+
*/
|
|
43
|
+
getClassMethod(meta) {
|
|
44
|
+
const className = meta.routeMeta.controllerClassName ?? 'Unknown';
|
|
45
|
+
return `${className}.${meta.methodName}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Log incoming request.
|
|
49
|
+
*/
|
|
50
|
+
logRequest(classMethod, url, request) {
|
|
51
|
+
console.log(`[API-SVR-req] '${classMethod} ${url}' request=${JSON.stringify(request)}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Log successful response.
|
|
55
|
+
*/
|
|
56
|
+
logSuccessResponse(classMethod, url, response) {
|
|
57
|
+
console.log(`[API-SVR-resp-SUCCESS] '${classMethod} ${url}' response=${JSON.stringify(response.response)}`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Log exception based on error type.
|
|
61
|
+
* User errors get OTHER (no stack trace), server errors get FAIL.
|
|
62
|
+
*/
|
|
63
|
+
logException(classMethod, url, error) {
|
|
64
|
+
if (this.isUserError(error)) {
|
|
65
|
+
// User errors (400, 401, 403, 404, 266) - no stack trace needed
|
|
66
|
+
const errorType = error?.constructor.name ?? 'UnknownError';
|
|
67
|
+
console.log(`[API-SVR-resp-OTHER] '${classMethod} ${url}' errorType=${errorType}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Server errors (500, 502, etc.) - log full details
|
|
71
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
72
|
+
console.error(`[API-SVR-resp-FAIL] '${classMethod} ${url}' error=${errorMessage}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if error is a user error (expected behavior from server perspective).
|
|
77
|
+
* These are NOT failures - just users making mistakes or validation issues.
|
|
78
|
+
*/
|
|
79
|
+
isUserError(error) {
|
|
80
|
+
return (error instanceof http_api_1.HttpBadRequestError ||
|
|
81
|
+
error instanceof http_api_1.HttpUnauthorizedError ||
|
|
82
|
+
error instanceof http_api_1.HttpForbiddenError ||
|
|
83
|
+
error instanceof http_api_1.HttpNotFoundError ||
|
|
84
|
+
error instanceof http_api_1.HttpUserError);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
exports.LogApiFilter = LogApiFilter;
|
|
88
|
+
exports.LogApiFilter = LogApiFilter = tslib_1.__decorate([
|
|
89
|
+
(0, http_routing_1.provideSingleton)(),
|
|
90
|
+
(0, inversify_1.injectable)()
|
|
91
|
+
], LogApiFilter);
|
|
92
|
+
//# sourceMappingURL=LogApiFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogApiFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/LogApiFilter.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,0DAAuE;AACvE,0DAAsE;AACtE,kDAM6B;AAE7B;;;;;;;;;;;;GAYG;AAGI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,qBAAuC;IACrE,KAAK,CAAC,MAAM,CACR,IAAgB,EAChB,UAAoD;QAEpD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QAEtB,cAAc;QACd,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAEnD,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE/C,uBAAuB;YACvB,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAEpD,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACtB,oEAAoE;YACpE,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC3C,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,IAAgB;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,IAAI,SAAS,CAAC;QAClE,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED;;OAEG;IACK,UAAU,CAAC,WAAmB,EAAE,GAAW,EAAE,OAAgB;QACjE,OAAO,CAAC,GAAG,CAAC,kBAAkB,WAAW,IAAI,GAAG,aAAa,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;OAEG;IACK,kBAAkB,CAAC,WAAmB,EAAE,GAAW,EAAE,QAA6B;QACtF,OAAO,CAAC,GAAG,CACP,2BAA2B,WAAW,IAAI,GAAG,cAAc,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CACjG,CAAC;IACN,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,WAAmB,EAAE,GAAW,EAAE,KAAc;QACjE,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,gEAAgE;YAChE,MAAM,SAAS,GAAI,KAAe,EAAE,WAAW,CAAC,IAAI,IAAI,cAAc,CAAC;YACvE,OAAO,CAAC,GAAG,CAAC,yBAAyB,WAAW,IAAI,GAAG,eAAe,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;aAAM,CAAC;YACJ,oDAAoD;YACpD,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO,CAAC,KAAK,CAAC,wBAAwB,WAAW,IAAI,GAAG,WAAW,YAAY,EAAE,CAAC,CAAC;QACvF,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,WAAW,CAAC,KAAc;QAC9B,OAAO,CACH,KAAK,YAAY,8BAAmB;YACpC,KAAK,YAAY,gCAAqB;YACtC,KAAK,YAAY,6BAAkB;YACnC,KAAK,YAAY,4BAAiB;YAClC,KAAK,YAAY,wBAAa,CACjC,CAAC;IACN,CAAC;CACJ,CAAA;AA9EY,oCAAY;uBAAZ,YAAY;IAFxB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,YAAY,CA8ExB","sourcesContent":["import { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta } from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from '@webpieces/http-api';\n\n/**\n * LogApiFilter - Structured API logging for all requests/responses.\n * Priority: 130 (just below ContextFilter at 140, with gap for custom context filters)\n *\n * Logging patterns:\n * - [API-SVR-req] 'Class.method / url' request=jsonStringOfRequest\n * - [API-SVR-resp-SUCCESS] 'Class.method / url' response=jsonStringOfResponse\n * - [API-SVR-resp-FAIL] 'Class.method / url' error=... (server errors: 500, 502, 504)\n * - [API-SVR-resp-OTHER] 'Class.method / url' errorType=... (user errors: 400, 401, 403, 404, 266)\n *\n * User errors (HttpBadRequestError, etc.) are logged as OTHER, not FAIL,\n * because they are expected behavior from the server's perspective.\n */\n@provideSingleton()\n@injectable()\nexport class LogApiFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const classMethod = this.getClassMethod(meta);\n const url = meta.path;\n\n // Log request\n this.logRequest(classMethod, url, meta.requestDto);\n\n try {\n const response = await nextFilter.invoke(meta);\n\n // Log success response\n this.logSuccessResponse(classMethod, url, response);\n\n return response;\n } catch (error: unknown) {\n // Log error and re-throw (jsonTranslator will handle serialization)\n this.logException(classMethod, url, error);\n throw error;\n }\n }\n\n /**\n * Get formatted class.method string for logging.\n */\n private getClassMethod(meta: MethodMeta): string {\n const className = meta.routeMeta.controllerClassName ?? 'Unknown';\n return `${className}.${meta.methodName}`;\n }\n\n /**\n * Log incoming request.\n */\n private logRequest(classMethod: string, url: string, request: unknown): void {\n console.log(`[API-SVR-req] '${classMethod} ${url}' request=${JSON.stringify(request)}`);\n }\n\n /**\n * Log successful response.\n */\n private logSuccessResponse(classMethod: string, url: string, response: WpResponse<unknown>): void {\n console.log(\n `[API-SVR-resp-SUCCESS] '${classMethod} ${url}' response=${JSON.stringify(response.response)}`,\n );\n }\n\n /**\n * Log exception based on error type.\n * User errors get OTHER (no stack trace), server errors get FAIL.\n */\n private logException(classMethod: string, url: string, error: unknown): void {\n if (this.isUserError(error)) {\n // User errors (400, 401, 403, 404, 266) - no stack trace needed\n const errorType = (error as Error)?.constructor.name ?? 'UnknownError';\n console.log(`[API-SVR-resp-OTHER] '${classMethod} ${url}' errorType=${errorType}`);\n } else {\n // Server errors (500, 502, etc.) - log full details\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`[API-SVR-resp-FAIL] '${classMethod} ${url}' error=${errorMessage}`);\n }\n }\n\n /**\n * Check if error is a user error (expected behavior from server perspective).\n * These are NOT failures - just users making mistakes or validation issues.\n */\n private isUserError(error: unknown): boolean {\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError ||\n error instanceof HttpUserError\n );\n }\n}\n"]}
|