@webpieces/http-routing 0.3.279 → 0.3.280
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 +5 -4
- package/src/InProcessApiClientFactory.d.ts +34 -0
- package/src/InProcessApiClientFactory.js +78 -0
- package/src/InProcessApiClientFactory.js.map +1 -0
- package/src/RouteBuilderImpl.d.ts +2 -8
- package/src/RouteBuilderImpl.js +12 -11
- package/src/RouteBuilderImpl.js.map +1 -1
- package/src/WebAppMeta.d.ts +15 -24
- package/src/WebAppMeta.js +14 -6
- package/src/WebAppMeta.js.map +1 -1
- package/src/WebpiecesRouter.d.ts +92 -0
- package/src/WebpiecesRouter.js +144 -0
- package/src/WebpiecesRouter.js.map +1 -0
- package/src/index.d.ts +5 -2
- package/src/index.js +16 -5
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-routing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.280",
|
|
4
4
|
"description": "Decorator-based routing with auto-wiring for WebPieces",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@webpieces/core-
|
|
26
|
-
"@webpieces/
|
|
24
|
+
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
|
+
"@webpieces/core-context": "0.3.280",
|
|
26
|
+
"@webpieces/core-util": "0.3.280",
|
|
27
|
+
"@webpieces/http-filters": "0.3.280",
|
|
27
28
|
"inversify": "7.10.4",
|
|
28
29
|
"minimatch": "10.0.1"
|
|
29
30
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RouteBuilderImpl } from './RouteBuilderImpl';
|
|
2
|
+
/**
|
|
3
|
+
* InProcessApiClientFactory - Creates API client proxies that invoke routes
|
|
4
|
+
* in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.
|
|
5
|
+
*
|
|
6
|
+
* This is the PRIMARY in-process/test builder. It lives in the node-only http-routing
|
|
7
|
+
* package (no express dependency) so both the node-only WebpiecesRouter and the express
|
|
8
|
+
* adapter (WebpiecesRouteCreator) share one code path.
|
|
9
|
+
*
|
|
10
|
+
* The client uses the ApiPrototype class to discover routes via decorators,
|
|
11
|
+
* then creates pre-configured invoker functions for each API method.
|
|
12
|
+
*
|
|
13
|
+
* IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
|
|
14
|
+
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
15
|
+
* so subsequent calls reuse the same filter chain (efficient!).
|
|
16
|
+
*/
|
|
17
|
+
export declare class InProcessApiClientFactory {
|
|
18
|
+
private routeBuilder;
|
|
19
|
+
constructor(routeBuilder: RouteBuilderImpl);
|
|
20
|
+
/**
|
|
21
|
+
* Create an API client proxy for testing.
|
|
22
|
+
*
|
|
23
|
+
* @param apiPrototype - The API prototype class with routing decorators (can be abstract)
|
|
24
|
+
* @returns A proxy that implements the API interface
|
|
25
|
+
*
|
|
26
|
+
* Example:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const saveApi = factory.createApiClient<SaveApi>(SaveApi);
|
|
29
|
+
* const response = await saveApi.save(request);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
|
|
33
|
+
private runMethod;
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InProcessApiClientFactory = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
const http_filters_1 = require("@webpieces/http-filters");
|
|
6
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
7
|
+
/**
|
|
8
|
+
* InProcessApiClientFactory - Creates API client proxies that invoke routes
|
|
9
|
+
* in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.
|
|
10
|
+
*
|
|
11
|
+
* This is the PRIMARY in-process/test builder. It lives in the node-only http-routing
|
|
12
|
+
* package (no express dependency) so both the node-only WebpiecesRouter and the express
|
|
13
|
+
* adapter (WebpiecesRouteCreator) share one code path.
|
|
14
|
+
*
|
|
15
|
+
* The client uses the ApiPrototype class to discover routes via decorators,
|
|
16
|
+
* then creates pre-configured invoker functions for each API method.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT: This loops over the API methods (from decorators), NOT all routes.
|
|
19
|
+
* For each API method, it sets up the filter chain ONCE during proxy creation,
|
|
20
|
+
* so subsequent calls reuse the same filter chain (efficient!).
|
|
21
|
+
*/
|
|
22
|
+
class InProcessApiClientFactory {
|
|
23
|
+
routeBuilder;
|
|
24
|
+
constructor(routeBuilder) {
|
|
25
|
+
this.routeBuilder = routeBuilder;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an API client proxy for testing.
|
|
29
|
+
*
|
|
30
|
+
* @param apiPrototype - The API prototype class with routing decorators (can be abstract)
|
|
31
|
+
* @returns A proxy that implements the API interface
|
|
32
|
+
*
|
|
33
|
+
* Example:
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const saveApi = factory.createApiClient<SaveApi>(SaveApi);
|
|
36
|
+
* const response = await saveApi.save(request);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
// webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args
|
|
40
|
+
createApiClient(apiPrototype) {
|
|
41
|
+
// Get endpoints from the API prototype using @ApiPath/@Endpoint decorators
|
|
42
|
+
const basePath = (0, core_util_1.getApiPath)(apiPrototype) || '';
|
|
43
|
+
const endpoints = (0, core_util_1.getEndpoints)(apiPrototype) || {};
|
|
44
|
+
// Create proxy object
|
|
45
|
+
// webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes
|
|
46
|
+
const proxy = {};
|
|
47
|
+
// Loop over API endpoints and create proxy functions
|
|
48
|
+
for (const [methodName, endpointPath] of Object.entries(endpoints)) {
|
|
49
|
+
const httpMethod = 'POST';
|
|
50
|
+
const path = basePath + endpointPath;
|
|
51
|
+
const authMeta = (0, core_util_1.getAuthMeta)(apiPrototype, methodName);
|
|
52
|
+
const routeMeta = new core_util_1.RouteMetadata(httpMethod, path, methodName, apiPrototype.name, authMeta);
|
|
53
|
+
// Create invoker service ONCE (sets up filter chain once, not on every call!)
|
|
54
|
+
const service = this.routeBuilder.createRouteInvoker(httpMethod, path);
|
|
55
|
+
// Proxy method creates MethodMeta and calls the pre-configured service
|
|
56
|
+
// webpieces-disable no-any-unknown -- request/response DTO types are erased at proxy level
|
|
57
|
+
proxy[methodName] = async (requestDto) => {
|
|
58
|
+
// Auto-activate a RequestContext if the test did not wrap the call itself
|
|
59
|
+
if (!core_context_1.RequestContext.isActive()) {
|
|
60
|
+
return core_context_1.RequestContext.run(async () => {
|
|
61
|
+
return await this.runMethod(routeMeta, requestDto, service);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return await this.runMethod(routeMeta, requestDto, service);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return proxy;
|
|
68
|
+
}
|
|
69
|
+
// webpieces-disable no-any-unknown -- DTO types are erased at the routing layer
|
|
70
|
+
async runMethod(routeMeta, requestDto, service) {
|
|
71
|
+
// Create MethodMeta without headers (in-process mode - no HTTP involved)
|
|
72
|
+
const meta = new http_filters_1.MethodMeta(routeMeta, undefined, requestDto);
|
|
73
|
+
const responseWrapper = await service.invoke(meta);
|
|
74
|
+
return responseWrapper.response;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.InProcessApiClientFactory = InProcessApiClientFactory;
|
|
78
|
+
//# sourceMappingURL=InProcessApiClientFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InProcessApiClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/InProcessApiClientFactory.ts"],"names":[],"mappings":";;;AAAA,oDAK8B;AAC9B,0DAA0E;AAC1E,0DAAyD;AAGzD;;;;;;;;;;;;;;GAcG;AACH,MAAa,yBAAyB;IACd;IAApB,YAAoB,YAA8B;QAA9B,iBAAY,GAAZ,YAAY,CAAkB;IAAG,CAAC;IAEtD;;;;;;;;;;;OAWG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,IAAA,sBAAU,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAEnD,sBAAsB;QACtB,kFAAkF;QAClF,MAAM,KAAK,GAA4B,EAAE,CAAC;QAE1C,qDAAqD;QACrD,KAAK,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,MAAM,UAAU,GAAG,MAAM,CAAC;YAC1B,MAAM,IAAI,GAAG,QAAQ,GAAG,YAAY,CAAC;YAErC,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACvD,MAAM,SAAS,GAAG,IAAI,yBAAa,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAE/F,8EAA8E;YAC9E,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEvE,uEAAuE;YACvE,2FAA2F;YAC3F,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,EAAE,UAAmB,EAAoB,EAAE;gBAChE,0EAA0E;gBAC1E,IAAI,CAAC,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;oBAC7B,OAAO,6BAAc,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;wBACjC,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;oBAChE,CAAC,CAAC,CAAC;gBACP,CAAC;gBACD,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;YAChE,CAAC,CAAC;QACN,CAAC;QAED,OAAO,KAAU,CAAC;IACtB,CAAC;IAED,gFAAgF;IACxE,KAAK,CAAC,SAAS,CAAC,SAAwB,EAAE,UAAmB,EAAE,OAAiD;QACpH,yEAAyE;QACzE,MAAM,IAAI,GAAG,IAAI,yBAAU,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QAC9D,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;CACJ;AA3DD,8DA2DC","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpoints,\n RouteMetadata,\n} from '@webpieces/core-util';\nimport { MethodMeta, Service, WpResponse } from '@webpieces/http-filters';\nimport { RequestContext } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\n\n/**\n * InProcessApiClientFactory - Creates API client proxies that invoke routes\n * in-process (api-tier filter chain + controller) WITHOUT any HTTP/express overhead.\n *\n * This is the PRIMARY in-process/test builder. It lives in the node-only http-routing\n * package (no express dependency) so both the node-only WebpiecesRouter and the express\n * adapter (WebpiecesRouteCreator) share one code path.\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 */\nexport class InProcessApiClientFactory {\n constructor(private routeBuilder: RouteBuilderImpl) {}\n\n /**\n * Create an API client proxy for testing.\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 = factory.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 // Get endpoints from the API prototype using @ApiPath/@Endpoint decorators\n const basePath = getApiPath(apiPrototype) || '';\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // Create proxy object\n // webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes\n const proxy: Record<string, unknown> = {};\n\n // Loop over API endpoints and create proxy functions\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const httpMethod = 'POST';\n const path = basePath + endpointPath;\n\n const authMeta = getAuthMeta(apiPrototype, methodName);\n const routeMeta = new RouteMetadata(httpMethod, path, methodName, apiPrototype.name, authMeta);\n\n // Create invoker service ONCE (sets up filter chain once, not on every call!)\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // Proxy method creates MethodMeta and calls the pre-configured service\n // webpieces-disable no-any-unknown -- request/response DTO types are erased at proxy level\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Auto-activate a RequestContext if the test did not wrap the call itself\n if (!RequestContext.isActive()) {\n return RequestContext.run(async () => {\n return await this.runMethod(routeMeta, requestDto, service);\n });\n }\n return await this.runMethod(routeMeta, requestDto, service);\n };\n }\n\n return proxy as T;\n }\n\n // webpieces-disable no-any-unknown -- DTO types are erased at the routing layer\n private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>): Promise<unknown> {\n // Create MethodMeta without headers (in-process mode - no HTTP involved)\n const meta = new MethodMeta(routeMeta, undefined, requestDto);\n const responseWrapper = await service.invoke(meta);\n return responseWrapper.response;\n }\n}\n"]}
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { Container } from 'inversify';
|
|
2
|
-
import { Request, Response, NextFunction } from 'express';
|
|
3
2
|
import { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';
|
|
4
3
|
import { RouteHandler } from './RouteHandler';
|
|
5
4
|
import { MethodMeta } from '@webpieces/http-filters';
|
|
6
5
|
import { RouteMetadata } from '@webpieces/core-util';
|
|
7
6
|
import { WpResponse, Service } from '@webpieces/http-filters';
|
|
8
7
|
import { HttpFilter } from './FilterMatcher';
|
|
9
|
-
/**
|
|
10
|
-
* Express route handler function type.
|
|
11
|
-
* Used by wrapExpress to create handlers that Express can call.
|
|
12
|
-
*/
|
|
13
|
-
export type ExpressRouteHandler = (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
14
8
|
/**
|
|
15
9
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
16
10
|
* Stores both the DI-resolved filter and the metadata needed for matching.
|
|
@@ -54,7 +48,7 @@ export declare class RouteHandlerWithMeta {
|
|
|
54
48
|
* - Make the code easier to understand
|
|
55
49
|
* - Enable better IDE navigation (Cmd+Click on addRoute works!)
|
|
56
50
|
*
|
|
57
|
-
* DI Pattern: This class is registered in webpiecesContainer via @
|
|
51
|
+
* DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()
|
|
58
52
|
* but needs appContainer to resolve filters/controllers. The container is set via
|
|
59
53
|
* setContainer() after appContainer is created (late binding pattern).
|
|
60
54
|
*/
|
|
@@ -142,7 +136,7 @@ export declare class RouteBuilderImpl implements RouteBuilder {
|
|
|
142
136
|
* @param routeWithMeta - Route handler with metadata
|
|
143
137
|
* @returns The service for this route
|
|
144
138
|
*/
|
|
145
|
-
createRouteHandler(routeWithMeta: RouteHandlerWithMeta): Service<MethodMeta, WpResponse<unknown>>;
|
|
139
|
+
createRouteHandler(routeWithMeta: RouteHandlerWithMeta, includeExpressTier?: boolean): Service<MethodMeta, WpResponse<unknown>>;
|
|
146
140
|
/**
|
|
147
141
|
* Create an invoker function for a route (for testing via createApiClient).
|
|
148
142
|
* Uses routeMap for O(1) lookup, sets up the filter chain ONCE,
|
package/src/RouteBuilderImpl.js
CHANGED
|
@@ -4,11 +4,10 @@ exports.RouteBuilderImpl = exports.RouteHandlerWithMeta = exports.RouteHandlerIm
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const inversify_1 = require("inversify");
|
|
6
6
|
const core_context_1 = require("@webpieces/core-context");
|
|
7
|
-
const core_util_1 = require("@webpieces/core-util");
|
|
8
7
|
const http_filters_1 = require("@webpieces/http-filters");
|
|
9
8
|
const FilterMatcher_1 = require("./FilterMatcher");
|
|
10
|
-
const
|
|
11
|
-
const log =
|
|
9
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
10
|
+
const log = core_util_1.LogManager.getLogger('RouteBuilder');
|
|
12
11
|
/**
|
|
13
12
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
14
13
|
* Stores both the DI-resolved filter and the metadata needed for matching.
|
|
@@ -69,7 +68,7 @@ exports.RouteHandlerWithMeta = RouteHandlerWithMeta;
|
|
|
69
68
|
* - Make the code easier to understand
|
|
70
69
|
* - Enable better IDE navigation (Cmd+Click on addRoute works!)
|
|
71
70
|
*
|
|
72
|
-
* DI Pattern: This class is registered in webpiecesContainer via @
|
|
71
|
+
* DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()
|
|
73
72
|
* but needs appContainer to resolve filters/controllers. The container is set via
|
|
74
73
|
* setContainer() after appContainer is created (late binding pattern).
|
|
75
74
|
*/
|
|
@@ -206,12 +205,14 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
206
205
|
* @param routeWithMeta - Route handler with metadata
|
|
207
206
|
* @returns The service for this route
|
|
208
207
|
*/
|
|
209
|
-
createRouteHandler(routeWithMeta) {
|
|
208
|
+
createRouteHandler(routeWithMeta, includeExpressTier = true) {
|
|
210
209
|
const route = routeWithMeta.definition;
|
|
211
210
|
const routeMeta = route.routeMeta;
|
|
212
211
|
log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);
|
|
213
|
-
// Get cached filter definitions
|
|
214
|
-
|
|
212
|
+
// Get cached filter definitions, then drop express-tier filters when composing an
|
|
213
|
+
// in-process (createApiClient) chain — those need the raw HTTP request (e.g. auth
|
|
214
|
+
// reading the Authorization header) and would wrongly reject a headerless in-process call.
|
|
215
|
+
const filterDefinitions = this.getFilterDefinitions().filter((def) => includeExpressTier || def.tier !== 'express');
|
|
215
216
|
// Find matching filters for this route
|
|
216
217
|
const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, filterDefinitions);
|
|
217
218
|
// Create service that wraps the controller execution
|
|
@@ -256,8 +257,9 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
256
257
|
if (!routeWithMeta) {
|
|
257
258
|
throw new Error(`Route not found: ${method} ${path}`);
|
|
258
259
|
}
|
|
259
|
-
// Setup filter chain ONCE (not on every invocation!)
|
|
260
|
-
|
|
260
|
+
// Setup filter chain ONCE (not on every invocation!).
|
|
261
|
+
// In-process client → api-tier filters only (skip express-tier like ServiceAuthFilter).
|
|
262
|
+
return this.createRouteHandler(routeWithMeta, false);
|
|
261
263
|
}
|
|
262
264
|
/**
|
|
263
265
|
* Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.
|
|
@@ -273,8 +275,7 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
273
275
|
};
|
|
274
276
|
exports.RouteBuilderImpl = RouteBuilderImpl;
|
|
275
277
|
exports.RouteBuilderImpl = RouteBuilderImpl = tslib_1.__decorate([
|
|
276
|
-
(0,
|
|
277
|
-
(0, core_context_1.provideSingleton)(),
|
|
278
|
+
(0, core_context_1.provideFrameworkSingleton)(),
|
|
278
279
|
(0, inversify_1.injectable)()
|
|
279
280
|
], RouteBuilderImpl);
|
|
280
281
|
//# sourceMappingURL=RouteBuilderImpl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":";;;;AAAA,yCAAkD;AAGlD,0DAA2D;AAG3D,oDAAqE;AACrE,0DAA8D;AAC9D,mDAA4D;AAC5D,oDAAkD;AAElD,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;AAYjD;;;GAGG;AACH,MAAa,cAAc;IAEZ;IACA;IAFX,YACW,MAAkB,EAClB,UAA4B;QAD5B,WAAM,GAAN,MAAM,CAAY;QAClB,eAAU,GAAV,UAAU,CAAkB;IACpC,CAAC;CACP;AALD,wCAKC;AAED;;;GAGG;AACH,MAAa,gBAAgB;IAEb;IACA;IAFZ,YACY,UAAmC,EACnC,MAAiE;QADjE,eAAU,GAAV,UAAU,CAAyB;QACnC,WAAM,GAAN,MAAM,CAA2D;IAC1E,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,IAAgB;QAC1B,8CAA8C;QAC9C,sEAAsE;QACtE,MAAM,MAAM,GAAY,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACjF,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAZD,4CAYC;AACD;;;;;;GAMG;AACH,MAAa,oBAAoB;IAElB;IACA;IAFX,YACW,uBAA8C,EAC9C,UAA2B;QAD3B,4BAAuB,GAAvB,uBAAuB,CAAuB;QAC9C,eAAU,GAAV,UAAU,CAAiB;IACnC,CAAC;CACP;AALD,oDAKC;AAED;;;;;;;;;;;;;;;GAeG;AAII,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACjB,MAAM,GAA2B,EAAE,CAAC;IACpC,cAAc,GAA0B,EAAE,CAAC;IAC3C,SAAS,CAAa;IAE9B;;;OAGG;IACK,QAAQ,GAAsC,IAAI,GAAG,EAAE,CAAC;IAEhE;;;OAGG;IACK,cAAc,CAAC,MAAc,EAAE,IAAY;QAC/C,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,SAAoB;QAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAsB;QAC3B,MAAM,aAAa,GAAG,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAEhC,iDAAiD;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAC3B,KAAK,CAAC,SAAS,CAAC,UAAU,EAC1B,KAAK,CAAC,SAAS,CAAC,IAAI,CACvB,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,0BAA0B,CAC9B,KAAsB;QAEtB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAElC,6EAA6E;QAC7E,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,CAA4B,CAAC;QAExF,4BAA4B;QAC5B,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;YAC/B,MAAM,cAAc,GAAI,KAAK,CAAC,eAAqC,CAAC,IAAI,IAAI,SAAS,CAAC;YACtF,MAAM,IAAI,KAAK,CACX,UAAU,SAAS,CAAC,UAAU,4BAA4B,cAAc,EAAE,CAC7E,CAAC;QACN,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAChC,UAAU,EACV,MAAmE,CACtE,CAAC;QAEF,uCAAuC;QACvC,OAAO,IAAI,oBAAoB,CAC3B,OAAgC,EAChC,KAAK,CACR,CAAC;IACN,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,SAA2B;QACjC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QAC1F,CAAC;QAED,4CAA4C;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAa,SAAS,CAAC,WAAW,CAAC,CAAC;QAErE,mCAAmC;QACnC,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACH,SAAS;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACZ,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,UAAU,CAAC,QAAQ,CAC1D,CAAC;IACN,CAAC;IAED;;OAEG;IACK,uBAAuB,CAAsB;IAErD;;OAEG;IACK,oBAAoB;QACxB,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9C,IAAI,CAAC,uBAAuB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACrD,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC;gBAC3B,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;gBACxB,OAAO,GAAG,CAAC;YACf,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,uBAAuB,CAAC;IACxC,CAAC;IAED;;;;;;;;;;OAUG;IACI,kBAAkB,CACrB,aAAmC;QAEnC,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC;QACvC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAElC,GAAG,CAAC,IAAI,CAAC,oCAAoC,SAAS,CAAC,UAAU,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QAEvF,gCAAgC;QAChC,MAAM,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAEtD,uCAAuC;QACvC,MAAM,eAAe,GAAG,6BAAa,CAAC,mBAAmB,CACrD,KAAK,CAAC,kBAAkB,EACxB,iBAAiB,CACpB,CAAC;QAEF,qDAAqD;QACrD,MAAM,iBAAiB,GAA6C;YAChE,MAAM,EAAE,KAAK,EAAE,IAAgB,EAAgC,EAAE;gBAC7D,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,uBAAuB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACzE,8EAA8E;gBAC9E,yEAAyE;gBACzE,+EAA+E;gBAC/E,OAAO,IAAI,yBAAU,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;YACxC,CAAC;SACJ,CAAC;QAEF,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,8FAA8F,CAAC,CAAC;QACpH,CAAC;QAED,mFAAmF;QACnF,yEAAyE;QACzE,0EAA0E;QAC1E,IAAI,OAAO,GAA6C,iBAAiB,CAAC;QAC1E,KAAK,IAAI,CAAC,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACnD,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACvD,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,MAAc,EAAE,IAAY;QAC3C,oDAAoD;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE7C,IAAI,CAAC,aAAa,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC;QAED,qDAAqD;QACrD,OAAO,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACH,YAAY,CAAC,MAAc,EAAE,IAAY;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC;IACxD,CAAC;CACJ,CAAA;AAvPY,4CAAgB;2BAAhB,gBAAgB;IAH5B,IAAA,0BAAc,GAAE;IAChB,IAAA,+BAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,gBAAgB,CAuP5B","sourcesContent":["import { Container, injectable } from 'inversify';\nimport { Request, Response, NextFunction } from 'express';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from '@webpieces/http-filters';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { WpResponse, Service } from '@webpieces/http-filters';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\n/**\n * Express route handler function type.\n * Used by wrapExpress to create handlers that Express can call.\n */\nexport type ExpressRouteHandler = (\n req: Request,\n res: Response,\n next: NextFunction,\n) => Promise<void>;\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n private method: (this: unknown, requestDto?: unknown) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.call(this.controller, meta.requestDto);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\n ) {}\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@DocumentDesign()\n@provideSingleton()\n@injectable()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(\n route.routeMeta.httpMethod,\n route.routeMeta.path\n );\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as { name?: string }).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n method as (this: unknown, requestDto?: unknown) => Promise<TResult>\n );\n\n // Return handler with route definition\n return new RouteHandlerWithMeta(\n handler as RouteHandler<unknown>,\n route,\n );\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // Get cached filter definitions\n const filterDefinitions = this.getFilterDefinitions();\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)\n // yields undefined; coerce to {} so the response is a non-null JSON body\n // (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\"No filters found for route. Check filter definitions as you must have at least ContextFilter\");\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!)\n return this.createRouteHandler(routeWithMeta);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, injectable } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from '@webpieces/http-filters';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { WpResponse, Service } from '@webpieces/http-filters';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n private method: (this: unknown, requestDto?: unknown) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.call(this.controller, meta.requestDto);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\n ) {}\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(\n route.routeMeta.httpMethod,\n route.routeMeta.path\n );\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as { name?: string }).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n method as (this: unknown, requestDto?: unknown) => Promise<TResult>\n );\n\n // Return handler with route definition\n return new RouteHandlerWithMeta(\n handler as RouteHandler<unknown>,\n route,\n );\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n includeExpressTier: boolean = true,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // Get cached filter definitions, then drop express-tier filters when composing an\n // in-process (createApiClient) chain — those need the raw HTTP request (e.g. auth\n // reading the Authorization header) and would wrongly reject a headerless in-process call.\n const filterDefinitions = this.getFilterDefinitions().filter(\n (def: FilterDefinition) => includeExpressTier || def.tier !== 'express',\n );\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)\n // yields undefined; coerce to {} so the response is a non-null JSON body\n // (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\"No filters found for route. Check filter definitions as you must have at least ContextFilter\");\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!).\n // In-process client → api-tier filters only (skip express-tier like ServiceAuthFilter).\n return this.createRouteHandler(routeWithMeta, false);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
|
package/src/WebAppMeta.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ContainerModule } from 'inversify';
|
|
2
1
|
import { RouteMetadata } from "@webpieces/core-util";
|
|
3
2
|
/**
|
|
4
3
|
* Represents a route configuration that can be registered with the router.
|
|
@@ -30,6 +29,15 @@ export declare class RouteDefinition {
|
|
|
30
29
|
controllerFilepath?: string | undefined;
|
|
31
30
|
constructor(routeMeta: RouteMetadata, controllerClass: any, controllerFilepath?: string | undefined);
|
|
32
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A filter's execution tier:
|
|
34
|
+
* - 'api' : runs for BOTH real HTTP requests AND the in-process createApiClient
|
|
35
|
+
* (business/cross-cutting filters — logging, recording, context seeding).
|
|
36
|
+
* - 'express' : runs ONLY for real HTTP requests mounted on express (transport-boundary
|
|
37
|
+
* filters that need the raw request — e.g. service auth reading the
|
|
38
|
+
* Authorization header). Skipped by the in-process client so tests don't 401.
|
|
39
|
+
*/
|
|
40
|
+
export type FilterTier = 'express' | 'api';
|
|
33
41
|
/**
|
|
34
42
|
* Definition of a filter with priority.
|
|
35
43
|
*
|
|
@@ -39,6 +47,9 @@ export declare class RouteDefinition {
|
|
|
39
47
|
* - '**' + '/UserController.ts' - Specific controller file
|
|
40
48
|
*
|
|
41
49
|
* If filepathPattern is not specified, the filter matches all controllers.
|
|
50
|
+
*
|
|
51
|
+
* tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over
|
|
52
|
+
* HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.
|
|
42
53
|
*/
|
|
43
54
|
export declare class FilterDefinition {
|
|
44
55
|
priority: number;
|
|
@@ -49,27 +60,7 @@ export declare class FilterDefinition {
|
|
|
49
60
|
* If not specified, defaults to matching all controllers.
|
|
50
61
|
*/
|
|
51
62
|
filepathPattern: string;
|
|
52
|
-
|
|
63
|
+
/** Execution tier — see {@link FilterTier}. Defaults to 'api'. */
|
|
64
|
+
tier: FilterTier;
|
|
65
|
+
constructor(priority: number, filterClass: any, filepathPattern: string, tier?: FilterTier);
|
|
53
66
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Main application metadata interface.
|
|
56
|
-
* Similar to Java WebPieces WebAppMeta.
|
|
57
|
-
*
|
|
58
|
-
* This is the entry point that WebpiecesServer calls to configure your application.
|
|
59
|
-
*/
|
|
60
|
-
export interface WebAppMeta {
|
|
61
|
-
/**
|
|
62
|
-
* Returns the list of Inversify container modules for dependency injection.
|
|
63
|
-
* Similar to getGuiceModules() in Java.
|
|
64
|
-
*/
|
|
65
|
-
getDIModules(): ContainerModule[];
|
|
66
|
-
/**
|
|
67
|
-
* Returns the list of route configurations.
|
|
68
|
-
* Similar to getRouteModules() in Java.
|
|
69
|
-
*/
|
|
70
|
-
getRoutes(): Routes[];
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* DI token for WebAppMeta injection.
|
|
74
|
-
*/
|
|
75
|
-
export declare const WEBAPP_META_TOKEN: unique symbol;
|
package/src/WebAppMeta.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.FilterDefinition = exports.RouteDefinition = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Definition of a single route.
|
|
6
6
|
*
|
|
@@ -27,26 +27,34 @@ exports.RouteDefinition = RouteDefinition;
|
|
|
27
27
|
* - '**' + '/UserController.ts' - Specific controller file
|
|
28
28
|
*
|
|
29
29
|
* If filepathPattern is not specified, the filter matches all controllers.
|
|
30
|
+
*
|
|
31
|
+
* tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over
|
|
32
|
+
* HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.
|
|
30
33
|
*/
|
|
31
34
|
class FilterDefinition {
|
|
32
35
|
priority;
|
|
36
|
+
// webpieces-disable no-any-unknown -- an arbitrary DI filter class used as a container token
|
|
33
37
|
filterClass;
|
|
38
|
+
// webpieces-disable no-any-unknown -- the resolved filter instance, of arbitrary shape
|
|
34
39
|
filter; // Filter instance (set by RouteBuilder when resolving from DI)
|
|
35
40
|
/**
|
|
36
41
|
* Glob pattern to match controller file paths.
|
|
37
42
|
* If not specified, defaults to matching all controllers.
|
|
38
43
|
*/
|
|
39
44
|
filepathPattern;
|
|
40
|
-
|
|
45
|
+
/** Execution tier — see {@link FilterTier}. Defaults to 'api'. */
|
|
46
|
+
tier;
|
|
47
|
+
// webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token
|
|
48
|
+
constructor(priority, filterClass, filepathPattern, tier = 'api') {
|
|
41
49
|
this.priority = priority;
|
|
42
50
|
this.filterClass = filterClass;
|
|
43
51
|
this.filepathPattern = filepathPattern;
|
|
52
|
+
this.tier = tier;
|
|
44
53
|
this.filter = undefined; // Set later by RouteBuilder
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
exports.FilterDefinition = FilterDefinition;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
exports.WEBAPP_META_TOKEN = Symbol.for('WebAppMeta');
|
|
57
|
+
// The old WebAppMeta interface + WEBAPP_META_TOKEN were removed with the WebpiecesServer/
|
|
58
|
+
// WebpiecesFactory flip. Apps now configure routes/filters imperatively on WebpiecesRouter
|
|
59
|
+
// (see WebpiecesRouter.addRoutes/addFilter) instead of implementing WebAppMeta.getDIModules/getRoutes.
|
|
52
60
|
//# sourceMappingURL=WebAppMeta.js.map
|
package/src/WebAppMeta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebAppMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebAppMeta.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"WebAppMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebAppMeta.ts"],"names":[],"mappings":";;;AAsBA;;;;;GAKG;AACH,MAAa,eAAe;IAEb;IACA;IACA;IAHX,YACW,SAAwB,EACxB,eAAoB,EACpB,kBAA2B;QAF3B,cAAS,GAAT,SAAS,CAAe;QACxB,oBAAe,GAAf,eAAe,CAAK;QACpB,uBAAkB,GAAlB,kBAAkB,CAAS;IACnC,CAAC;CACP;AAND,0CAMC;AAYD;;;;;;;;;;;;GAYG;AACH,MAAa,gBAAgB;IACzB,QAAQ,CAAS;IACjB,6FAA6F;IAC7F,WAAW,CAAM;IACjB,uFAAuF;IACvF,MAAM,CAAO,CAAC,+DAA+D;IAE7E;;;OAGG;IACH,eAAe,CAAS;IAExB,kEAAkE;IAClE,IAAI,CAAa;IAEjB,8FAA8F;IAC9F,YAAY,QAAgB,EAAE,WAAgB,EAAE,eAAuB,EAAE,OAAmB,KAAK;QAC7F,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,4BAA4B;IACzD,CAAC;CACJ;AAxBD,4CAwBC;AAGD,0FAA0F;AAC1F,2FAA2F;AAC3F,uGAAuG","sourcesContent":["import {RouteMetadata} from \"@webpieces/core-util\";\n\n/**\n * Represents a route configuration that can be registered with the router.\n * Similar to Java WebPieces Routes interface.\n */\nexport interface Routes {\n /**\n * Configure routes using the provided RouteBuilder.\n */\n configure(routeBuilder: RouteBuilder): void;\n}\n\n/**\n * Builder for registering routes.\n * Will be implemented in http-server package.\n */\nexport interface RouteBuilder {\n addRoute(route: RouteDefinition): void;\n addFilter(filter: FilterDefinition): void;\n}\n\n/**\n * Definition of a single route.\n *\n * Generic type parameter TResult represents the return type of the route handler.\n * This provides type safety for the entire request/response cycle.\n */\nexport class RouteDefinition {\n constructor(\n public routeMeta: RouteMetadata,\n public controllerClass: any,\n public controllerFilepath?: string,\n ) {}\n}\n\n/**\n * A filter's execution tier:\n * - 'api' : runs for BOTH real HTTP requests AND the in-process createApiClient\n * (business/cross-cutting filters — logging, recording, context seeding).\n * - 'express' : runs ONLY for real HTTP requests mounted on express (transport-boundary\n * filters that need the raw request — e.g. service auth reading the\n * Authorization header). Skipped by the in-process client so tests don't 401.\n */\nexport type FilterTier = 'express' | 'api';\n\n/**\n * Definition of a filter with priority.\n *\n * Use filepathPattern to scope filters to specific controllers:\n * - 'src/controllers/admin/**' + '/*.ts' - All admin controllers\n * - '**' + '/admin/**' - Any file in admin directories\n * - '**' + '/UserController.ts' - Specific controller file\n *\n * If filepathPattern is not specified, the filter matches all controllers.\n *\n * tier defaults to 'api' so a filter runs in-process (via createApiClient) as well as over\n * HTTP. Pass 'express' for transport-boundary filters that must be skipped in-process.\n */\nexport class FilterDefinition {\n priority: number;\n // webpieces-disable no-any-unknown -- an arbitrary DI filter class used as a container token\n filterClass: any;\n // webpieces-disable no-any-unknown -- the resolved filter instance, of arbitrary shape\n filter?: any; // Filter instance (set by RouteBuilder when resolving from DI)\n\n /**\n * Glob pattern to match controller file paths.\n * If not specified, defaults to matching all controllers.\n */\n filepathPattern: string;\n\n /** Execution tier — see {@link FilterTier}. Defaults to 'api'. */\n tier: FilterTier;\n\n // webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token\n constructor(priority: number, filterClass: any, filepathPattern: string, tier: FilterTier = 'api') {\n this.priority = priority;\n this.filterClass = filterClass;\n this.filepathPattern = filepathPattern;\n this.tier = tier;\n this.filter = undefined; // Set later by RouteBuilder\n }\n}\n\n\n// The old WebAppMeta interface + WEBAPP_META_TOKEN were removed with the WebpiecesServer/\n// WebpiecesFactory flip. Apps now configure routes/filters imperatively on WebpiecesRouter\n// (see WebpiecesRouter.addRoutes/addFilter) instead of implementing WebAppMeta.getDIModules/getRoutes.\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Container, ContainerModule } from 'inversify';
|
|
2
|
+
import { RouteBuilderImpl } from './RouteBuilderImpl';
|
|
3
|
+
import { ClassType } from './ApiRoutingFactory';
|
|
4
|
+
import { FilterDefinition } from './WebAppMeta';
|
|
5
|
+
import { WebpiecesConfig } from './WebpiecesConfig';
|
|
6
|
+
/**
|
|
7
|
+
* Options for {@link WebpiecesRouterFactory.create}.
|
|
8
|
+
*
|
|
9
|
+
* appBindings - REQUIRED DI ContainerModules to load (framework + app), e.g.
|
|
10
|
+
* [WebpiecesModule, CompanyHeadersModule, AppModule]. Loaded after the
|
|
11
|
+
* @provideSingleton auto-scan so they can add/override bindings.
|
|
12
|
+
* appOverrides - A single ContainerModule loaded LAST so tests can rebind real
|
|
13
|
+
* controllers/clients to mocks (see @webpieces/core-mock createMock()).
|
|
14
|
+
*/
|
|
15
|
+
export interface WebpiecesRouterOptions {
|
|
16
|
+
appBindings: ContainerModule[];
|
|
17
|
+
appOverrides?: ContainerModule;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
21
|
+
* chain + an in-process API client. It has NO express dependency, so it runs anywhere
|
|
22
|
+
* node runs and is fully testable with zero HTTP.
|
|
23
|
+
*
|
|
24
|
+
* DI-resolved from the platform container (like the old WebpiecesServerImpl):
|
|
25
|
+
* `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in
|
|
26
|
+
* initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.
|
|
27
|
+
*
|
|
28
|
+
* Two-container pattern (mirrors Java WebPieces):
|
|
29
|
+
* - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)
|
|
30
|
+
* - appContainer : your controllers/filters/modules (a child of the framework one)
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const router = await WebpiecesRouterFactory.create(new WebpiecesConfig(), {
|
|
35
|
+
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
36
|
+
* });
|
|
37
|
+
* router.addRoutes(SaveApi, SaveController);
|
|
38
|
+
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // api tier
|
|
39
|
+
* router.addFilter(new FilterDefinition(1950, ServiceAuthFilter, '*', 'express')); // express tier
|
|
40
|
+
*
|
|
41
|
+
* // test (no express): runs the api-tier filter chain -> controller
|
|
42
|
+
* const api = router.createApiClient(SaveApi);
|
|
43
|
+
* await api.save(new SaveRequest(...));
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* To serve real HTTP, hand this router to the express adapter in @webpieces/http-server
|
|
47
|
+
* (bindExpress / bindAndStartExpress) — express lifecycle lives THERE, never here.
|
|
48
|
+
*
|
|
49
|
+
* @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.
|
|
50
|
+
*/
|
|
51
|
+
export declare class WebpiecesRouter {
|
|
52
|
+
private readonly routeBuilder;
|
|
53
|
+
private webpiecesContainer;
|
|
54
|
+
private appContainer;
|
|
55
|
+
constructor(routeBuilder: RouteBuilderImpl);
|
|
56
|
+
/**
|
|
57
|
+
* Build the app container (child of the framework container), load the @provideSingleton
|
|
58
|
+
* auto-scan + appBindings + appOverrides, and point the RouteBuilder at it. Called once by
|
|
59
|
+
* the factory after this router is resolved from the framework container.
|
|
60
|
+
*/
|
|
61
|
+
initialize(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void>;
|
|
62
|
+
private loadDIModules;
|
|
63
|
+
/**
|
|
64
|
+
* Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
|
|
65
|
+
* The controller is resolved from the container at request time.
|
|
66
|
+
*/
|
|
67
|
+
addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
|
|
68
|
+
/**
|
|
69
|
+
* Register a filter. Defaults to the 'api' tier (runs in-process AND over HTTP);
|
|
70
|
+
* pass a 'express'-tier FilterDefinition for transport-boundary filters.
|
|
71
|
+
*/
|
|
72
|
+
addFilter(filter: FilterDefinition): this;
|
|
73
|
+
/**
|
|
74
|
+
* Create an in-process API client that runs the api-tier filter chain + controller
|
|
75
|
+
* with NO express/HTTP. The primary path for tests and node-only callers.
|
|
76
|
+
*/
|
|
77
|
+
createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
|
|
78
|
+
/** The application DI container (child of the framework container). */
|
|
79
|
+
getContainer(): Container;
|
|
80
|
+
/** The framework container (holds the config token + @DocumentDesign design roots). */
|
|
81
|
+
getFrameworkContainer(): Container;
|
|
82
|
+
/** The route table + filter chain. Used by the express adapter to mount HTTP routes. */
|
|
83
|
+
getRouteBuilder(): RouteBuilderImpl;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors
|
|
87
|
+
* WebpiecesServerFactory.create), RESOLVES the router from DI, then initializes its app child
|
|
88
|
+
* container with the @provideSingleton auto-scan + appBindings + optional test overrides.
|
|
89
|
+
*/
|
|
90
|
+
export declare class WebpiecesRouterFactory {
|
|
91
|
+
static create(config: WebpiecesConfig, options: WebpiecesRouterOptions): Promise<WebpiecesRouter>;
|
|
92
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebpiecesRouterFactory = exports.WebpiecesRouter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const inversify_1 = require("inversify");
|
|
6
|
+
const binding_decorators_1 = require("@inversifyjs/binding-decorators");
|
|
7
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
8
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
9
|
+
const RouteBuilderImpl_1 = require("./RouteBuilderImpl");
|
|
10
|
+
const ApiRoutingFactory_1 = require("./ApiRoutingFactory");
|
|
11
|
+
const WebpiecesConfig_1 = require("./WebpiecesConfig");
|
|
12
|
+
const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
13
|
+
/**
|
|
14
|
+
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
15
|
+
* chain + an in-process API client. It has NO express dependency, so it runs anywhere
|
|
16
|
+
* node runs and is fully testable with zero HTTP.
|
|
17
|
+
*
|
|
18
|
+
* DI-resolved from the platform container (like the old WebpiecesServerImpl):
|
|
19
|
+
* `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in
|
|
20
|
+
* initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.
|
|
21
|
+
*
|
|
22
|
+
* Two-container pattern (mirrors Java WebPieces):
|
|
23
|
+
* - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)
|
|
24
|
+
* - appContainer : your controllers/filters/modules (a child of the framework one)
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const router = await WebpiecesRouterFactory.create(new WebpiecesConfig(), {
|
|
29
|
+
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
30
|
+
* });
|
|
31
|
+
* router.addRoutes(SaveApi, SaveController);
|
|
32
|
+
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // api tier
|
|
33
|
+
* router.addFilter(new FilterDefinition(1950, ServiceAuthFilter, '*', 'express')); // express tier
|
|
34
|
+
*
|
|
35
|
+
* // test (no express): runs the api-tier filter chain -> controller
|
|
36
|
+
* const api = router.createApiClient(SaveApi);
|
|
37
|
+
* await api.save(new SaveRequest(...));
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* To serve real HTTP, hand this router to the express adapter in @webpieces/http-server
|
|
41
|
+
* (bindExpress / bindAndStartExpress) — express lifecycle lives THERE, never here.
|
|
42
|
+
*
|
|
43
|
+
* @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.
|
|
44
|
+
*/
|
|
45
|
+
let WebpiecesRouter = class WebpiecesRouter {
|
|
46
|
+
routeBuilder;
|
|
47
|
+
webpiecesContainer;
|
|
48
|
+
appContainer;
|
|
49
|
+
constructor(routeBuilder) {
|
|
50
|
+
this.routeBuilder = routeBuilder;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build the app container (child of the framework container), load the @provideSingleton
|
|
54
|
+
* auto-scan + appBindings + appOverrides, and point the RouteBuilder at it. Called once by
|
|
55
|
+
* the factory after this router is resolved from the framework container.
|
|
56
|
+
*/
|
|
57
|
+
async initialize(webpiecesContainer, options) {
|
|
58
|
+
this.webpiecesContainer = webpiecesContainer;
|
|
59
|
+
// App container is a child so app bindings see framework bindings while staying separate.
|
|
60
|
+
this.appContainer = new inversify_1.Container({ parent: webpiecesContainer });
|
|
61
|
+
this.routeBuilder.setContainer(this.appContainer);
|
|
62
|
+
await this.loadDIModules(options);
|
|
63
|
+
}
|
|
64
|
+
async loadDIModules(options) {
|
|
65
|
+
// Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's
|
|
66
|
+
// own @provideSingleton classes (binding-decorators global). A client's
|
|
67
|
+
// buildProviderModule() only ever contains the client's classes — never framework internals.
|
|
68
|
+
await this.appContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
69
|
+
await this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
70
|
+
// Load all modules into application container
|
|
71
|
+
// (webpiecesContainer is currently empty, reserved for future framework bindings)
|
|
72
|
+
for (const module of options.appBindings) {
|
|
73
|
+
await this.appContainer.load(module);
|
|
74
|
+
}
|
|
75
|
+
// Load appOverrides LAST so they can override existing bindings
|
|
76
|
+
if (options.appOverrides) {
|
|
77
|
+
await this.appContainer.load(options.appOverrides);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
|
|
82
|
+
* The controller is resolved from the container at request time.
|
|
83
|
+
*/
|
|
84
|
+
addRoutes(api, controller) {
|
|
85
|
+
new ApiRoutingFactory_1.ApiRoutingFactory(api, controller).configure(this.routeBuilder);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Register a filter. Defaults to the 'api' tier (runs in-process AND over HTTP);
|
|
90
|
+
* pass a 'express'-tier FilterDefinition for transport-boundary filters.
|
|
91
|
+
*/
|
|
92
|
+
addFilter(filter) {
|
|
93
|
+
this.routeBuilder.addFilter(filter);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create an in-process API client that runs the api-tier filter chain + controller
|
|
98
|
+
* with NO express/HTTP. The primary path for tests and node-only callers.
|
|
99
|
+
*/
|
|
100
|
+
// webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args
|
|
101
|
+
createApiClient(apiPrototype) {
|
|
102
|
+
return new InProcessApiClientFactory_1.InProcessApiClientFactory(this.routeBuilder).createApiClient(apiPrototype);
|
|
103
|
+
}
|
|
104
|
+
/** The application DI container (child of the framework container). */
|
|
105
|
+
getContainer() {
|
|
106
|
+
return this.appContainer;
|
|
107
|
+
}
|
|
108
|
+
/** The framework container (holds the config token + @DocumentDesign design roots). */
|
|
109
|
+
getFrameworkContainer() {
|
|
110
|
+
return this.webpiecesContainer;
|
|
111
|
+
}
|
|
112
|
+
/** The route table + filter chain. Used by the express adapter to mount HTTP routes. */
|
|
113
|
+
getRouteBuilder() {
|
|
114
|
+
return this.routeBuilder;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
exports.WebpiecesRouter = WebpiecesRouter;
|
|
118
|
+
exports.WebpiecesRouter = WebpiecesRouter = tslib_1.__decorate([
|
|
119
|
+
(0, core_util_1.DocumentDesign)(),
|
|
120
|
+
(0, core_context_1.provideFrameworkSingleton)(),
|
|
121
|
+
tslib_1.__param(0, (0, inversify_1.inject)(RouteBuilderImpl_1.RouteBuilderImpl)),
|
|
122
|
+
tslib_1.__metadata("design:paramtypes", [RouteBuilderImpl_1.RouteBuilderImpl])
|
|
123
|
+
], WebpiecesRouter);
|
|
124
|
+
/**
|
|
125
|
+
* Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors
|
|
126
|
+
* WebpiecesServerFactory.create), RESOLVES the router from DI, then initializes its app child
|
|
127
|
+
* container with the @provideSingleton auto-scan + appBindings + optional test overrides.
|
|
128
|
+
*/
|
|
129
|
+
class WebpiecesRouterFactory {
|
|
130
|
+
static async create(config, options) {
|
|
131
|
+
// Platform (framework) container — build via buildFrameworkModule so framework
|
|
132
|
+
// singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,
|
|
133
|
+
// NOT the client's global one.
|
|
134
|
+
const webpiecesContainer = new inversify_1.Container();
|
|
135
|
+
webpiecesContainer.bind(WebpiecesConfig_1.WEBPIECES_CONFIG_TOKEN).toConstantValue(config);
|
|
136
|
+
await webpiecesContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
137
|
+
// Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.
|
|
138
|
+
const router = webpiecesContainer.get(WebpiecesRouter);
|
|
139
|
+
await router.initialize(webpiecesContainer, options);
|
|
140
|
+
return router;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.WebpiecesRouterFactory = WebpiecesRouterFactory;
|
|
144
|
+
//# sourceMappingURL=WebpiecesRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { provideFrameworkSingleton, buildFrameworkModule } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { InProcessApiClientFactory } from './InProcessApiClientFactory';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create}.\n *\n * appBindings - REQUIRED DI ContainerModules to load (framework + app), e.g.\n * [WebpiecesModule, CompanyHeadersModule, AppModule]. Loaded after the\n * @provideSingleton auto-scan so they can add/override bindings.\n * appOverrides - A single ContainerModule loaded LAST so tests can rebind real\n * controllers/clients to mocks (see @webpieces/core-mock createMock()).\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n}\n\n/**\n * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter\n * chain + an in-process API client. It has NO express dependency, so it runs anywhere\n * node runs and is fully testable with zero HTTP.\n *\n * DI-resolved from the platform container (like the old WebpiecesServerImpl):\n * `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in\n * initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.\n *\n * Two-container pattern (mirrors Java WebPieces):\n * - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)\n * - appContainer : your controllers/filters/modules (a child of the framework one)\n *\n * Usage:\n * ```typescript\n * const router = await WebpiecesRouterFactory.create(new WebpiecesConfig(), {\n * appBindings: [WebpiecesModule, CompanyHeadersModule],\n * });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // api tier\n * router.addFilter(new FilterDefinition(1950, ServiceAuthFilter, '*', 'express')); // express tier\n *\n * // test (no express): runs the api-tier filter chain -> controller\n * const api = router.createApiClient(SaveApi);\n * await api.save(new SaveRequest(...));\n * ```\n *\n * To serve real HTTP, hand this router to the express adapter in @webpieces/http-server\n * (bindExpress / bindAndStartExpress) — express lifecycle lives THERE, never here.\n *\n * @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\nexport class WebpiecesRouter {\n private webpiecesContainer!: Container;\n private appContainer!: Container;\n\n constructor(\n @inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl,\n ) {}\n\n /**\n * Build the app container (child of the framework container), load the @provideSingleton\n * auto-scan + appBindings + appOverrides, and point the RouteBuilder at it. Called once by\n * the factory after this router is resolved from the framework container.\n */\n async initialize(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n this.appContainer = new Container({ parent: webpiecesContainer });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n }\n\n private async loadDIModules(options: WebpiecesRouterOptions): Promise<void> {\n // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's\n // own @provideSingleton classes (binding-decorators global). A client's\n // buildProviderModule() only ever contains the client's classes — never framework internals.\n await this.appContainer.load(buildFrameworkModule());\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 options.appBindings) {\n await this.appContainer.load(module);\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n await this.appContainer.load(options.appOverrides);\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * The controller is resolved from the container at request time.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder);\n return this;\n }\n\n /**\n * Register a filter. Defaults to the 'api' tier (runs in-process AND over HTTP);\n * pass a 'express'-tier FilterDefinition for transport-boundary filters.\n */\n addFilter(filter: FilterDefinition): this {\n this.routeBuilder.addFilter(filter);\n return this;\n }\n\n /**\n * Create an in-process API client that runs the api-tier filter chain + controller\n * with NO express/HTTP. The primary path for tests and node-only callers.\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n return new InProcessApiClientFactory(this.routeBuilder).createApiClient(apiPrototype);\n }\n\n /** The application DI container (child of the framework container). */\n getContainer(): Container {\n return this.appContainer;\n }\n\n /** The framework container (holds the config token + @DocumentDesign design roots). */\n getFrameworkContainer(): Container {\n return this.webpiecesContainer;\n }\n\n /** The route table + filter chain. Used by the express adapter to mount HTTP routes. */\n getRouteBuilder(): RouteBuilderImpl {\n return this.routeBuilder;\n }\n}\n\n/**\n * Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors\n * WebpiecesServerFactory.create), RESOLVES the router from DI, then initializes its app child\n * container with the @provideSingleton auto-scan + appBindings + optional test overrides.\n */\nexport class WebpiecesRouterFactory {\n static async create(\n config: WebpiecesConfig,\n options: WebpiecesRouterOptions,\n ): Promise<WebpiecesRouter> {\n // Platform (framework) container — build via buildFrameworkModule so framework\n // singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,\n // NOT the client's global one.\n const webpiecesContainer = new Container();\n webpiecesContainer.bind(WEBPIECES_CONFIG_TOKEN).toConstantValue(config);\n await webpiecesContainer.load(buildFrameworkModule());\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = webpiecesContainer.get(WebpiecesRouter);\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -2,11 +2,14 @@ export { ApiPath, Endpoint, Authentication, AuthenticationConfig, Public, AuthJw
|
|
|
2
2
|
export type { AuthMode, ApiKind } from '@webpieces/core-util';
|
|
3
3
|
export { SourceFile, ROUTING_METADATA_KEYS, } from './decorators';
|
|
4
4
|
export { provideSingleton, provideSingletonAs, provideTransient } from '@webpieces/core-context';
|
|
5
|
+
export { provideFrameworkSingleton, provideFrameworkSingletonAs, buildFrameworkModule, } from '@webpieces/core-context';
|
|
5
6
|
export { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';
|
|
6
|
-
export {
|
|
7
|
+
export { Routes, RouteBuilder, RouteDefinition, FilterDefinition, FilterTier, } from './WebAppMeta';
|
|
7
8
|
export { MethodMeta } from '@webpieces/http-filters';
|
|
8
9
|
export { RouteHandler } from './RouteHandler';
|
|
9
|
-
export { RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta,
|
|
10
|
+
export { RouteBuilderImpl, RouteHandlerWithMeta, FilterWithMeta, } from './RouteBuilderImpl';
|
|
10
11
|
export { FilterMatcher, HttpFilter } from './FilterMatcher';
|
|
12
|
+
export { InProcessApiClientFactory } from './InProcessApiClientFactory';
|
|
13
|
+
export { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';
|
|
11
14
|
export { RequestContextReader } from '@webpieces/core-context';
|
|
12
15
|
export { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.RouteHandler = exports.MethodMeta = exports.FilterDefinition = exports.RouteDefinition = exports.
|
|
3
|
+
exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = exports.WebpiecesRouterFactory = exports.WebpiecesRouter = exports.InProcessApiClientFactory = exports.FilterMatcher = exports.FilterWithMeta = exports.RouteHandlerWithMeta = exports.RouteBuilderImpl = exports.RouteHandler = exports.MethodMeta = exports.FilterDefinition = exports.RouteDefinition = exports.ApiRoutingFactory = exports.buildFrameworkModule = exports.provideFrameworkSingletonAs = exports.provideFrameworkSingleton = exports.provideTransient = exports.provideSingletonAs = exports.provideSingleton = exports.ROUTING_METADATA_KEYS = exports.SourceFile = exports.isDocumentDesign = exports.DocumentDesign = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = void 0;
|
|
4
4
|
// Re-export API decorators from core-util for convenience
|
|
5
5
|
var core_util_1 = require("@webpieces/core-util");
|
|
6
6
|
Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return core_util_1.ApiPath; } });
|
|
@@ -40,11 +40,15 @@ var core_context_1 = require("@webpieces/core-context");
|
|
|
40
40
|
Object.defineProperty(exports, "provideSingleton", { enumerable: true, get: function () { return core_context_1.provideSingleton; } });
|
|
41
41
|
Object.defineProperty(exports, "provideSingletonAs", { enumerable: true, get: function () { return core_context_1.provideSingletonAs; } });
|
|
42
42
|
Object.defineProperty(exports, "provideTransient", { enumerable: true, get: function () { return core_context_1.provideTransient; } });
|
|
43
|
+
// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)
|
|
44
|
+
var core_context_2 = require("@webpieces/core-context");
|
|
45
|
+
Object.defineProperty(exports, "provideFrameworkSingleton", { enumerable: true, get: function () { return core_context_2.provideFrameworkSingleton; } });
|
|
46
|
+
Object.defineProperty(exports, "provideFrameworkSingletonAs", { enumerable: true, get: function () { return core_context_2.provideFrameworkSingletonAs; } });
|
|
47
|
+
Object.defineProperty(exports, "buildFrameworkModule", { enumerable: true, get: function () { return core_context_2.buildFrameworkModule; } });
|
|
43
48
|
var ApiRoutingFactory_1 = require("./ApiRoutingFactory");
|
|
44
49
|
Object.defineProperty(exports, "ApiRoutingFactory", { enumerable: true, get: function () { return ApiRoutingFactory_1.ApiRoutingFactory; } });
|
|
45
|
-
// Core routing types
|
|
50
|
+
// Core routing types
|
|
46
51
|
var WebAppMeta_1 = require("./WebAppMeta");
|
|
47
|
-
Object.defineProperty(exports, "WEBAPP_META_TOKEN", { enumerable: true, get: function () { return WebAppMeta_1.WEBAPP_META_TOKEN; } });
|
|
48
52
|
Object.defineProperty(exports, "RouteDefinition", { enumerable: true, get: function () { return WebAppMeta_1.RouteDefinition; } });
|
|
49
53
|
Object.defineProperty(exports, "FilterDefinition", { enumerable: true, get: function () { return WebAppMeta_1.FilterDefinition; } });
|
|
50
54
|
// Method metadata (moved to http-filters) re-exported for back-compat; route handler
|
|
@@ -60,9 +64,16 @@ Object.defineProperty(exports, "FilterWithMeta", { enumerable: true, get: functi
|
|
|
60
64
|
// Filter matching
|
|
61
65
|
var FilterMatcher_1 = require("./FilterMatcher");
|
|
62
66
|
Object.defineProperty(exports, "FilterMatcher", { enumerable: true, get: function () { return FilterMatcher_1.FilterMatcher; } });
|
|
67
|
+
// In-process API client builder (node-only; the primary test/in-process path)
|
|
68
|
+
var InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
69
|
+
Object.defineProperty(exports, "InProcessApiClientFactory", { enumerable: true, get: function () { return InProcessApiClientFactory_1.InProcessApiClientFactory; } });
|
|
70
|
+
// Node-only router (the express-free heart: container + filter chain + in-process client)
|
|
71
|
+
var WebpiecesRouter_1 = require("./WebpiecesRouter");
|
|
72
|
+
Object.defineProperty(exports, "WebpiecesRouter", { enumerable: true, get: function () { return WebpiecesRouter_1.WebpiecesRouter; } });
|
|
73
|
+
Object.defineProperty(exports, "WebpiecesRouterFactory", { enumerable: true, get: function () { return WebpiecesRouter_1.WebpiecesRouterFactory; } });
|
|
63
74
|
// Context readers (Node.js only) moved to core-context; re-exported for back-compat
|
|
64
|
-
var
|
|
65
|
-
Object.defineProperty(exports, "RequestContextReader", { enumerable: true, get: function () { return
|
|
75
|
+
var core_context_3 = require("@webpieces/core-context");
|
|
76
|
+
Object.defineProperty(exports, "RequestContextReader", { enumerable: true, get: function () { return core_context_3.RequestContextReader; } });
|
|
66
77
|
// Server configuration
|
|
67
78
|
var WebpiecesConfig_1 = require("./WebpiecesConfig");
|
|
68
79
|
Object.defineProperty(exports, "WebpiecesConfig", { enumerable: true, get: function () { return WebpiecesConfig_1.WebpiecesConfig; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;AAAA,0DAA0D;AAC1D,kDA8B8B;AA7B1B,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,2GAAA,cAAc,OAAA;AACd,iHAAA,oBAAoB,OAAA;AACpB,mGAAA,MAAM,OAAA;AACN,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,6GAAA,gBAAgB,OAAA;AAChB,gGAAA,GAAG,OAAA;AACH,mGAAA,MAAM,OAAA;AACN,kGAAA,KAAK,OAAA;AACL,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AACZ,sGAAA,SAAS,OAAA;AACT,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AACX,2HAAA,8BAA8B,OAAA;AAC9B,uGAAA,UAAU,OAAA;AACV,0GAAA,aAAa,OAAA;AACb,oHAAA,uBAAuB,OAAA;AACvB,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,0GAAA,aAAa,OAAA;AACb,0GAAA,aAAa,OAAA;AAEb,2EAA2E;AAC3E,oCAAoC;AACpC,2GAAA,cAAc,OAAA;AACd,6GAAA,gBAAgB,OAAA;AAIpB,+CAA+C;AAC/C,2CAGsB;AAFlB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAGzB,iFAAiF;AACjF,wDAAiG;AAAxF,gHAAA,gBAAgB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;AAAA,0DAA0D;AAC1D,kDA8B8B;AA7B1B,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,2GAAA,cAAc,OAAA;AACd,iHAAA,oBAAoB,OAAA;AACpB,mGAAA,MAAM,OAAA;AACN,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,6GAAA,gBAAgB,OAAA;AAChB,gGAAA,GAAG,OAAA;AACH,mGAAA,MAAM,OAAA;AACN,kGAAA,KAAK,OAAA;AACL,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AACZ,sGAAA,SAAS,OAAA;AACT,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AACX,2HAAA,8BAA8B,OAAA;AAC9B,uGAAA,UAAU,OAAA;AACV,0GAAA,aAAa,OAAA;AACb,oHAAA,uBAAuB,OAAA;AACvB,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,0GAAA,aAAa,OAAA;AACb,0GAAA,aAAa,OAAA;AAEb,2EAA2E;AAC3E,oCAAoC;AACpC,2GAAA,cAAc,OAAA;AACd,6GAAA,gBAAgB,OAAA;AAIpB,+CAA+C;AAC/C,2CAGsB;AAFlB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAGzB,iFAAiF;AACjF,wDAAiG;AAAxF,gHAAA,gBAAgB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAC/D,gGAAgG;AAChG,wDAIiC;AAH7B,yHAAA,yBAAyB,OAAA;AACzB,2HAAA,2BAA2B,OAAA;AAC3B,oHAAA,oBAAoB,OAAA;AAGxB,yDAAmE;AAA1D,sHAAA,iBAAiB,OAAA;AAE1B,qBAAqB;AACrB,2CAMsB;AAHlB,6GAAA,eAAe,OAAA;AACf,8GAAA,gBAAgB,OAAA;AAIpB,qFAAqF;AACrF,wDAAqD;AAA5C,0GAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+BAA+B;AAC/B,uDAI4B;AAHxB,oHAAA,gBAAgB,OAAA;AAChB,wHAAA,oBAAoB,OAAA;AACpB,kHAAA,cAAc,OAAA;AAGlB,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAEtB,8EAA8E;AAC9E,yEAAwE;AAA/D,sIAAA,yBAAyB,OAAA;AAElC,0FAA0F;AAC1F,qDAAoG;AAA3F,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAEhD,oFAAoF;AACpF,wDAA+D;AAAtD,oHAAA,oBAAoB,OAAA;AAE7B,uBAAuB;AACvB,qDAA4E;AAAnE,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n Public,\n AuthJwt,\n AuthOidc,\n AuthSharedSecret,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n ValidateImplementation,\n // @DocumentDesign moved to core-util (design-root marker, browser + Node);\n // re-exported here for back-compat.\n DocumentDesign,\n isDocumentDesign,\n} from '@webpieces/core-util';\nexport type { AuthMode, ApiKind } from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport {\n SourceFile,\n ROUTING_METADATA_KEYS,\n} from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingleton, provideSingletonAs, provideTransient } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonAs,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport {\n Routes,\n RouteBuilder,\n RouteDefinition,\n FilterDefinition,\n FilterTier,\n} from './WebAppMeta';\n\n// Method metadata (moved to http-filters) re-exported for back-compat; route handler\nexport { MethodMeta } from '@webpieces/http-filters';\nexport { RouteHandler } from './RouteHandler';\n\n// Route builder implementation\nexport {\n RouteBuilderImpl,\n RouteHandlerWithMeta,\n FilterWithMeta,\n} from './RouteBuilderImpl';\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// In-process API client builder (node-only; the primary test/in-process path)\nexport { InProcessApiClientFactory } from './InProcessApiClientFactory';\n\n// Node-only router (the express-free heart: container + filter chain + in-process client)\nexport { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';\n\n// Context readers (Node.js only) moved to core-context; re-exported for back-compat\nexport { RequestContextReader } from '@webpieces/core-context';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n"]}
|