@webpieces/http-server 0.2.11 → 0.2.13

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.
@@ -1,15 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebpiecesServer = void 0;
4
- const tslib_1 = require("tslib");
5
- const express_1 = tslib_1.__importDefault(require("express"));
6
4
  const inversify_1 = require("inversify");
7
5
  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");
6
+ const WebpiecesCoreServer_1 = require("./WebpiecesCoreServer");
13
7
  /**
14
8
  * WebpiecesServer - Main bootstrap class for WebPieces applications.
15
9
  *
@@ -44,247 +38,34 @@ const FilterMatcher_1 = require("./FilterMatcher");
44
38
  */
45
39
  class WebpiecesServer {
46
40
  constructor(meta) {
47
- /**
48
- * Routes registry: Maps "METHOD:path" -> RegisteredRoute
49
- * Example: "POST:/search/item" -> { method: "POST", path: "/search/item", handler: ... }
50
- *
51
- * We use unknown instead of any for type safety - each route has its own return type,
52
- * but we can't have different generic types in the same Map.
53
- */
54
- this.routes = new Map();
55
- /**
56
- * Registered filters with their definitions.
57
- * Used by FilterMatcher to match filters to routes based on filepath patterns.
58
- */
59
- this.filterRegistry = [];
60
- this.initialized = false;
61
- this.port = 8080;
62
41
  this.meta = meta;
63
42
  // Create WebPieces container for framework-level bindings
64
43
  this.webpiecesContainer = new inversify_1.Container();
65
- // Create application container as a child of WebPieces container
66
- // This allows app container to access framework bindings
67
- this.appContainer = new inversify_1.Container({ parent: this.webpiecesContainer });
44
+ // Load buildProviderModule to auto-scan for @provideSingleton decorators
45
+ // This registers framework classes (WebpiecesCoreServer, RouteBuilderImpl)
46
+ this.webpiecesContainer.load((0, binding_decorators_1.buildProviderModule)());
47
+ // Resolve WebpiecesCoreServer from DI container (proper DI - no 'new'!)
48
+ this.coreService = this.webpiecesContainer.get(WebpiecesCoreServer_1.WebpiecesCoreServer);
68
49
  }
69
50
  /**
70
51
  * Initialize the server (DI container, routes, filters).
71
52
  * This is called automatically by start() or can be called manually for testing.
72
53
  */
73
54
  initialize() {
74
- if (this.initialized) {
75
- return;
76
- }
77
- // 1. Load DI modules
78
- this.loadDIModules();
79
- // 2. Register routes and filters
80
- this.registerRoutes();
81
- this.initialized = true;
82
- }
83
- /**
84
- * Load DI modules from WebAppMeta.
85
- *
86
- * Currently, all user modules are loaded into the application container.
87
- * In the future, we could separate:
88
- * - WebPieces framework modules -> webpiecesContainer
89
- * - Application modules -> appContainer
90
- *
91
- * For now, everything goes into appContainer which has access to webpiecesContainer.
92
- */
93
- loadDIModules() {
94
- const modules = this.meta.getDIModules();
95
- // Load buildProviderModule to auto-scan for @provideSingleton decorators
96
- this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
97
- // Load all modules into application container
98
- // (webpiecesContainer is currently empty, reserved for future framework bindings)
99
- for (const module of modules) {
100
- this.appContainer.load(module);
101
- }
102
- }
103
- /**
104
- * Register routes from WebAppMeta.
105
- *
106
- * Creates an explicit RouteBuilderImpl instead of an anonymous object.
107
- * This improves:
108
- * - Traceability: Can Cmd+Click on addRoute to see implementation
109
- * - Debugging: Explicit class shows up in stack traces
110
- * - Understanding: Clear class name vs anonymous object
111
- */
112
- registerRoutes() {
113
- const routeConfigs = this.meta.getRoutes();
114
- // Create explicit RouteBuilder implementation
115
- // Filters are resolved from appContainer (which has access to platformContainer too)
116
- const routeBuilder = new RouteBuilderImpl_1.RouteBuilderImpl(this.routes, this.filterRegistry, this.appContainer);
117
- // Configure routes using the explicit RouteBuilder
118
- for (const routeConfig of routeConfigs) {
119
- routeConfig.configure(routeBuilder);
120
- }
55
+ this.coreService.initialize(this.webpiecesContainer, this.meta);
121
56
  }
122
57
  /**
123
58
  * Start the HTTP server with Express.
124
59
  */
125
60
  start(port = 8080) {
126
- this.port = port;
127
61
  this.initialize();
128
- // Create Express app
129
- this.app = (0, express_1.default)();
130
- // Middleware
131
- this.app.use(express_1.default.json());
132
- this.app.use(express_1.default.urlencoded({ extended: true }));
133
- // Register routes
134
- this.registerExpressRoutes();
135
- // Start listening
136
- this.server = this.app.listen(this.port, () => {
137
- console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
138
- console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);
139
- console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);
140
- });
141
- }
142
- /**
143
- * Register all routes with Express.
144
- */
145
- registerExpressRoutes() {
146
- if (!this.app) {
147
- throw new Error('Express app not initialized');
148
- }
149
- for (const [key, route] of this.routes.entries()) {
150
- const method = route.method.toLowerCase();
151
- const path = route.path;
152
- console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);
153
- // Find matching filters for this route
154
- const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, this.filterRegistry);
155
- // Create Express route handler
156
- const handler = async (req, res, next) => {
157
- try {
158
- // Create method metadata
159
- const meta = new http_filters_1.MethodMeta(route.method, route.path, key, [req.body], new core_meta_1.RouteRequest(req.body, req.query, req.params, req.headers), undefined, new Map());
160
- // Create filter chain with matched filters
161
- const filterChain = new http_filters_1.FilterChain(matchingFilters);
162
- // Execute the filter chain
163
- const action = await filterChain.execute(meta, async () => {
164
- // Create typed route context
165
- // Use appContainer which has access to both app and framework bindings
166
- const routeContext = new core_meta_1.RouteContext(this.appContainer, [req.body], meta.request);
167
- // Final handler: invoke the controller method via route handler
168
- const result = await route.handler.execute(routeContext);
169
- // Wrap result in a JSON action
170
- return (0, http_filters_1.jsonAction)(result);
171
- });
172
- // Send response
173
- if (action.type === 'json') {
174
- res.json(action.data);
175
- }
176
- else if (action.type === 'error') {
177
- res.status(500).json({ error: action.data });
178
- }
179
- }
180
- catch (error) {
181
- console.error('[WebpiecesServer] Error handling request:', error);
182
- res.status(500).json({ error: error.message });
183
- }
184
- };
185
- // Register with Express
186
- switch (method) {
187
- case 'get':
188
- this.app.get(path, handler);
189
- break;
190
- case 'post':
191
- this.app.post(path, handler);
192
- break;
193
- case 'put':
194
- this.app.put(path, handler);
195
- break;
196
- case 'delete':
197
- this.app.delete(path, handler);
198
- break;
199
- case 'patch':
200
- this.app.patch(path, handler);
201
- break;
202
- default:
203
- console.warn(`[WebpiecesServer] Unknown HTTP method: ${method}`);
204
- }
205
- }
62
+ this.coreService.start(port);
206
63
  }
207
64
  /**
208
65
  * Stop the HTTP server.
209
66
  */
210
67
  stop() {
211
- if (this.server) {
212
- this.server.close(() => {
213
- console.log('[WebpiecesServer] Server stopped');
214
- });
215
- }
216
- }
217
- /**
218
- * Create an API client proxy for testing (no HTTP).
219
- *
220
- * This creates a proxy object that implements the API interface
221
- * and routes method calls through the full filter chain to the controller.
222
- *
223
- * @param apiMetaClass - The API interface class with decorators
224
- * @returns Proxy object implementing the API interface
225
- */
226
- createApiClient(apiMetaClass) {
227
- this.initialize();
228
- // Get routes from the API metadata
229
- const routes = (0, http_routing_1.getRoutes)(apiMetaClass);
230
- // Create a proxy object
231
- const proxy = {};
232
- for (const route of routes) {
233
- const methodName = route.methodName;
234
- // Create a function that routes through the filter chain
235
- proxy[methodName] = async (...args) => {
236
- return this.invokeRoute(route, args);
237
- };
238
- }
239
- return proxy;
240
- }
241
- /**
242
- * Invoke a route through the filter chain.
243
- */
244
- async invokeRoute(route, args) {
245
- // Find the registered route
246
- const key = `${route.httpMethod}:${route.path}`;
247
- const registeredRoute = this.routes.get(key);
248
- if (!registeredRoute) {
249
- throw new Error(`Route not found: ${key}`);
250
- }
251
- // Find matching filters for this route
252
- const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(registeredRoute.controllerFilepath, this.filterRegistry);
253
- // Create method metadata
254
- const meta = new http_filters_1.MethodMeta(route.httpMethod, route.path, route.methodName, [...args], new core_meta_1.RouteRequest(args[0]), // Assume first arg is the request body
255
- undefined, new Map());
256
- // Create filter chain with matched filters
257
- const filterChain = new http_filters_1.FilterChain(matchingFilters);
258
- // Execute the filter chain
259
- const action = await filterChain.execute(meta, async () => {
260
- // Create typed route context
261
- // Use appContainer which has access to both app and framework bindings
262
- const routeContext = new core_meta_1.RouteContext(this.appContainer, meta.params, meta.request);
263
- // Final handler: invoke the controller method via route handler
264
- const result = await registeredRoute.handler.execute(routeContext);
265
- // Wrap result in a JSON action
266
- return (0, http_filters_1.jsonAction)(result);
267
- });
268
- // Return the data from the action
269
- if (action.type === 'error') {
270
- throw new Error(JSON.stringify(action.data));
271
- }
272
- return action.data;
273
- }
274
- /**
275
- * Get the application DI container (for testing).
276
- * Returns appContainer which has access to both app and framework bindings.
277
- */
278
- getContainer() {
279
- this.initialize();
280
- return this.appContainer;
281
- }
282
- /**
283
- * Get the WebPieces framework container (for advanced testing/debugging).
284
- */
285
- getWebpiecesContainer() {
286
- this.initialize();
287
- return this.webpiecesContainer;
68
+ this.coreService.stop();
288
69
  }
289
70
  }
290
71
  exports.WebpiecesServer = WebpiecesServer;
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"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","sourcesContent":["import express, { Express, Request, Response, NextFunction } from 'express';\nimport { Container } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta, RouteContext, RouteRequest, FilterDefinition } from '@webpieces/core-meta';\nimport { FilterChain, Filter, MethodMeta, jsonAction } from '@webpieces/http-filters';\nimport { getRoutes, RouteMetadata } from '@webpieces/http-routing';\nimport { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';\nimport { FilterMatcher } from './FilterMatcher';\n\n/**\n * WebpiecesServer - Main bootstrap class for WebPieces applications.\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * Usage for testing (no HTTP):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.initialize();\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n *\n * Usage for production (HTTP server):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.start(); // Starts Express server\n * ```\n */\nexport class WebpiecesServer {\n private meta: WebAppMeta;\n\n /**\n * WebPieces container: Core WebPieces framework bindings.\n * This includes framework-level services like filters, routing infrastructure,\n * logging, metrics, etc. Similar to Java WebPieces platform container.\n */\n private webpiecesContainer: Container;\n\n /**\n * Application container: User's application bindings.\n * This is a child container of webpiecesContainer, so it can access\n * framework bindings while keeping app bindings separate.\n */\n private appContainer: Container;\n\n /**\n * Routes registry: Maps \"METHOD:path\" -> RegisteredRoute\n * Example: \"POST:/search/item\" -> { method: \"POST\", path: \"/search/item\", handler: ... }\n *\n * We use unknown instead of any for type safety - each route has its own return type,\n * but we can't have different generic types in the same Map.\n */\n private routes: Map<string, RegisteredRoute<unknown>> = new Map();\n\n /**\n * Registered filters with their definitions.\n * Used by FilterMatcher to match filters to routes based on filepath patterns.\n */\n private filterRegistry: Array<{ filter: Filter; definition: FilterDefinition }> = [];\n\n private initialized = false;\n private app?: Express;\n private server?: any;\n private port: number = 8080;\n\n constructor(meta: WebAppMeta) {\n this.meta = meta;\n\n // Create WebPieces container for framework-level bindings\n this.webpiecesContainer = new Container();\n\n // Create application container as a child of WebPieces container\n // This allows app container to access framework bindings\n this.appContainer = new Container({ parent: this.webpiecesContainer });\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called automatically by start() or can be called manually for testing.\n */\n initialize(): void {\n if (this.initialized) {\n return;\n }\n\n // 1. Load DI modules\n this.loadDIModules();\n\n // 2. Register routes and filters\n this.registerRoutes();\n\n this.initialized = true;\n }\n\n /**\n * Load DI modules from WebAppMeta.\n *\n * Currently, all user modules are loaded into the application container.\n * In the future, we could separate:\n * - WebPieces framework modules -> webpiecesContainer\n * - Application modules -> appContainer\n *\n * For now, everything goes into appContainer which has access to webpiecesContainer.\n */\n private loadDIModules(): void {\n const modules = this.meta.getDIModules();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of modules) {\n this.appContainer.load(module);\n }\n }\n\n /**\n * Register routes from WebAppMeta.\n *\n * Creates an explicit RouteBuilderImpl instead of an anonymous object.\n * This improves:\n * - Traceability: Can Cmd+Click on addRoute to see implementation\n * - Debugging: Explicit class shows up in stack traces\n * - Understanding: Clear class name vs anonymous object\n */\n private registerRoutes(): void {\n const routeConfigs = this.meta.getRoutes();\n\n // Create explicit RouteBuilder implementation\n // Filters are resolved from appContainer (which has access to platformContainer too)\n const routeBuilder = new RouteBuilderImpl(\n this.routes,\n this.filterRegistry,\n this.appContainer\n );\n\n // Configure routes using the explicit RouteBuilder\n for (const routeConfig of routeConfigs) {\n routeConfig.configure(routeBuilder);\n }\n }\n\n /**\n * Start the HTTP server with Express.\n */\n start(port: number = 8080): void {\n this.port = port;\n this.initialize();\n\n // Create Express app\n this.app = express();\n\n // Middleware\n this.app.use(express.json());\n this.app.use(express.urlencoded({ extended: true }));\n\n // Register routes\n this.registerExpressRoutes();\n\n // Start listening\n this.server = this.app.listen(this.port, () => {\n console.log(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n console.log(`[WebpiecesServer] Registered ${this.routes.size} routes`);\n console.log(`[WebpiecesServer] Registered ${this.filterRegistry.length} filters`);\n });\n }\n\n /**\n * Register all routes with Express.\n */\n private registerExpressRoutes(): void {\n if (!this.app) {\n throw new Error('Express app not initialized');\n }\n\n for (const [key, route] of this.routes.entries()) {\n const method = route.method.toLowerCase();\n const path = route.path;\n\n console.log(`[WebpiecesServer] Registering route: ${method.toUpperCase()} ${path}`);\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n this.filterRegistry\n );\n\n // Create Express route handler\n const handler = async (req: Request, res: Response, next: NextFunction) => {\n try {\n // Create method metadata\n const meta = new MethodMeta(\n route.method,\n route.path,\n key,\n [req.body],\n new RouteRequest(req.body, req.query, req.params, req.headers),\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n [req.body],\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await route.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Send response\n if (action.type === 'json') {\n res.json(action.data);\n } else if (action.type === 'error') {\n res.status(500).json({ error: action.data });\n }\n } catch (error: any) {\n console.error('[WebpiecesServer] Error handling request:', error);\n res.status(500).json({ error: error.message });\n }\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 /**\n * Stop the HTTP server.\n */\n stop(): void {\n if (this.server) {\n this.server.close(() => {\n console.log('[WebpiecesServer] Server stopped');\n });\n }\n }\n\n /**\n * Create an API client proxy for testing (no HTTP).\n *\n * This creates a proxy object that implements the API interface\n * and routes method calls through the full filter chain to the controller.\n *\n * @param apiMetaClass - The API interface class with decorators\n * @returns Proxy object implementing the API interface\n */\n createApiClient<T>(apiMetaClass: any): T {\n this.initialize();\n\n // Get routes from the API metadata\n const routes = getRoutes(apiMetaClass);\n\n // Create a proxy object\n const proxy: any = {};\n\n for (const route of routes) {\n const methodName = route.methodName;\n\n // Create a function that routes through the filter chain\n proxy[methodName] = async (...args: any[]) => {\n return this.invokeRoute(route, args);\n };\n }\n\n return proxy as T;\n }\n\n /**\n * Invoke a route through the filter chain.\n */\n private async invokeRoute(route: RouteMetadata, args: any[]): Promise<any> {\n // Find the registered route\n const key = `${route.httpMethod}:${route.path}`;\n const registeredRoute = this.routes.get(key);\n\n if (!registeredRoute) {\n throw new Error(`Route not found: ${key}`);\n }\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n registeredRoute.controllerFilepath,\n this.filterRegistry\n );\n\n // Create method metadata\n const meta = new MethodMeta(\n route.httpMethod,\n route.path,\n route.methodName,\n [...args],\n new RouteRequest(args[0]), // Assume first arg is the request body\n undefined,\n new Map()\n );\n\n // Create filter chain with matched filters\n const filterChain = new FilterChain(matchingFilters);\n\n // Execute the filter chain\n const action = await filterChain.execute(meta, async () => {\n // Create typed route context\n // Use appContainer which has access to both app and framework bindings\n const routeContext = new RouteContext(\n this.appContainer,\n meta.params,\n meta.request\n );\n\n // Final handler: invoke the controller method via route handler\n const result = await registeredRoute.handler.execute(routeContext);\n\n // Wrap result in a JSON action\n return jsonAction(result);\n });\n\n // Return the data from the action\n if (action.type === 'error') {\n throw new Error(JSON.stringify(action.data));\n }\n\n return action.data;\n }\n\n /**\n * Get the application DI container (for testing).\n * Returns appContainer which has access to both app and framework bindings.\n */\n getContainer(): Container {\n this.initialize();\n return this.appContainer;\n }\n\n /**\n * Get the WebPieces framework container (for advanced testing/debugging).\n */\n getWebpiecesContainer(): Container {\n this.initialize();\n return this.webpiecesContainer;\n }\n\n\n}\n"]}
1
+ {"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AACtC,wEAAsE;AAEtE,+DAA4D;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAa,eAAe;IAY1B,YAAY,IAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAEjB,0DAA0D;QAC1D,IAAI,CAAC,kBAAkB,GAAG,IAAI,qBAAS,EAAE,CAAC;QAE1C,yEAAyE;QACzE,2EAA2E;QAC3E,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAEpD,wEAAwE;QACxE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,yCAAmB,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,UAAU;QACR,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAe,IAAI;QACvB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CAEF;AAjDD,0CAiDC","sourcesContent":["import { Container } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta } from '@webpieces/core-meta';\nimport { WebpiecesCoreServer } from \"./WebpiecesCoreServer\";\n\n/**\n * WebpiecesServer - Main bootstrap class for WebPieces applications.\n *\n * This class uses a two-container pattern similar to Java WebPieces:\n * 1. webpiecesContainer: Core WebPieces framework bindings\n * 2. appContainer: User's application bindings (child of webpiecesContainer)\n *\n * This separation allows:\n * - Clean separation of concerns\n * - Better testability\n * - Ability to override framework bindings in tests\n *\n * The server:\n * 1. Initializes both DI containers from WebAppMeta.getDIModules()\n * 2. Registers routes using explicit RouteBuilderImpl\n * 3. Creates filter chains\n * 4. Supports both HTTP server mode and testing mode (no HTTP)\n *\n * Usage for testing (no HTTP):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.initialize();\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n *\n * Usage for production (HTTP server):\n * ```typescript\n * const server = new WebpiecesServer(new ProdServerMeta());\n * server.start(); // Starts Express server\n * ```\n */\nexport class WebpiecesServer {\n private meta: WebAppMeta;\n\n /**\n * WebPieces container: Core WebPieces framework bindings.\n * This includes framework-level services like filters, routing infrastructure,\n * logging, metrics, etc. Similar to Java WebPieces platform container.\n */\n private webpiecesContainer: Container;\n\n private coreService: WebpiecesCoreServer;\n\n constructor(meta: WebAppMeta) {\n this.meta = meta;\n\n // Create WebPieces container for framework-level bindings\n this.webpiecesContainer = new Container();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n // This registers framework classes (WebpiecesCoreServer, RouteBuilderImpl)\n this.webpiecesContainer.load(buildProviderModule());\n\n // Resolve WebpiecesCoreServer from DI container (proper DI - no 'new'!)\n this.coreService = this.webpiecesContainer.get(WebpiecesCoreServer);\n }\n\n /**\n * Initialize the server (DI container, routes, filters).\n * This is called automatically by start() or can be called manually for testing.\n */\n initialize(): void {\n this.coreService.initialize(this.webpiecesContainer, this.meta);\n }\n\n /**\n * Start the HTTP server with Express.\n */\n start(port: number = 8080): void {\n this.initialize();\n this.coreService.start(port);\n }\n\n /**\n * Stop the HTTP server.\n */\n stop(): void {\n this.coreService.stop();\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
+ }
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpException = exports.ValidationException = exports.JsonFilter = exports.JsonFilterConfig = exports.FILTER_TYPES = 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 core_util_1 = require("@webpieces/core-util");
9
+ /**
10
+ * DI tokens for JsonFilter.
11
+ */
12
+ exports.FILTER_TYPES = {
13
+ JsonFilterConfig: Symbol.for('JsonFilterConfig'),
14
+ };
15
+ /**
16
+ * Configuration for JsonFilter.
17
+ * Register this in your DI container to customize JsonFilter behavior.
18
+ */
19
+ let JsonFilterConfig = class JsonFilterConfig {
20
+ };
21
+ exports.JsonFilterConfig = JsonFilterConfig;
22
+ exports.JsonFilterConfig = JsonFilterConfig = tslib_1.__decorate([
23
+ (0, inversify_1.injectable)()
24
+ ], JsonFilterConfig);
25
+ /**
26
+ * JsonFilter - Handles JSON serialization/deserialization and writes response to HTTP body.
27
+ *
28
+ * Similar to Java WebPieces JacksonCatchAllFilter.
29
+ *
30
+ * Flow:
31
+ * 1. Log request
32
+ * 2. Deserialize request body to DTO and set on meta.requestDto
33
+ * 3. Call next filter/controller
34
+ * 4. Get response (WpResponse)
35
+ * 5. Write response to Express response
36
+ * 6. On ANY exception, send 500
37
+ */
38
+ let JsonFilter = class JsonFilter extends http_filters_1.Filter {
39
+ constructor(config) {
40
+ super();
41
+ this.config = config;
42
+ }
43
+ async filter(meta, nextFilter) {
44
+ // Get Express Request/Response from routeRequest
45
+ const expressRequest = meta.routeRequest.request;
46
+ const expressResponse = meta.routeRequest.response;
47
+ try {
48
+ // 1. Log request
49
+ this.logRequest(meta, expressRequest);
50
+ // 2. Deserialize request body to DTO
51
+ this.deserializeRequest(meta, expressRequest);
52
+ // 3. Call next filter/controller
53
+ const responseWrapper = await nextFilter.invoke(meta);
54
+ // 4. Log response
55
+ this.logResponse(responseWrapper);
56
+ // 5. Write response to Express response
57
+ this.writeResponse(expressResponse, responseWrapper);
58
+ return responseWrapper;
59
+ }
60
+ catch (err) {
61
+ const error = (0, core_util_1.toError)(err);
62
+ // 6. On ANY exception, send 500
63
+ console.error('[JsonFilter] Error:', error);
64
+ const errorResponse = new http_filters_1.WpResponse({ error: 'Internal server error' }, 500);
65
+ this.writeResponse(expressResponse, errorResponse);
66
+ return errorResponse;
67
+ }
68
+ }
69
+ /**
70
+ * Deserialize request body to DTO and set on meta.requestDto.
71
+ */
72
+ deserializeRequest(meta, expressRequest) {
73
+ if (expressRequest.body) {
74
+ // Set the deserialized body as requestDto
75
+ meta.requestDto = expressRequest.body;
76
+ }
77
+ }
78
+ /**
79
+ * Write WpResponse to HTTP response body as JSON.
80
+ */
81
+ writeResponse(expressResponse, responseWrapper) {
82
+ // Set status code
83
+ expressResponse.status(responseWrapper.statusCode);
84
+ // Set headers from wrapper
85
+ responseWrapper.headers.forEach((value, name) => {
86
+ expressResponse.setHeader(name, value);
87
+ });
88
+ // Set content type to JSON
89
+ expressResponse.setHeader('Content-Type', 'application/json');
90
+ // Serialize and write response body
91
+ if (responseWrapper.response !== undefined) {
92
+ expressResponse.json(responseWrapper.response);
93
+ }
94
+ else {
95
+ expressResponse.end();
96
+ }
97
+ }
98
+ /**
99
+ * Log the incoming request.
100
+ */
101
+ logRequest(meta, expressRequest) {
102
+ console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);
103
+ if (expressRequest.body) {
104
+ console.log('[JsonFilter] Request body:', JSON.stringify(expressRequest.body, null, 2));
105
+ }
106
+ }
107
+ /**
108
+ * Log the outgoing response.
109
+ */
110
+ logResponse(responseWrapper) {
111
+ console.log(`[JsonFilter] Response: ${responseWrapper.statusCode}`);
112
+ if (responseWrapper.response) {
113
+ console.log('[JsonFilter] Response body:', JSON.stringify(responseWrapper.response, null, 2));
114
+ }
115
+ }
116
+ };
117
+ exports.JsonFilter = JsonFilter;
118
+ exports.JsonFilter = JsonFilter = tslib_1.__decorate([
119
+ (0, http_routing_1.provideSingleton)(),
120
+ (0, inversify_1.injectable)(),
121
+ tslib_1.__param(0, (0, inversify_1.inject)(exports.FILTER_TYPES.JsonFilterConfig)),
122
+ tslib_1.__metadata("design:paramtypes", [JsonFilterConfig])
123
+ ], JsonFilter);
124
+ /**
125
+ * Exception thrown when validation fails.
126
+ */
127
+ class ValidationException extends Error {
128
+ constructor(violations) {
129
+ super('Validation failed');
130
+ this.violations = violations;
131
+ this.name = 'ValidationException';
132
+ }
133
+ }
134
+ exports.ValidationException = ValidationException;
135
+ /**
136
+ * HTTP exception with status code.
137
+ */
138
+ class HttpException extends Error {
139
+ constructor(message, statusCode) {
140
+ super(message);
141
+ this.statusCode = statusCode;
142
+ this.name = 'HttpException';
143
+ }
144
+ }
145
+ exports.HttpException = HttpException;
146
+ //# sourceMappingURL=JsonFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-server/src/filters/JsonFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport { provideSingleton } from '@webpieces/http-routing';\nimport { Filter, WpResponse, Service } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\nimport { Request, Response } from 'express';\nimport { MethodMeta } from '../MethodMeta';\n\n/**\n * DI tokens for JsonFilter.\n */\nexport const FILTER_TYPES = {\n JsonFilterConfig: Symbol.for('JsonFilterConfig'),\n};\n\n/**\n * Configuration for JsonFilter.\n * Register this in your DI container to customize JsonFilter behavior.\n */\n@injectable()\nexport class JsonFilterConfig {\n // Configuration options can be added here\n}\n\n/**\n * JsonFilter - Handles JSON serialization/deserialization and writes response to HTTP body.\n *\n * Similar to Java WebPieces JacksonCatchAllFilter.\n *\n * Flow:\n * 1. Log request\n * 2. Deserialize request body to DTO and set on meta.requestDto\n * 3. Call next filter/controller\n * 4. Get response (WpResponse)\n * 5. Write response to Express response\n * 6. On ANY exception, send 500\n */\n@provideSingleton()\n@injectable()\nexport class JsonFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n\n constructor(@inject(FILTER_TYPES.JsonFilterConfig) private config: JsonFilterConfig) {\n super();\n }\n\n async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>\n ): Promise<WpResponse<unknown>> {\n // Get Express Request/Response from routeRequest\n const expressRequest = meta.routeRequest.request as Request;\n const expressResponse = meta.routeRequest.response as Response;\n\n try {\n // 1. Log request\n this.logRequest(meta, expressRequest);\n\n // 2. Deserialize request body to DTO\n this.deserializeRequest(meta, expressRequest);\n\n // 3. Call next filter/controller\n const responseWrapper = await nextFilter.invoke(meta);\n\n // 4. Log response\n this.logResponse(responseWrapper);\n\n // 5. Write response to Express response\n this.writeResponse(expressResponse, responseWrapper);\n\n return responseWrapper;\n } catch (err: unknown) {\n const error = toError(err);\n // 6. On ANY exception, send 500\n console.error('[JsonFilter] Error:', error);\n const errorResponse = new WpResponse({ error: 'Internal server error' }, 500);\n this.writeResponse(expressResponse, errorResponse);\n return errorResponse;\n }\n }\n\n /**\n * Deserialize request body to DTO and set on meta.requestDto.\n */\n private deserializeRequest(meta: MethodMeta, expressRequest: Request): void {\n if (expressRequest.body) {\n // Set the deserialized body as requestDto\n meta.requestDto = expressRequest.body;\n }\n }\n\n /**\n * Write WpResponse to HTTP response body as JSON.\n */\n private writeResponse(\n expressResponse: Response,\n responseWrapper: WpResponse<unknown>\n ): void {\n // Set status code\n expressResponse.status(responseWrapper.statusCode);\n\n // Set headers from wrapper\n responseWrapper.headers.forEach((value, name) => {\n expressResponse.setHeader(name, value);\n });\n\n // Set content type to JSON\n expressResponse.setHeader('Content-Type', 'application/json');\n\n // Serialize and write response body\n if (responseWrapper.response !== undefined) {\n expressResponse.json(responseWrapper.response);\n } else {\n expressResponse.end();\n }\n }\n\n /**\n * Log the incoming request.\n */\n private logRequest(meta: MethodMeta, expressRequest: Request): void {\n console.log(`[JsonFilter] ${meta.httpMethod} ${meta.path}`);\n if (expressRequest.body) {\n console.log('[JsonFilter] Request body:', JSON.stringify(expressRequest.body, null, 2));\n }\n }\n\n /**\n * Log the outgoing response.\n */\n private logResponse(responseWrapper: WpResponse<unknown>): void {\n console.log(`[JsonFilter] Response: ${responseWrapper.statusCode}`);\n if (responseWrapper.response) {\n console.log('[JsonFilter] Response body:', JSON.stringify(responseWrapper.response, null, 2));\n }\n }\n}\n\n/**\n * Exception thrown when validation fails.\n */\nexport class ValidationException extends Error {\n constructor(public violations: string[]) {\n super('Validation failed');\n this.name = 'ValidationException';\n }\n}\n\n/**\n * HTTP exception with status code.\n */\nexport class HttpException extends Error {\n constructor(message: string, public statusCode: number) {\n super(message);\n this.name = 'HttpException';\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  export { WebpiecesServer } from './WebpiecesServer';
2
- export { RouteBuilderImpl, RegisteredRoute } from './RouteBuilderImpl';
2
+ export { RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta, HttpFilter } from './RouteBuilderImpl';
3
+ export { WebpiecesCoreServer } from './WebpiecesCoreServer';
4
+ export { RouteHandler } from './RouteHandler';
5
+ export { MethodMeta } from './MethodMeta';
6
+ export { ContextFilter } from './filters/ContextFilter';
7
+ export { JsonFilter, JsonFilterConfig, FILTER_TYPES, ValidationException, HttpException, } from './filters/JsonFilter';
package/src/index.js CHANGED
@@ -1,9 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RegisteredRoute = exports.RouteBuilderImpl = exports.WebpiecesServer = void 0;
3
+ exports.HttpException = exports.ValidationException = exports.FILTER_TYPES = exports.JsonFilterConfig = exports.JsonFilter = exports.ContextFilter = exports.MethodMeta = exports.RouteHandler = exports.WebpiecesCoreServer = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.WebpiecesServer = void 0;
4
4
  var WebpiecesServer_1 = require("./WebpiecesServer");
5
5
  Object.defineProperty(exports, "WebpiecesServer", { enumerable: true, get: function () { return WebpiecesServer_1.WebpiecesServer; } });
6
6
  var RouteBuilderImpl_1 = require("./RouteBuilderImpl");
7
7
  Object.defineProperty(exports, "RouteBuilderImpl", { enumerable: true, get: function () { return RouteBuilderImpl_1.RouteBuilderImpl; } });
8
- Object.defineProperty(exports, "RegisteredRoute", { enumerable: true, get: function () { return RouteBuilderImpl_1.RegisteredRoute; } });
8
+ Object.defineProperty(exports, "RouteHandlerWithMeta", { enumerable: true, get: function () { return RouteBuilderImpl_1.RouteHandlerWithMeta; } });
9
+ Object.defineProperty(exports, "FilterWithMeta", { enumerable: true, get: function () { return RouteBuilderImpl_1.FilterWithMeta; } });
10
+ var WebpiecesCoreServer_1 = require("./WebpiecesCoreServer");
11
+ Object.defineProperty(exports, "WebpiecesCoreServer", { enumerable: true, get: function () { return WebpiecesCoreServer_1.WebpiecesCoreServer; } });
12
+ var RouteHandler_1 = require("./RouteHandler");
13
+ Object.defineProperty(exports, "RouteHandler", { enumerable: true, get: function () { return RouteHandler_1.RouteHandler; } });
14
+ var MethodMeta_1 = require("./MethodMeta");
15
+ Object.defineProperty(exports, "MethodMeta", { enumerable: true, get: function () { return MethodMeta_1.MethodMeta; } });
16
+ var ContextFilter_1 = require("./filters/ContextFilter");
17
+ Object.defineProperty(exports, "ContextFilter", { enumerable: true, get: function () { return ContextFilter_1.ContextFilter; } });
18
+ var JsonFilter_1 = require("./filters/JsonFilter");
19
+ Object.defineProperty(exports, "JsonFilter", { enumerable: true, get: function () { return JsonFilter_1.JsonFilter; } });
20
+ Object.defineProperty(exports, "JsonFilterConfig", { enumerable: true, get: function () { return JsonFilter_1.JsonFilterConfig; } });
21
+ Object.defineProperty(exports, "FILTER_TYPES", { enumerable: true, get: function () { return JsonFilter_1.FILTER_TYPES; } });
22
+ Object.defineProperty(exports, "ValidationException", { enumerable: true, get: function () { return JsonFilter_1.ValidationException; } });
23
+ Object.defineProperty(exports, "HttpException", { enumerable: true, get: function () { return JsonFilter_1.HttpException; } });
9
24
  //# sourceMappingURL=index.js.map