@webpieces/http-server 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -5
- package/src/WebpiecesFactory.d.ts +23 -4
- package/src/WebpiecesFactory.js +25 -8
- package/src/WebpiecesFactory.js.map +1 -1
- package/src/WebpiecesMiddleware.d.ts +59 -0
- package/src/WebpiecesMiddleware.js +159 -0
- package/src/WebpiecesMiddleware.js.map +1 -0
- package/src/WebpiecesServer.d.ts +42 -5
- package/src/WebpiecesServer.js.map +1 -1
- package/src/WebpiecesServerImpl.d.ts +57 -28
- package/src/WebpiecesServerImpl.js +136 -135
- package/src/WebpiecesServerImpl.js.map +1 -1
- package/src/filters/ContextFilter.d.ts +1 -1
- package/src/filters/ContextFilter.js.map +1 -1
- package/src/filters/LogApiFilter.d.ts +40 -0
- package/src/filters/LogApiFilter.js +92 -0
- package/src/filters/LogApiFilter.js.map +1 -0
- package/src/index.d.ts +3 -4
- package/src/index.js +13 -15
- package/src/index.js.map +1 -1
- package/src/FilterMatcher.d.ts +0 -39
- package/src/FilterMatcher.js +0 -69
- package/src/FilterMatcher.js.map +0 -1
- package/src/MethodMeta.d.ts +0 -42
- package/src/MethodMeta.js +0 -40
- package/src/MethodMeta.js.map +0 -1
- package/src/RouteBuilderImpl.d.ts +0 -90
- package/src/RouteBuilderImpl.js +0 -141
- package/src/RouteBuilderImpl.js.map +0 -1
- package/src/RouteHandler.d.ts +0 -22
- package/src/RouteHandler.js +0 -20
- package/src/RouteHandler.js.map +0 -1
- package/src/filters/JsonFilter.d.ts +0 -62
- package/src/filters/JsonFilter.js +0 -146
- package/src/filters/JsonFilter.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "WebPieces server with filter chain and dependency injection",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/core-
|
|
26
|
-
"@webpieces/
|
|
27
|
-
"@webpieces/http-
|
|
28
|
-
"@webpieces/http-filters": "0.2.14"
|
|
25
|
+
"@webpieces/core-util": "0.2.16",
|
|
26
|
+
"@webpieces/http-routing": "0.2.16",
|
|
27
|
+
"@webpieces/http-filters": "0.2.16"
|
|
29
28
|
}
|
|
30
29
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContainerModule } from 'inversify';
|
|
2
|
+
import { WebAppMeta } from '@webpieces/http-routing';
|
|
2
3
|
import { WebpiecesServer } from './WebpiecesServer';
|
|
3
4
|
/**
|
|
4
5
|
* WebpiecesFactory - Factory for creating WebPieces server instances.
|
|
@@ -15,10 +16,16 @@ import { WebpiecesServer } from './WebpiecesServer';
|
|
|
15
16
|
*
|
|
16
17
|
* Usage:
|
|
17
18
|
* ```typescript
|
|
19
|
+
* // Production
|
|
18
20
|
* const server = WebpiecesFactory.create(new ProdServerMeta());
|
|
19
21
|
* server.start(8080);
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
*
|
|
23
|
+
* // Testing with overrides
|
|
24
|
+
* const overrides = new ContainerModule((bind) => {
|
|
25
|
+
* bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);
|
|
26
|
+
* });
|
|
27
|
+
* const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
|
|
28
|
+
* const api = server.createApiClient(SaveApiPrototype);
|
|
22
29
|
* ```
|
|
23
30
|
*
|
|
24
31
|
* This pattern:
|
|
@@ -36,9 +43,21 @@ export declare class WebpiecesFactory {
|
|
|
36
43
|
* 2. Loads framework bindings via buildProviderModule()
|
|
37
44
|
* 3. Resolves the server implementation from DI
|
|
38
45
|
* 4. Initializes the server with the container and meta
|
|
46
|
+
* 5. Loads optional override module (for testing)
|
|
39
47
|
*
|
|
40
48
|
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
49
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
|
|
41
50
|
* @returns A fully initialized WebpiecesServer ready to start()
|
|
42
51
|
*/
|
|
43
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Create a new WebPieces server instance asynchronously.
|
|
54
|
+
*
|
|
55
|
+
* Use this method when you need async operations in your override modules
|
|
56
|
+
* (e.g., rebind() in new Inversify versions).
|
|
57
|
+
*
|
|
58
|
+
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
59
|
+
* @param overrides - Optional ContainerModule for test overrides (can use async operations)
|
|
60
|
+
* @returns Promise of a fully initialized WebpiecesServer ready to start()
|
|
61
|
+
*/
|
|
62
|
+
static create(meta: WebAppMeta, overrides?: ContainerModule, testMode?: boolean): Promise<WebpiecesServer>;
|
|
44
63
|
}
|
package/src/WebpiecesFactory.js
CHANGED
|
@@ -19,10 +19,16 @@ const WebpiecesServerImpl_1 = require("./WebpiecesServerImpl");
|
|
|
19
19
|
*
|
|
20
20
|
* Usage:
|
|
21
21
|
* ```typescript
|
|
22
|
+
* // Production
|
|
22
23
|
* const server = WebpiecesFactory.create(new ProdServerMeta());
|
|
23
24
|
* server.start(8080);
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
|
+
* // Testing with overrides
|
|
27
|
+
* const overrides = new ContainerModule((bind) => {
|
|
28
|
+
* bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);
|
|
29
|
+
* });
|
|
30
|
+
* const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
|
|
31
|
+
* const api = server.createApiClient(SaveApiPrototype);
|
|
26
32
|
* ```
|
|
27
33
|
*
|
|
28
34
|
* This pattern:
|
|
@@ -40,20 +46,31 @@ class WebpiecesFactory {
|
|
|
40
46
|
* 2. Loads framework bindings via buildProviderModule()
|
|
41
47
|
* 3. Resolves the server implementation from DI
|
|
42
48
|
* 4. Initializes the server with the container and meta
|
|
49
|
+
* 5. Loads optional override module (for testing)
|
|
43
50
|
*
|
|
44
51
|
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
52
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
|
|
45
53
|
* @returns A fully initialized WebpiecesServer ready to start()
|
|
46
54
|
*/
|
|
47
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Create a new WebPieces server instance asynchronously.
|
|
57
|
+
*
|
|
58
|
+
* Use this method when you need async operations in your override modules
|
|
59
|
+
* (e.g., rebind() in new Inversify versions).
|
|
60
|
+
*
|
|
61
|
+
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
62
|
+
* @param overrides - Optional ContainerModule for test overrides (can use async operations)
|
|
63
|
+
* @returns Promise of a fully initialized WebpiecesServer ready to start()
|
|
64
|
+
*/
|
|
65
|
+
static async create(meta, overrides, testMode) {
|
|
48
66
|
// Create WebPieces container for framework-level bindings
|
|
49
67
|
const webpiecesContainer = new inversify_1.Container();
|
|
50
68
|
// Load buildProviderModule to auto-scan for @provideSingleton decorators
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Resolve WebpiecesServerImpl from DI container (proper DI - no 'new'!)
|
|
69
|
+
await webpiecesContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
70
|
+
// Resolve WebpiecesServerImpl from DI container
|
|
54
71
|
const serverImpl = webpiecesContainer.get(WebpiecesServerImpl_1.WebpiecesServerImpl);
|
|
55
|
-
// Initialize the server (loads app DI modules, registers routes)
|
|
56
|
-
serverImpl.initialize(webpiecesContainer, meta);
|
|
72
|
+
// Initialize the server asynchronously (loads app DI modules, registers routes)
|
|
73
|
+
await serverImpl.initialize(webpiecesContainer, meta, overrides);
|
|
57
74
|
// Return as interface to hide initialize() from consumers
|
|
58
75
|
return serverImpl;
|
|
59
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesFactory.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"WebpiecesFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesFactory.ts"],"names":[],"mappings":";;;AAAA,yCAAuD;AACvD,wEAAsE;AAGtE,+DAA4D;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAa,gBAAgB;IACzB;;;;;;;;;;;;;OAaG;IAEH;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CACf,IAAgB,EAChB,SAA2B,EAC3B,QAAkB;QAElB,0DAA0D;QAC1D,MAAM,kBAAkB,GAAG,IAAI,qBAAS,EAAE,CAAC;QAE3C,yEAAyE;QACzE,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAA,wCAAmB,GAAE,CAAC,CAAC;QAErD,gDAAgD;QAChD,MAAM,UAAU,GAAG,kBAAkB,CAAC,GAAG,CAAC,yCAAmB,CAAC,CAAC;QAE/D,gFAAgF;QAChF,MAAM,UAAU,CAAC,UAAU,CAAC,kBAAkB,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAEjE,0DAA0D;QAC1D,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA9CD,4CA8CC","sourcesContent":["import { Container, ContainerModule } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { WebAppMeta } from '@webpieces/http-routing';\nimport { WebpiecesServer } from './WebpiecesServer';\nimport { WebpiecesServerImpl } from './WebpiecesServerImpl';\n\n/**\n * WebpiecesFactory - Factory for creating WebPieces server instances.\n *\n * This factory encapsulates the server creation and initialization logic:\n * 1. Creates the WebPieces DI container\n * 2. Loads the provider module for @provideSingleton decorators\n * 3. Resolves WebpiecesServerImpl from DI\n * 4. Calls initialize() with the container and meta\n * 5. Returns the server as the WebpiecesServer interface\n *\n * The returned WebpiecesServer interface only exposes start() and stop(),\n * hiding the internal initialize() method from consumers.\n *\n * Usage:\n * ```typescript\n * // Production\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * server.start(8080);\n *\n * // Testing with overrides\n * const overrides = new ContainerModule((bind) => {\n * bind(TYPES.RemoteApi).toConstantValue(mockRemoteApi);\n * });\n * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);\n * const api = server.createApiClient(SaveApiPrototype);\n * ```\n *\n * This pattern:\n * - Enforces proper initialization order\n * - Hides implementation details from consumers\n * - Makes the API simpler and harder to misuse\n * - Follows the principle of least privilege\n */\nexport class WebpiecesFactory {\n /**\n * Create a new WebPieces server instance.\n *\n * This method:\n * 1. Creates the WebPieces framework DI container\n * 2. Loads framework bindings via buildProviderModule()\n * 3. Resolves the server implementation from DI\n * 4. Initializes the server with the container and meta\n * 5. Loads optional override module (for testing)\n *\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)\n * @returns A fully initialized WebpiecesServer ready to start()\n */\n\n /**\n * Create a new WebPieces server instance asynchronously.\n *\n * Use this method when you need async operations in your override modules\n * (e.g., rebind() in new Inversify versions).\n *\n * @param meta - User-provided WebAppMeta with DI modules and routes\n * @param overrides - Optional ContainerModule for test overrides (can use async operations)\n * @returns Promise of a fully initialized WebpiecesServer ready to start()\n */\n static async create(\n meta: WebAppMeta,\n overrides?: ContainerModule,\n testMode?: boolean\n ): Promise<WebpiecesServer> {\n // Create WebPieces container for framework-level bindings\n const webpiecesContainer = new Container();\n\n // Load buildProviderModule to auto-scan for @provideSingleton decorators\n await webpiecesContainer.load(buildProviderModule());\n\n // Resolve WebpiecesServerImpl from DI container\n const serverImpl = webpiecesContainer.get(WebpiecesServerImpl);\n\n // Initialize the server asynchronously (loads app DI modules, registers routes)\n await serverImpl.initialize(webpiecesContainer, meta, overrides);\n\n // Return as interface to hide initialize() from consumers\n return serverImpl;\n }\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { MethodMeta } from '@webpieces/http-routing';
|
|
3
|
+
import { RouteMetadata } from '@webpieces/http-api';
|
|
4
|
+
import { Service, WpResponse } from '@webpieces/http-filters';
|
|
5
|
+
export declare class ExpressWrapper {
|
|
6
|
+
private service;
|
|
7
|
+
private routeMeta;
|
|
8
|
+
private jsonSerializer;
|
|
9
|
+
constructor(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata);
|
|
10
|
+
execute(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Handle errors - translate to JSON ProtocolError.
|
|
13
|
+
* PUBLIC so wrapExpress can call it for symmetric error handling.
|
|
14
|
+
* Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
|
|
15
|
+
*/
|
|
16
|
+
handleError(res: Response, error: unknown): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* WebpiecesMiddleware - Express middleware for WebPieces server.
|
|
20
|
+
*
|
|
21
|
+
* This class contains all Express middleware used by WebpiecesServer:
|
|
22
|
+
* 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
|
|
23
|
+
* 2. logNextLayer - Request/response logging
|
|
24
|
+
* 3. jsonTranslator - JSON Content-Type validation and error translation
|
|
25
|
+
*
|
|
26
|
+
* The middleware is injected into WebpiecesServerImpl and registered with Express
|
|
27
|
+
* in the start() method.
|
|
28
|
+
*
|
|
29
|
+
* IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
|
|
30
|
+
* Express's registered route handlers (created by RouteBuilder.createHandler()).
|
|
31
|
+
* jsonTranslator only validates Content-Type and translates errors to JSON.
|
|
32
|
+
*
|
|
33
|
+
* DI Pattern: This class is registered via @provideSingleton() with no dependencies.
|
|
34
|
+
*/
|
|
35
|
+
export declare class WebpiecesMiddleware {
|
|
36
|
+
/**
|
|
37
|
+
* Global error handler middleware - catches ALL unhandled errors.
|
|
38
|
+
* Returns HTML 500 error page for any errors that escape the filter chain.
|
|
39
|
+
*
|
|
40
|
+
* This is the outermost safety net - JsonTranslator catches JSON API errors,
|
|
41
|
+
* this catches everything else.
|
|
42
|
+
*/
|
|
43
|
+
globalErrorHandler(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Logging middleware - logs request/response flow.
|
|
46
|
+
* Demonstrates middleware execution order.
|
|
47
|
+
* IMPORTANT: Must be async and await next() to properly chain with async middleware.
|
|
48
|
+
*/
|
|
49
|
+
logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Create an ExpressWrapper for a route.
|
|
52
|
+
* The wrapper handles the full request/response cycle (symmetric design).
|
|
53
|
+
*
|
|
54
|
+
* @param service - The service wrapping the filter chain and controller
|
|
55
|
+
* @param routeMeta - Route metadata for MethodMeta and DTO type
|
|
56
|
+
* @returns ExpressWrapper instance
|
|
57
|
+
*/
|
|
58
|
+
createExpressWrapper(service: Service<MethodMeta, WpResponse<unknown>>, routeMeta: RouteMetadata): ExpressWrapper;
|
|
59
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebpiecesMiddleware = exports.ExpressWrapper = 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_api_1 = require("@webpieces/http-api");
|
|
8
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
9
|
+
const typescript_json_serializer_1 = require("typescript-json-serializer");
|
|
10
|
+
class ExpressWrapper {
|
|
11
|
+
constructor(service, routeMeta) {
|
|
12
|
+
this.service = service;
|
|
13
|
+
this.routeMeta = routeMeta;
|
|
14
|
+
this.jsonSerializer = new typescript_json_serializer_1.JsonSerializer();
|
|
15
|
+
}
|
|
16
|
+
async execute(req, res, next) {
|
|
17
|
+
try {
|
|
18
|
+
// 1. Get request DTO class from routeMeta
|
|
19
|
+
const requestDtoClass = this.routeMeta.parameterTypes?.[0];
|
|
20
|
+
if (!requestDtoClass)
|
|
21
|
+
throw new Error('No request DTO class found for route');
|
|
22
|
+
// 2. Deserialize req.body → DTO instance
|
|
23
|
+
const requestDto = this.jsonSerializer.deserializeObject(req.body, requestDtoClass);
|
|
24
|
+
// 3. Create MethodMeta with deserialized DTO
|
|
25
|
+
const methodMeta = new http_routing_1.MethodMeta(this.routeMeta, requestDto);
|
|
26
|
+
// 4. Invoke the service (filter chain + controller)
|
|
27
|
+
const wpResponse = await this.service.invoke(methodMeta);
|
|
28
|
+
if (!wpResponse.response)
|
|
29
|
+
throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);
|
|
30
|
+
// 6. Serialize response → plain object
|
|
31
|
+
const responseDtoStr = this.jsonSerializer.serializeObject(wpResponse.response);
|
|
32
|
+
// 7. WRITE response as JSON (SYMMETRIC with reading request!)
|
|
33
|
+
res.status(200);
|
|
34
|
+
res.setHeader('Content-Type', 'application/json');
|
|
35
|
+
res.json(responseDtoStr);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
// 8. Handle errors (SYMMETRIC - wrapExpress owns error handling!)
|
|
39
|
+
this.handleError(res, err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Handle errors - translate to JSON ProtocolError.
|
|
44
|
+
* PUBLIC so wrapExpress can call it for symmetric error handling.
|
|
45
|
+
* Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.
|
|
46
|
+
*/
|
|
47
|
+
handleError(res, error) {
|
|
48
|
+
if (res.headersSent) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const protocolError = new http_api_1.ProtocolError();
|
|
52
|
+
if (error instanceof http_api_1.HttpError) {
|
|
53
|
+
protocolError.message = error.message;
|
|
54
|
+
protocolError.subType = error.subType;
|
|
55
|
+
protocolError.name = error.name;
|
|
56
|
+
if (error instanceof http_api_1.HttpBadRequestError) {
|
|
57
|
+
protocolError.field = error.field;
|
|
58
|
+
protocolError.guiAlertMessage = error.guiMessage;
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof http_api_1.HttpVendorError) {
|
|
61
|
+
protocolError.waitSeconds = error.waitSeconds;
|
|
62
|
+
}
|
|
63
|
+
if (error instanceof http_api_1.HttpUserError) {
|
|
64
|
+
protocolError.errorCode = error.errorCode;
|
|
65
|
+
}
|
|
66
|
+
res.status(error.code).json(protocolError);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Unknown error - 500
|
|
70
|
+
const err = (0, core_util_1.toError)(error);
|
|
71
|
+
protocolError.message = 'Internal Server Error';
|
|
72
|
+
console.error('[JsonTranslator] Unexpected error:', err);
|
|
73
|
+
res.status(500).json(protocolError);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.ExpressWrapper = ExpressWrapper;
|
|
78
|
+
/**
|
|
79
|
+
* WebpiecesMiddleware - Express middleware for WebPieces server.
|
|
80
|
+
*
|
|
81
|
+
* This class contains all Express middleware used by WebpiecesServer:
|
|
82
|
+
* 1. globalErrorHandler - Outermost error handler, returns HTML 500 page
|
|
83
|
+
* 2. logNextLayer - Request/response logging
|
|
84
|
+
* 3. jsonTranslator - JSON Content-Type validation and error translation
|
|
85
|
+
*
|
|
86
|
+
* The middleware is injected into WebpiecesServerImpl and registered with Express
|
|
87
|
+
* in the start() method.
|
|
88
|
+
*
|
|
89
|
+
* IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via
|
|
90
|
+
* Express's registered route handlers (created by RouteBuilder.createHandler()).
|
|
91
|
+
* jsonTranslator only validates Content-Type and translates errors to JSON.
|
|
92
|
+
*
|
|
93
|
+
* DI Pattern: This class is registered via @provideSingleton() with no dependencies.
|
|
94
|
+
*/
|
|
95
|
+
let WebpiecesMiddleware = class WebpiecesMiddleware {
|
|
96
|
+
/**
|
|
97
|
+
* Global error handler middleware - catches ALL unhandled errors.
|
|
98
|
+
* Returns HTML 500 error page for any errors that escape the filter chain.
|
|
99
|
+
*
|
|
100
|
+
* This is the outermost safety net - JsonTranslator catches JSON API errors,
|
|
101
|
+
* this catches everything else.
|
|
102
|
+
*/
|
|
103
|
+
async globalErrorHandler(req, res, next) {
|
|
104
|
+
console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);
|
|
105
|
+
try {
|
|
106
|
+
// await next() catches BOTH:
|
|
107
|
+
// 1. Synchronous throws from next() itself
|
|
108
|
+
// 2. Rejected promises from downstream async middleware
|
|
109
|
+
await next();
|
|
110
|
+
console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (success):', req.method, req.path);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
const error = (0, core_util_1.toError)(err);
|
|
114
|
+
console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);
|
|
115
|
+
if (!res.headersSent) {
|
|
116
|
+
// Return HTML error page (not JSON - JsonTranslator handles JSON errors)
|
|
117
|
+
res.status(500).send(`
|
|
118
|
+
<!DOCTYPE html>
|
|
119
|
+
<html>
|
|
120
|
+
<head><title>Server Error</title></head>
|
|
121
|
+
<body>
|
|
122
|
+
<h1>You hit a server error</h1>
|
|
123
|
+
<p>An unexpected error occurred while processing your request.</p>
|
|
124
|
+
<pre>${error.message}</pre>
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
console.log('🔴 [Layer 1: GlobalErrorHandler] Request END (error):', req.method, req.path);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Logging middleware - logs request/response flow.
|
|
134
|
+
* Demonstrates middleware execution order.
|
|
135
|
+
* IMPORTANT: Must be async and await next() to properly chain with async middleware.
|
|
136
|
+
*/
|
|
137
|
+
async logNextLayer(req, res, next) {
|
|
138
|
+
console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);
|
|
139
|
+
await next();
|
|
140
|
+
console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create an ExpressWrapper for a route.
|
|
144
|
+
* The wrapper handles the full request/response cycle (symmetric design).
|
|
145
|
+
*
|
|
146
|
+
* @param service - The service wrapping the filter chain and controller
|
|
147
|
+
* @param routeMeta - Route metadata for MethodMeta and DTO type
|
|
148
|
+
* @returns ExpressWrapper instance
|
|
149
|
+
*/
|
|
150
|
+
createExpressWrapper(service, routeMeta) {
|
|
151
|
+
return new ExpressWrapper(service, routeMeta);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
exports.WebpiecesMiddleware = WebpiecesMiddleware;
|
|
155
|
+
exports.WebpiecesMiddleware = WebpiecesMiddleware = tslib_1.__decorate([
|
|
156
|
+
(0, http_routing_1.provideSingleton)(),
|
|
157
|
+
(0, inversify_1.injectable)()
|
|
158
|
+
], WebpiecesMiddleware);
|
|
159
|
+
//# sourceMappingURL=WebpiecesMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebpiecesMiddleware.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { injectable } from 'inversify';\nimport { provideSingleton, MethodMeta, ExpressRouteHandler } from '@webpieces/http-routing';\nimport {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpVendorError,\n HttpUserError,\n RouteMetadata,\n} from '@webpieces/http-api';\nimport { Service, WpResponse } from '@webpieces/http-filters';\nimport { toError } from '@webpieces/core-util';\nimport { JsonSerializer } from 'typescript-json-serializer';\n\nexport class ExpressWrapper {\n private jsonSerializer = new JsonSerializer();\n\n constructor(\n private service: Service<MethodMeta, WpResponse<unknown>>,\n private routeMeta: RouteMetadata\n ) {\n }\n\n public async execute(req: Request, res: Response, next: NextFunction) {\n try {\n // 1. Get request DTO class from routeMeta\n const requestDtoClass = this.routeMeta.parameterTypes?.[0];\n if(!requestDtoClass)\n throw new Error('No request DTO class found for route');\n\n // 2. Deserialize req.body → DTO instance\n const requestDto = this.jsonSerializer.deserializeObject(req.body, requestDtoClass);\n // 3. Create MethodMeta with deserialized DTO\n const methodMeta = new MethodMeta(this.routeMeta, requestDto);\n // 4. Invoke the service (filter chain + controller)\n const wpResponse = await this.service.invoke(methodMeta);\n if(!wpResponse.response)\n throw new Error(`Route chain(filters & all) is not returning a response. ${this.routeMeta.controllerClassName}.${this.routeMeta.methodName}`);\n\n // 6. Serialize response → plain object\n const responseDtoStr = this.jsonSerializer.serializeObject(wpResponse.response);\n\n // 7. WRITE response as JSON (SYMMETRIC with reading request!)\n res.status(200);\n res.setHeader('Content-Type', 'application/json');\n res.json(responseDtoStr);\n } catch (err: unknown) {\n // 8. Handle errors (SYMMETRIC - wrapExpress owns error handling!)\n this.handleError(res, err);\n }\n }\n\n /**\n * Handle errors - translate to JSON ProtocolError.\n * PUBLIC so wrapExpress can call it for symmetric error handling.\n * Maps HttpError subclasses to appropriate HTTP status codes and ProtocolError response.\n */\n public handleError(res: Response, error: unknown): void {\n if (res.headersSent) {\n return;\n }\n\n const protocolError = new ProtocolError();\n\n if (error instanceof HttpError) {\n protocolError.message = error.message;\n protocolError.subType = error.subType;\n protocolError.name = error.name;\n\n if (error instanceof HttpBadRequestError) {\n protocolError.field = error.field;\n protocolError.guiAlertMessage = error.guiMessage;\n }\n if (error instanceof HttpVendorError) {\n protocolError.waitSeconds = error.waitSeconds;\n }\n if (error instanceof HttpUserError) {\n protocolError.errorCode = error.errorCode;\n }\n\n res.status(error.code).json(protocolError);\n } else {\n // Unknown error - 500\n const err = toError(error);\n protocolError.message = 'Internal Server Error';\n console.error('[JsonTranslator] Unexpected error:', err);\n res.status(500).json(protocolError);\n }\n }\n}\n/**\n * WebpiecesMiddleware - Express middleware for WebPieces server.\n *\n * This class contains all Express middleware used by WebpiecesServer:\n * 1. globalErrorHandler - Outermost error handler, returns HTML 500 page\n * 2. logNextLayer - Request/response logging\n * 3. jsonTranslator - JSON Content-Type validation and error translation\n *\n * The middleware is injected into WebpiecesServerImpl and registered with Express\n * in the start() method.\n *\n * IMPORTANT: jsonTranslator does NOT dispatch routes - route dispatch happens via\n * Express's registered route handlers (created by RouteBuilder.createHandler()).\n * jsonTranslator only validates Content-Type and translates errors to JSON.\n *\n * DI Pattern: This class is registered via @provideSingleton() with no dependencies.\n */\n@provideSingleton()\n@injectable()\nexport class WebpiecesMiddleware {\n\n /**\n * Global error handler middleware - catches ALL unhandled errors.\n * Returns HTML 500 error page for any errors that escape the filter chain.\n *\n * This is the outermost safety net - JsonTranslator catches JSON API errors,\n * this catches everything else.\n */\n async globalErrorHandler(\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> {\n console.log('🔴 [Layer 1: GlobalErrorHandler] Request START:', req.method, req.path);\n\n try {\n // await next() catches BOTH:\n // 1. Synchronous throws from next() itself\n // 2. Rejected promises from downstream async middleware\n await next();\n console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (success):',\n req.method,\n req.path,\n );\n } catch (err: unknown) {\n const error = toError(err);\n console.error('🔴 [Layer 1: GlobalErrorHandler] Caught unhandled error:', error);\n if (!res.headersSent) {\n // Return HTML error page (not JSON - JsonTranslator handles JSON errors)\n res.status(500).send(`\n <!DOCTYPE html>\n <html>\n <head><title>Server Error</title></head>\n <body>\n <h1>You hit a server error</h1>\n <p>An unexpected error occurred while processing your request.</p>\n <pre>${error.message}</pre>\n </body>\n </html>\n `);\n }\n console.log(\n '🔴 [Layer 1: GlobalErrorHandler] Request END (error):',\n req.method,\n req.path,\n );\n }\n }\n\n /**\n * Logging middleware - logs request/response flow.\n * Demonstrates middleware execution order.\n * IMPORTANT: Must be async and await next() to properly chain with async middleware.\n */\n async logNextLayer(req: Request, res: Response, next: NextFunction): Promise<void> {\n console.log('🟡 [Layer 2: LogNextLayer] Before next() -', req.method, req.path);\n await next();\n console.log('🟡 [Layer 2: LogNextLayer] After next() -', req.method, req.path);\n }\n\n /**\n * Create an ExpressWrapper for a route.\n * The wrapper handles the full request/response cycle (symmetric design).\n *\n * @param service - The service wrapping the filter chain and controller\n * @param routeMeta - Route metadata for MethodMeta and DTO type\n * @returns ExpressWrapper instance\n */\n createExpressWrapper(\n service: Service<MethodMeta, WpResponse<unknown>>,\n routeMeta: RouteMetadata,\n ): ExpressWrapper {\n return new ExpressWrapper(service, routeMeta);\n }\n}\n"]}
|
package/src/WebpiecesServer.d.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
+
import { Container } from 'inversify';
|
|
1
2
|
/**
|
|
2
3
|
* WebpiecesServer - Public interface for WebPieces server.
|
|
3
4
|
*
|
|
4
|
-
* This interface exposes
|
|
5
|
+
* This interface exposes the methods needed by application code:
|
|
5
6
|
* - start(): Start the HTTP server
|
|
6
7
|
* - stop(): Stop the HTTP server
|
|
8
|
+
* - createApiClient(): Create API client proxy for testing (no HTTP!)
|
|
9
|
+
* - getContainer(): Access DI container for verification
|
|
7
10
|
*
|
|
8
11
|
* The initialization logic is hidden inside WebpiecesFactory.create().
|
|
9
12
|
* This provides a clean API and prevents accidental re-initialization.
|
|
10
13
|
*
|
|
11
14
|
* Usage:
|
|
12
15
|
* ```typescript
|
|
16
|
+
* // Production
|
|
13
17
|
* const server = WebpiecesFactory.create(new ProdServerMeta());
|
|
14
18
|
* await server.start(8080);
|
|
15
|
-
*
|
|
16
|
-
* //
|
|
17
|
-
* server.
|
|
19
|
+
*
|
|
20
|
+
* // Testing (no HTTP needed!)
|
|
21
|
+
* const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);
|
|
22
|
+
* const api = server.createApiClient(SaveApiPrototype);
|
|
23
|
+
* const response = await api.save(request);
|
|
18
24
|
* ```
|
|
19
25
|
*/
|
|
20
26
|
export interface WebpiecesServer {
|
|
@@ -29,6 +35,37 @@ export interface WebpiecesServer {
|
|
|
29
35
|
start(port?: number): Promise<void>;
|
|
30
36
|
/**
|
|
31
37
|
* Stop the HTTP server.
|
|
38
|
+
* Returns a Promise that resolves when the server is stopped,
|
|
39
|
+
* or rejects if there's an error stopping the server.
|
|
40
|
+
*
|
|
41
|
+
* @returns Promise that resolves when server is stopped
|
|
42
|
+
*/
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Create an API client proxy for testing.
|
|
46
|
+
*
|
|
47
|
+
* This creates a client that routes calls through the full filter chain
|
|
48
|
+
* and controller, but WITHOUT any HTTP overhead. Perfect for testing!
|
|
49
|
+
*
|
|
50
|
+
* The client uses the ApiPrototype class to discover routes via decorators,
|
|
51
|
+
* then invokes the corresponding controller method through the filter chain.
|
|
52
|
+
*
|
|
53
|
+
* @param apiPrototype - The API prototype class with routing decorators (can be abstract)
|
|
54
|
+
* @returns A proxy that implements the API interface
|
|
55
|
+
*
|
|
56
|
+
* Example:
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
|
|
59
|
+
* const response = await saveApi.save(request);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
|
|
63
|
+
/**
|
|
64
|
+
* Get the application DI container.
|
|
65
|
+
*
|
|
66
|
+
* Useful for testing to verify state or access services directly.
|
|
67
|
+
*
|
|
68
|
+
* @returns The application Container
|
|
32
69
|
*/
|
|
33
|
-
|
|
70
|
+
getContainer(): Container;
|
|
34
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * WebpiecesServer - Public interface for WebPieces server.\n *\n * This interface exposes
|
|
1
|
+
{"version":3,"file":"WebpiecesServer.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/WebpiecesServer.ts"],"names":[],"mappings":"","sourcesContent":["import { Container } from 'inversify';\n\n/**\n * WebpiecesServer - Public interface for WebPieces server.\n *\n * This interface exposes the methods needed by application code:\n * - start(): Start the HTTP server\n * - stop(): Stop the HTTP server\n * - createApiClient(): Create API client proxy for testing (no HTTP!)\n * - getContainer(): Access DI container for verification\n *\n * The initialization logic is hidden inside WebpiecesFactory.create().\n * This provides a clean API and prevents accidental re-initialization.\n *\n * Usage:\n * ```typescript\n * // Production\n * const server = WebpiecesFactory.create(new ProdServerMeta());\n * await server.start(8080);\n *\n * // Testing (no HTTP needed!)\n * const server = WebpiecesFactory.create(new ProdServerMeta(), overrides);\n * const api = server.createApiClient(SaveApiPrototype);\n * const response = await api.save(request);\n * ```\n */\nexport interface WebpiecesServer {\n /**\n * Start the HTTP server with Express.\n * Returns a Promise that resolves when the server is listening,\n * or rejects if the server fails to start.\n *\n * @param port - The port to listen on (default: 8080)\n * @returns Promise that resolves when server is ready\n */\n start(port?: number): Promise<void>;\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 stop(): Promise<void>;\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 invokes the corresponding controller method through the filter chain.\n *\n * @param apiPrototype - The API prototype class with routing decorators (can be abstract)\n * @returns A proxy that implements the API interface\n *\n * Example:\n * ```typescript\n * const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);\n * const response = await saveApi.save(request);\n * ```\n */\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;\n\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}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Container } from 'inversify';
|
|
2
|
-
import { WebAppMeta } from '@webpieces/
|
|
3
|
-
import { RouteBuilderImpl } from './RouteBuilderImpl';
|
|
1
|
+
import { Container, ContainerModule } from 'inversify';
|
|
2
|
+
import { ExpressRouteHandler, RouteBuilderImpl, WebAppMeta } from '@webpieces/http-routing';
|
|
4
3
|
import { WebpiecesServer } from './WebpiecesServer';
|
|
4
|
+
import { WebpiecesMiddleware } from './WebpiecesMiddleware';
|
|
5
5
|
/**
|
|
6
6
|
* WebpiecesServerImpl - Internal server implementation.
|
|
7
7
|
*
|
|
@@ -28,6 +28,7 @@ import { WebpiecesServer } from './WebpiecesServer';
|
|
|
28
28
|
*/
|
|
29
29
|
export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
30
30
|
private routeBuilder;
|
|
31
|
+
private middleware;
|
|
31
32
|
private meta;
|
|
32
33
|
private webpiecesContainer;
|
|
33
34
|
/**
|
|
@@ -40,7 +41,7 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
40
41
|
private app?;
|
|
41
42
|
private server?;
|
|
42
43
|
private port;
|
|
43
|
-
constructor(routeBuilder: RouteBuilderImpl);
|
|
44
|
+
constructor(routeBuilder: RouteBuilderImpl, middleware: WebpiecesMiddleware);
|
|
44
45
|
/**
|
|
45
46
|
* Initialize the server (DI container, routes, filters).
|
|
46
47
|
* This is called by WebpiecesFactory.create() after resolving this class from DI.
|
|
@@ -48,8 +49,17 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
48
49
|
*
|
|
49
50
|
* @param webpiecesContainer - The framework container
|
|
50
51
|
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
52
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST)
|
|
51
53
|
*/
|
|
52
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the server asynchronously.
|
|
56
|
+
* Use this when overrides module contains async operations (e.g., rebind() in new Inversify).
|
|
57
|
+
*
|
|
58
|
+
* @param webpiecesContainer - The framework container
|
|
59
|
+
* @param meta - User-provided WebAppMeta with DI modules and routes
|
|
60
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST)
|
|
61
|
+
*/
|
|
62
|
+
initialize(webpiecesContainer: Container, meta: WebAppMeta, overrides?: ContainerModule): Promise<void>;
|
|
53
63
|
/**
|
|
54
64
|
* Load DI modules from WebAppMeta.
|
|
55
65
|
*
|
|
@@ -59,6 +69,8 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
59
69
|
* - Application modules -> appContainer
|
|
60
70
|
*
|
|
61
71
|
* For now, everything goes into appContainer which has access to webpiecesContainer.
|
|
72
|
+
*
|
|
73
|
+
* @param overrides - Optional ContainerModule for test overrides (loaded LAST to override bindings)
|
|
62
74
|
*/
|
|
63
75
|
private loadDIModules;
|
|
64
76
|
/**
|
|
@@ -71,19 +83,6 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
71
83
|
* - Understanding: Clear class name vs anonymous object
|
|
72
84
|
*/
|
|
73
85
|
private registerRoutes;
|
|
74
|
-
/**
|
|
75
|
-
* Global error handler middleware - catches ALL unhandled errors.
|
|
76
|
-
* Returns HTML 500 error page for any errors that escape the filter chain.
|
|
77
|
-
*
|
|
78
|
-
* This is the outermost safety net - JsonFilter catches JSON API errors,
|
|
79
|
-
* this catches everything else.
|
|
80
|
-
*/
|
|
81
|
-
private globalErrorHandler;
|
|
82
|
-
/**
|
|
83
|
-
* Logging middleware - logs request/response flow.
|
|
84
|
-
* Demonstrates middleware execution order.
|
|
85
|
-
*/
|
|
86
|
-
private logNextLayer;
|
|
87
86
|
/**
|
|
88
87
|
* Start the HTTP server with Express.
|
|
89
88
|
* Returns a Promise that resolves when the server is listening,
|
|
@@ -92,22 +91,52 @@ export declare class WebpiecesServerImpl implements WebpiecesServer {
|
|
|
92
91
|
* @param port - The port to listen on (default: 8080)
|
|
93
92
|
* @returns Promise that resolves when server is ready
|
|
94
93
|
*/
|
|
95
|
-
start(port?: number): Promise<void>;
|
|
94
|
+
start(port?: number, testMode?: boolean): Promise<void>;
|
|
96
95
|
/**
|
|
97
|
-
* Register
|
|
96
|
+
* Register Express routes - the SINGLE loop over routes.
|
|
97
|
+
* For each route: createHandler (sets up filter chain) → wrapExpress → registerHandler.
|
|
98
|
+
*
|
|
99
|
+
* @returns Number of routes registered
|
|
98
100
|
*/
|
|
99
101
|
private registerExpressRoutes;
|
|
102
|
+
registerHandler(httpMethod: string, path: string, expressHandler: ExpressRouteHandler): void;
|
|
100
103
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
104
|
+
* Stop the HTTP server.
|
|
105
|
+
* Returns a Promise that resolves when the server is stopped,
|
|
106
|
+
* or rejects if there's an error stopping the server.
|
|
103
107
|
*
|
|
104
|
-
* @
|
|
105
|
-
* @param routeWithMeta - The route handler paired with its definition
|
|
106
|
-
* @param filtersWithMeta - All filters with their definitions
|
|
108
|
+
* @returns Promise that resolves when server is stopped
|
|
107
109
|
*/
|
|
108
|
-
|
|
110
|
+
stop(): Promise<void>;
|
|
109
111
|
/**
|
|
110
|
-
*
|
|
112
|
+
* Get the application DI container.
|
|
113
|
+
*
|
|
114
|
+
* Useful for testing to verify state or access services directly.
|
|
115
|
+
*
|
|
116
|
+
* @returns The application Container
|
|
117
|
+
*/
|
|
118
|
+
getContainer(): Container;
|
|
119
|
+
/**
|
|
120
|
+
* Create an API client proxy for testing.
|
|
121
|
+
*
|
|
122
|
+
* This creates a client that routes calls through the full filter chain
|
|
123
|
+
* and controller, but WITHOUT any HTTP overhead. Perfect for testing!
|
|
124
|
+
*
|
|
125
|
+
* The client uses the ApiPrototype class to discover routes via decorators,
|
|
126
|
+
* then creates pre-configured invoker functions for each API method.
|
|
127
|
+
*
|
|
128
|
+
* IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
|
|
129
|
+
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
130
|
+
* so subsequent calls reuse the same filter chain (efficient!).
|
|
131
|
+
*
|
|
132
|
+
* @param apiPrototype - The API prototype class with routing decorators (can be abstract)
|
|
133
|
+
* @returns A proxy that implements the API interface
|
|
134
|
+
*
|
|
135
|
+
* Example:
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const saveApi = server.createApiClient<SaveApi>(SaveApiPrototype);
|
|
138
|
+
* const response = await saveApi.save(request);
|
|
139
|
+
* ```
|
|
111
140
|
*/
|
|
112
|
-
|
|
141
|
+
createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
|
|
113
142
|
}
|