@webpieces/http-server 0.3.279 → 0.3.281

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,255 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebpiecesServerImpl = void 0;
4
- const tslib_1 = require("tslib");
5
- const express_1 = tslib_1.__importDefault(require("express"));
6
- const inversify_1 = require("inversify");
7
- const binding_decorators_1 = require("@inversifyjs/binding-decorators");
8
- const http_routing_1 = require("@webpieces/http-routing");
9
- const core_util_1 = require("@webpieces/core-util");
10
- const WebpiecesMiddleware_1 = require("./WebpiecesMiddleware");
11
- const WebpiecesRouteCreator_1 = require("./WebpiecesRouteCreator");
12
- const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
13
- const core_util_2 = require("@webpieces/core-util");
14
- /**
15
- * WebpiecesServerImpl - Internal server implementation.
16
- *
17
- * This class implements the WebpiecesServer interface and contains
18
- * all the actual server logic. It is created by WebpiecesFactory.create().
19
- *
20
- * This class uses a two-container pattern similar to Java WebPieces:
21
- * 1. webpiecesContainer: Core WebPieces framework bindings
22
- * 2. appContainer: User's application bindings (child of webpiecesContainer)
23
- *
24
- * This separation allows:
25
- * - Clean separation of concerns
26
- * - Better testability
27
- * - Ability to override framework bindings in tests
28
- *
29
- * The server:
30
- * 1. Initializes both DI containers from WebAppMeta.getDIModules()
31
- * 2. Registers routes using explicit RouteBuilderImpl
32
- * 3. Creates filter chains
33
- * 4. Supports both HTTP server mode and testing mode (no HTTP)
34
- *
35
- * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()
36
- * and resolved by WebpiecesFactory. It receives RouteBuilder via constructor injection.
37
- */
38
- const log = core_util_2.LogManager.getLogger('WebpiecesServer');
39
- let WebpiecesServerImpl = class WebpiecesServerImpl {
40
- meta;
41
- routeBuilder;
42
- middleware;
43
- webpiecesContainer;
44
- /**
45
- * Application container: User's application bindings.
46
- * This is a child container of webpiecesContainer, so it can access
47
- * framework bindings while keeping app bindings separate.
48
- */
49
- appContainer;
50
- initialized = false;
51
- app;
52
- server;
53
- port = 8200;
54
- constructor(meta, routeBuilder, middleware) {
55
- this.meta = meta;
56
- this.routeBuilder = routeBuilder;
57
- this.middleware = middleware;
58
- }
59
- /**
60
- * Initialize the server asynchronously.
61
- * This is called by WebpiecesFactory.create() after resolving this class from DI.
62
- * This method is internal and not exposed on the WebpiecesServer interface.
63
- *
64
- * @param webpiecesContainer - The framework container
65
- * @param meta - User-provided WebAppMeta with DI modules and routes
66
- * @param appOverrides - Optional ContainerModule for app test overrides (loaded LAST)
67
- */
68
- async initialize(webpiecesContainer, appOverrides) {
69
- if (this.initialized) {
70
- return;
71
- }
72
- this.webpiecesContainer = webpiecesContainer;
73
- // Create application container as child of framework container
74
- this.appContainer = new inversify_1.Container({ parent: this.webpiecesContainer });
75
- // Set container on RouteBuilder (late binding - appContainer didn't exist in constructor)
76
- this.routeBuilder.setContainer(this.appContainer);
77
- // 1. Load DI modules asynchronously
78
- await this.loadDIModules(appOverrides);
79
- // buildProviderModule bound a SEPARATE RouteBuilderImpl into appContainer; make
80
- // app-side singletons resolve the SAME framework instance that actually holds the
81
- // registered routes (setContainer + addRoute happen on `this.routeBuilder`). Without
82
- // this, an app singleton that injects RouteBuilderImpl would see an empty route table.
83
- (await this.appContainer.rebind(http_routing_1.RouteBuilderImpl)).toConstantValue(this.routeBuilder);
84
- // 2. Register routes and filters
85
- this.registerRoutes();
86
- this.initialized = true;
87
- }
88
- /**
89
- * Load DI modules from WebAppMeta.
90
- *
91
- * Currently, all user modules are loaded into the application container.
92
- * In the future, we could separate:
93
- * - WebPieces framework modules -> webpiecesContainer
94
- * - Application modules -> appContainer
95
- *
96
- * For now, everything goes into appContainer which has access to webpiecesContainer.
97
- *
98
- * @param appOverrides - Optional ContainerModule for app test overrides (loaded LAST to override bindings)
99
- */
100
- async loadDIModules(appOverrides) {
101
- const modules = this.meta.getDIModules();
102
- // Load buildProviderModule to auto-scan for @provideSingleton decorators
103
- await this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
104
- // Load all modules into application container
105
- // (webpiecesContainer is currently empty, reserved for future framework bindings)
106
- for (const module of modules) {
107
- await this.appContainer.load(module);
108
- }
109
- // Load appOverrides LAST so they can override existing bindings
110
- if (appOverrides) {
111
- await this.appContainer.load(appOverrides);
112
- }
113
- }
114
- /**
115
- * Register routes from WebAppMeta.
116
- *
117
- * Creates an explicit RouteBuilderImpl instead of an anonymous object.
118
- * This improves:
119
- * - Traceability: Can Cmd+Click on addRoute to see implementation
120
- * - Debugging: Explicit class shows up in stack traces
121
- * - Understanding: Clear class name vs anonymous object
122
- */
123
- registerRoutes() {
124
- const routeConfigs = this.meta.getRoutes();
125
- // Configure routes using the explicit RouteBuilder
126
- for (const routeConfig of routeConfigs) {
127
- routeConfig.configure(this.routeBuilder);
128
- }
129
- }
130
- /**
131
- * Start the HTTP server with Express.
132
- * Returns a Promise that resolves when the server is listening,
133
- * or rejects if the server fails to start.
134
- *
135
- * @param port - The port to listen on (default: 8080)
136
- * @returns Promise that resolves when server is ready
137
- */
138
- async start(port = 8200, testMode) {
139
- if (!this.initialized) {
140
- throw new Error('Server not initialized. Call initialize() before start().');
141
- }
142
- this.port = port;
143
- if (testMode) {
144
- //In testMode, we eliminate express ENTIRELY and use
145
- //Router, method filters and controllers so that we can test full stack
146
- return;
147
- }
148
- // Create Express app
149
- this.app = (0, express_1.default)();
150
- // Layer 1: Global Error Handler (OUTERMOST - runs FIRST)
151
- // Catches all unhandled errors and returns HTML 500 page
152
- this.app.use(this.middleware.globalErrorHandler.bind(this.middleware));
153
- // Layer 2: CORS for localhost development
154
- this.app.use(this.middleware.corsForLocalhost());
155
- // Layer 3: Request/Response Logging
156
- this.app.use(this.middleware.logNextLayer.bind(this.middleware));
157
- // Register routes via the shared adapter (same code path as the
158
- // embeddable WebpiecesRouteCreator used by legacy Express apps)
159
- const routeCreator = new WebpiecesRouteCreator_1.WebpiecesRouteCreator(this.app, this.appContainer, this.routeBuilder, this.middleware);
160
- const routeCount = routeCreator.mountRegisteredRoutes();
161
- // Start listening - wrap in Promise
162
- const promise = new Promise((resolve, reject) => {
163
- this.server = this.app.listen(this.port, (error) => {
164
- if (error) {
165
- log.error(`[WebpiecesServer] Failed to start server:`, error);
166
- reject(error);
167
- return;
168
- }
169
- log.info(`[WebpiecesServer] Server listening on http://localhost:${this.port}`);
170
- log.info(`[WebpiecesServer] Registered ${routeCount} routes`);
171
- resolve();
172
- });
173
- });
174
- await promise;
175
- }
176
- /**
177
- * Stop the HTTP server.
178
- * Returns a Promise that resolves when the server is stopped,
179
- * or rejects if there's an error stopping the server.
180
- *
181
- * @returns Promise that resolves when server is stopped
182
- */
183
- async stop() {
184
- if (!this.server) {
185
- return;
186
- }
187
- return new Promise((resolve, reject) => {
188
- this.server.close((err) => {
189
- if (err) {
190
- log.error('[WebpiecesServer] Error stopping server:', err);
191
- reject(err);
192
- return;
193
- }
194
- log.info('[WebpiecesServer] Server stopped');
195
- resolve();
196
- });
197
- });
198
- }
199
- /**
200
- * Get the application DI container.
201
- *
202
- * Useful for testing to verify state or access services directly.
203
- *
204
- * @returns The application Container
205
- */
206
- getContainer() {
207
- return this.appContainer;
208
- }
209
- /**
210
- * Create an API client proxy for testing.
211
- *
212
- * This creates a client that routes calls through the full filter chain
213
- * and controller, but WITHOUT any HTTP overhead. Perfect for testing!
214
- *
215
- * The client uses the ApiPrototype class to discover routes via decorators,
216
- * then creates pre-configured invoker functions for each API method.
217
- *
218
- * IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
219
- * For each API method, it sets up the filter chain ONCE during proxy creation,
220
- * so subsequent calls reuse the same filter chain (efficient!).
221
- *
222
- * @param apiPrototype - The abstract API prototype whose routing decorators declare the routes
223
- * @returns A proxy that implements the API interface
224
- *
225
- * Example:
226
- * ```typescript
227
- * const saveApi = server.createApiClient<SaveApi>(SaveApi);
228
- * const response = await saveApi.save(request);
229
- * ```
230
- */
231
- // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args
232
- createApiClient(apiPrototype) {
233
- if (!this.initialized) {
234
- throw new Error('Server not initialized. Call initialize() before createApiClient().');
235
- }
236
- // Delegates to the shared factory (same code path as WebpiecesRouteCreator.createApiClient)
237
- if (!this.clientFactory) {
238
- this.clientFactory = new InProcessApiClientFactory_1.InProcessApiClientFactory(this.routeBuilder);
239
- }
240
- return this.clientFactory.createApiClient(apiPrototype);
241
- }
242
- clientFactory;
243
- };
244
- exports.WebpiecesServerImpl = WebpiecesServerImpl;
245
- exports.WebpiecesServerImpl = WebpiecesServerImpl = tslib_1.__decorate([
246
- (0, core_util_1.DocumentDesign)(),
247
- (0, http_routing_1.provideSingleton)(),
248
- (0, inversify_1.injectable)(),
249
- tslib_1.__param(0, (0, inversify_1.inject)(http_routing_1.WEBAPP_META_TOKEN)),
250
- tslib_1.__param(1, (0, inversify_1.inject)(http_routing_1.RouteBuilderImpl)),
251
- tslib_1.__param(2, (0, inversify_1.inject)(WebpiecesMiddleware_1.WebpiecesMiddleware)),
252
- tslib_1.__metadata("design:paramtypes", [Object, http_routing_1.RouteBuilderImpl,
253
- WebpiecesMiddleware_1.WebpiecesMiddleware])
254
- ], WebpiecesServerImpl);
255
- //# sourceMappingURL=WebpiecesServerImpl.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebpiecesServerImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServerImpl.ts"],"names":[],"mappings":";;;;AAAA,8DAAyC;AACzC,yCAAyE;AACzE,wEAAoE;AACpE,0DAKiC;AACjC,oDAAoD;AAEpD,+DAA0D;AAC1D,mEAA8D;AAC9D,2EAAsE;AACtE,oDAAgD;AAEhD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;AAK7C,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAgBW;IACD;IACG;IAjBjC,kBAAkB,CAAa;IAEvC;;;;OAIG;IACK,YAAY,CAAa;IAEzB,WAAW,GAAG,KAAK,CAAC;IACpB,GAAG,CAAW;IACd,MAAM,CAAiC;IACvC,IAAI,GAAW,IAAI,CAAC;IAE5B,YACuC,IAAgB,EACjB,YAA8B,EAC3B,UAA+B;QAFjC,SAAI,GAAJ,IAAI,CAAY;QACjB,iBAAY,GAAZ,YAAY,CAAkB;QAC3B,eAAU,GAAV,UAAU,CAAqB;IACrE,CAAC;IAEJ;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,CACZ,kBAA6B,EAC7B,YAA8B;QAE9B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAE7C,+DAA+D;QAC/D,IAAI,CAAC,YAAY,GAAG,IAAI,qBAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC;QAEvE,0FAA0F;QAC1F,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAElD,oCAAoC;QACpC,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAEvC,gFAAgF;QAChF,kFAAkF;QAClF,qFAAqF;QACrF,uFAAuF;QACvF,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,+BAAgB,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAEtF,iCAAiC;QACjC,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,aAAa,CAAC,YAA8B;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QAEzC,yEAAyE;QACzE,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAEpD,8CAA8C;QAC9C,kFAAkF;QAClF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;QAED,gEAAgE;QAChE,IAAI,YAAY,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACK,cAAc;QAClB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAE3C,mDAAmD;QACnD,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACrC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,CAAC,OAAe,IAAI,EAAE,QAAkB;QAC/C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;QACjF,CAAC;QAED,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAEjB,IAAG,QAAQ,EAAE,CAAC;YACV,oDAAoD;YACpD,uEAAuE;YACvE,OAAO;QACX,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;QAErB,yDAAyD;QACzD,yDAAyD;QACzD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAEvE,0CAA0C;QAC1C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAEjD,oCAAoC;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAEjE,gEAAgE;QAChE,gEAAgE;QAChE,MAAM,YAAY,GAAG,IAAI,6CAAqB,CAC1C,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAClB,CAAC;QACF,MAAM,UAAU,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC;QAExD,oCAAoC;QACpC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAa,EAAE,EAAE;gBACxD,IAAI,KAAK,EAAE,CAAC;oBACR,GAAG,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;oBAC9D,MAAM,CAAC,KAAK,CAAC,CAAC;oBACd,OAAO;gBACX,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,0DAA0D,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBAChF,GAAG,CAAC,IAAI,CAAC,gCAAgC,UAAU,SAAS,CAAC,CAAC;gBAC9D,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC;IAClB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI;QACN,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACf,OAAO;QACX,CAAC;QAED,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzC,IAAI,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,GAAW,EAAE,EAAE;gBAC/B,IAAI,GAAG,EAAE,CAAC;oBACN,GAAG,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;oBAC3D,MAAM,CAAC,GAAG,CAAC,CAAC;oBACZ,OAAO;gBACX,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;gBAC7C,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;OAMG;IACH,YAAY;QACR,OAAO,IAAI,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QAC3F,CAAC;QAED,4FAA4F;QAC5F,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,GAAG,IAAI,qDAAyB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;IAC5D,CAAC;IAEO,aAAa,CAA6B;CACrD,CAAA;AAlPY,kDAAmB;8BAAnB,mBAAmB;IAH/B,IAAA,0BAAc,GAAE;IAChB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;IAiBJ,mBAAA,IAAA,kBAAM,EAAC,gCAAiB,CAAC,CAAA;IACzB,mBAAA,IAAA,kBAAM,EAAC,+BAAgB,CAAC,CAAA;IACxB,mBAAA,IAAA,kBAAM,EAAC,yCAAmB,CAAC,CAAA;qDADoB,+BAAgB;QACf,yCAAmB;GAlB/D,mBAAmB,CAkP/B","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 {DocumentDesign} from '@webpieces/core-util';\nimport {WebpiecesServer} from './WebpiecesServer';\nimport {WebpiecesMiddleware} from './WebpiecesMiddleware';\nimport {WebpiecesRouteCreator} from './WebpiecesRouteCreator';\nimport {InProcessApiClientFactory} from './InProcessApiClientFactory';\nimport {LogManager} from '@webpieces/core-util';\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@DocumentDesign()\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 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"]}