@webpieces/http-server 0.3.259 → 0.3.260
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.260",
|
|
4
4
|
"description": "WebPieces server with filter chain and dependency injection",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/http-routing": "0.3.
|
|
26
|
-
"@webpieces/wp-logging": "0.3.
|
|
25
|
+
"@webpieces/http-routing": "0.3.260",
|
|
26
|
+
"@webpieces/wp-logging": "0.3.260",
|
|
27
27
|
"cors": "2.8.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
@@ -75,6 +75,12 @@ let WebpiecesServerImpl = class WebpiecesServerImpl {
|
|
|
75
75
|
this.routeBuilder.setContainer(this.appContainer);
|
|
76
76
|
// 1. Load DI modules asynchronously
|
|
77
77
|
await this.loadDIModules(appOverrides);
|
|
78
|
+
// buildProviderModule bound a SEPARATE RouteBuilderImpl into appContainer; make
|
|
79
|
+
// app-side singletons resolve the SAME framework instance that actually holds the
|
|
80
|
+
// registered routes (setContainer + addRoute happen on `this.routeBuilder`). Without
|
|
81
|
+
// this, an app singleton that injects RouteBuilderImpl (e.g. LocalTaskDispatcherImpl,
|
|
82
|
+
// used by InMemoryTaskInvoker) would see an empty route table.
|
|
83
|
+
(await this.appContainer.rebind(http_routing_1.RouteBuilderImpl)).toConstantValue(this.routeBuilder);
|
|
78
84
|
// 2. Register routes and filters
|
|
79
85
|
this.registerRoutes();
|
|
80
86
|
this.initialized = true;
|
|
@@ -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} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n WEBAPP_META_TOKEN,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {WebpiecesRouteCreator} from './WebpiecesRouteCreator';\nimport {InProcessApiClientFactory} from './InProcessApiClientFactory';\nimport {LogManager} from '@webpieces/wp-logging';\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 */\nconst log = LogManager.getLogger('WebpiecesServer');\n\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\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(WEBAPP_META_TOKEN) private meta: WebAppMeta,\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server asynchronously.\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n appOverrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\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(appOverrides);\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(appOverrides?: 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 appOverrides LAST so they can override existing bindings\n if (appOverrides) {\n await this.appContainer.load(appOverrides);\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: CORS for localhost development\n this.app.use(this.middleware.corsForLocalhost());\n\n // Layer 3: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes via the shared adapter (same code path as the\n // embeddable WebpiecesRouteCreator used by legacy Express apps)\n const routeCreator = new WebpiecesRouteCreator(\n this.app,\n this.appContainer,\n this.routeBuilder,\n this.middleware,\n );\n const routeCount = routeCreator.mountRegisteredRoutes();\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 log.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n log.info(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n log.info(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\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 log.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n log.info('[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 abstract API prototype whose routing decorators declare the routes\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\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 // Delegates to the shared factory (same code path as WebpiecesRouteCreator.createApiClient)\n if (!this.clientFactory) {\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n private clientFactory?: InProcessApiClientFactory;\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} from 'express';\nimport {Container, ContainerModule, inject, injectable} from 'inversify';\nimport {buildProviderModule} from '@inversifyjs/binding-decorators';\nimport {\n provideSingleton,\n RouteBuilderImpl,\n WebAppMeta,\n WEBAPP_META_TOKEN,\n} from '@webpieces/http-routing';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {WebpiecesRouteCreator} from './WebpiecesRouteCreator';\nimport {InProcessApiClientFactory} from './InProcessApiClientFactory';\nimport {LogManager} from '@webpieces/wp-logging';\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 */\nconst log = LogManager.getLogger('WebpiecesServer');\n\n@provideSingleton()\n@injectable()\nexport class WebpiecesServerImpl implements WebpiecesServer {\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(WEBAPP_META_TOKEN) private meta: WebAppMeta,\n @inject(RouteBuilderImpl) private routeBuilder: RouteBuilderImpl,\n @inject(WebpiecesMiddleware) private middleware: WebpiecesMiddleware,\n ) {}\n\n /**\n * Initialize the server asynchronously.\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST)\n */\n async initialize(\n webpiecesContainer: Container,\n appOverrides?: ContainerModule\n ): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n this.webpiecesContainer = webpiecesContainer;\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(appOverrides);\n\n // buildProviderModule bound a SEPARATE RouteBuilderImpl into appContainer; make\n // app-side singletons resolve the SAME framework instance that actually holds the\n // registered routes (setContainer + addRoute happen on `this.routeBuilder`). Without\n // this, an app singleton that injects RouteBuilderImpl (e.g. LocalTaskDispatcherImpl,\n // used by InMemoryTaskInvoker) would see an empty route table.\n (await this.appContainer.rebind(RouteBuilderImpl)).toConstantValue(this.routeBuilder);\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 appOverrides - Optional ContainerModule for app test overrides (loaded LAST to override bindings)\n */\n private async loadDIModules(appOverrides?: 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 appOverrides LAST so they can override existing bindings\n if (appOverrides) {\n await this.appContainer.load(appOverrides);\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: CORS for localhost development\n this.app.use(this.middleware.corsForLocalhost());\n\n // Layer 3: Request/Response Logging\n this.app.use(this.middleware.logNextLayer.bind(this.middleware));\n\n // Register routes via the shared adapter (same code path as the\n // embeddable WebpiecesRouteCreator used by legacy Express apps)\n const routeCreator = new WebpiecesRouteCreator(\n this.app,\n this.appContainer,\n this.routeBuilder,\n this.middleware,\n );\n const routeCount = routeCreator.mountRegisteredRoutes();\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 log.error(`[WebpiecesServer] Failed to start server:`, error);\n reject(error);\n return;\n }\n log.info(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);\n log.info(`[WebpiecesServer] Registered ${routeCount} routes`);\n resolve();\n });\n });\n\n await promise;\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 log.error('[WebpiecesServer] Error stopping server:', err);\n reject(err);\n return;\n }\n log.info('[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 abstract API prototype whose routing decorators declare the routes\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApi);\n * const response = await saveApi.save(request);\n * ```\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\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 // Delegates to the shared factory (same code path as WebpiecesRouteCreator.createApiClient)\n if (!this.clientFactory) {\n this.clientFactory = new InProcessApiClientFactory(this.routeBuilder);\n }\n return this.clientFactory.createApiClient(apiPrototype);\n }\n\n private clientFactory?: InProcessApiClientFactory;\n}\n"]}
|