@webpieces/http-routing 0.3.292 → 0.3.297
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 +3 -4
- package/src/ApiClient.d.ts +19 -0
- package/src/ApiClient.js +26 -0
- package/src/ApiClient.js.map +1 -0
- package/src/ApiFactory.d.ts +25 -0
- package/src/ApiFactory.js +3 -0
- package/src/ApiFactory.js.map +1 -0
- package/src/ApiRoutingFactory.js +1 -1
- package/src/ApiRoutingFactory.js.map +1 -1
- package/src/AuthConfig.d.ts +30 -0
- package/src/AuthConfig.js +35 -0
- package/src/AuthConfig.js.map +1 -0
- package/src/Filter.d.ts +76 -0
- package/src/Filter.js +75 -0
- package/src/Filter.js.map +1 -0
- package/src/FilterChain.d.ts +30 -0
- package/src/FilterChain.js +63 -0
- package/src/FilterChain.js.map +1 -0
- package/src/FilterMatcher.d.ts +2 -2
- package/src/FilterMatcher.js.map +1 -1
- package/src/InProcessApiClientFactory.d.ts +1 -0
- package/src/InProcessApiClientFactory.js +15 -3
- package/src/InProcessApiClientFactory.js.map +1 -1
- package/src/MethodMeta.d.ts +53 -0
- package/src/MethodMeta.js +74 -0
- package/src/MethodMeta.js.map +1 -0
- package/src/RouteBuilderImpl.d.ts +11 -3
- package/src/RouteBuilderImpl.js +20 -11
- package/src/RouteBuilderImpl.js.map +1 -1
- package/src/RouteHandler.d.ts +1 -1
- package/src/RouteHandler.js.map +1 -1
- package/src/WebAppMeta.d.ts +6 -15
- package/src/WebAppMeta.js +11 -7
- package/src/WebAppMeta.js.map +1 -1
- package/src/WebpiecesRouter.d.ts +21 -10
- package/src/WebpiecesRouter.js +27 -13
- package/src/WebpiecesRouter.js.map +1 -1
- package/src/fillContext.d.ts +11 -0
- package/src/fillContext.js +35 -0
- package/src/fillContext.js.map +1 -0
- package/src/filters/AuthFilter.d.ts +24 -0
- package/src/filters/AuthFilter.js +102 -0
- package/src/filters/AuthFilter.js.map +1 -0
- package/src/filters/ErrorLogFilter.d.ts +15 -0
- package/src/filters/ErrorLogFilter.js +40 -0
- package/src/filters/ErrorLogFilter.js.map +1 -0
- package/src/index.d.ts +9 -4
- package/src/index.js +27 -14
- package/src/index.js.map +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RouteMetadata, AuthMeta } from '@webpieces/core-util';
|
|
2
|
+
/**
|
|
3
|
+
* Metadata about the method being invoked.
|
|
4
|
+
* Passed to filters and contains request information.
|
|
5
|
+
*
|
|
6
|
+
* MethodMeta is DTO-only - it does NOT contain Express req/res, nor the raw headers. The raw
|
|
7
|
+
* inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in
|
|
8
|
+
* RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed
|
|
9
|
+
* body + route/auth metadata that flow as the chain's call argument.
|
|
10
|
+
*
|
|
11
|
+
* It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in
|
|
12
|
+
* @webpieces/http-routing and is express-free, so filter authors reference it without pulling
|
|
13
|
+
* in any express dependency.
|
|
14
|
+
*
|
|
15
|
+
* Fields:
|
|
16
|
+
* - routeMeta: Static route information (httpMethod, path, methodName)
|
|
17
|
+
* - requestDto: The deserialized request body
|
|
18
|
+
* - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators
|
|
19
|
+
* - metadata: Request-scoped data for filters to communicate
|
|
20
|
+
*/
|
|
21
|
+
export declare class MethodMeta {
|
|
22
|
+
/**
|
|
23
|
+
* Route metadata (httpMethod, path, methodName, parameterTypes)
|
|
24
|
+
*/
|
|
25
|
+
routeMeta: RouteMetadata;
|
|
26
|
+
/**
|
|
27
|
+
* The deserialized request DTO.
|
|
28
|
+
*/
|
|
29
|
+
requestDto?: unknown;
|
|
30
|
+
/**
|
|
31
|
+
* Auth metadata from @Public/@Authenticated/@Roles decorators.
|
|
32
|
+
* Populated by ApiRoutingFactory so filters can read auth requirements.
|
|
33
|
+
*/
|
|
34
|
+
authMeta?: AuthMeta;
|
|
35
|
+
/**
|
|
36
|
+
* Additional metadata for storing request-scoped data.
|
|
37
|
+
* Used by filters to pass data to other filters/controllers.
|
|
38
|
+
*/
|
|
39
|
+
metadata: Map<string, unknown>;
|
|
40
|
+
constructor(routeMeta: RouteMetadata, requestDto?: unknown, metadata?: Map<string, unknown>, authMeta?: AuthMeta);
|
|
41
|
+
/**
|
|
42
|
+
* Get the HTTP method (convenience accessor).
|
|
43
|
+
*/
|
|
44
|
+
get httpMethod(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get the request path (convenience accessor).
|
|
47
|
+
*/
|
|
48
|
+
get path(): string;
|
|
49
|
+
/**
|
|
50
|
+
* Get the method name (convenience accessor).
|
|
51
|
+
*/
|
|
52
|
+
get methodName(): string;
|
|
53
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MethodMeta = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Metadata about the method being invoked.
|
|
6
|
+
* Passed to filters and contains request information.
|
|
7
|
+
*
|
|
8
|
+
* MethodMeta is DTO-only - it does NOT contain Express req/res, nor the raw headers. The raw
|
|
9
|
+
* inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in
|
|
10
|
+
* RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed
|
|
11
|
+
* body + route/auth metadata that flow as the chain's call argument.
|
|
12
|
+
*
|
|
13
|
+
* It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in
|
|
14
|
+
* @webpieces/http-routing and is express-free, so filter authors reference it without pulling
|
|
15
|
+
* in any express dependency.
|
|
16
|
+
*
|
|
17
|
+
* Fields:
|
|
18
|
+
* - routeMeta: Static route information (httpMethod, path, methodName)
|
|
19
|
+
* - requestDto: The deserialized request body
|
|
20
|
+
* - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators
|
|
21
|
+
* - metadata: Request-scoped data for filters to communicate
|
|
22
|
+
*/
|
|
23
|
+
class MethodMeta {
|
|
24
|
+
/**
|
|
25
|
+
* Route metadata (httpMethod, path, methodName, parameterTypes)
|
|
26
|
+
*/
|
|
27
|
+
routeMeta;
|
|
28
|
+
/**
|
|
29
|
+
* The deserialized request DTO.
|
|
30
|
+
*/
|
|
31
|
+
// webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary
|
|
32
|
+
requestDto;
|
|
33
|
+
/**
|
|
34
|
+
* Auth metadata from @Public/@Authenticated/@Roles decorators.
|
|
35
|
+
* Populated by ApiRoutingFactory so filters can read auth requirements.
|
|
36
|
+
*/
|
|
37
|
+
authMeta;
|
|
38
|
+
/**
|
|
39
|
+
* Additional metadata for storing request-scoped data.
|
|
40
|
+
* Used by filters to pass data to other filters/controllers.
|
|
41
|
+
*/
|
|
42
|
+
// webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data
|
|
43
|
+
metadata;
|
|
44
|
+
constructor(routeMeta,
|
|
45
|
+
// webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary
|
|
46
|
+
requestDto,
|
|
47
|
+
// webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data
|
|
48
|
+
metadata, authMeta) {
|
|
49
|
+
this.routeMeta = routeMeta;
|
|
50
|
+
this.requestDto = requestDto;
|
|
51
|
+
this.metadata = metadata ?? new Map();
|
|
52
|
+
this.authMeta = authMeta ?? routeMeta.authMeta;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the HTTP method (convenience accessor).
|
|
56
|
+
*/
|
|
57
|
+
get httpMethod() {
|
|
58
|
+
return this.routeMeta.httpMethod;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the request path (convenience accessor).
|
|
62
|
+
*/
|
|
63
|
+
get path() {
|
|
64
|
+
return this.routeMeta.path;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the method name (convenience accessor).
|
|
68
|
+
*/
|
|
69
|
+
get methodName() {
|
|
70
|
+
return this.routeMeta.methodName;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.MethodMeta = MethodMeta;
|
|
74
|
+
//# sourceMappingURL=MethodMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MethodMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/MethodMeta.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,UAAU;IACnB;;OAEG;IACH,SAAS,CAAgB;IAEzB;;OAEG;IACH,wFAAwF;IACxF,UAAU,CAAW;IAErB;;;OAGG;IACH,QAAQ,CAAY;IAEpB;;;OAGG;IACH,yFAAyF;IACzF,QAAQ,CAAuB;IAE/B,YACI,SAAwB;IACxB,wFAAwF;IACxF,UAAoB;IACpB,yFAAyF;IACzF,QAA+B,EAC/B,QAAmB;QAEnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,GAAG,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;IACrC,CAAC;CACJ;AA3DD,gCA2DC","sourcesContent":["import { RouteMetadata, AuthMeta } from '@webpieces/core-util';\n\n/**\n * Metadata about the method being invoked.\n * Passed to filters and contains request information.\n *\n * MethodMeta is DTO-only - it does NOT contain Express req/res, nor the raw headers. The raw\n * inbound request (headers/method/path) lives on the transport-neutral {@link HttpRequest} in\n * RequestContext (read via `RequestContext.getRequest()`); MethodMeta carries only the typed\n * body + route/auth metadata that flow as the chain's call argument.\n *\n * It is the meta type every `Filter<MethodMeta, …>` is parameterized over. It lives in\n * @webpieces/http-routing and is express-free, so filter authors reference it without pulling\n * in any express dependency.\n *\n * Fields:\n * - routeMeta: Static route information (httpMethod, path, methodName)\n * - requestDto: The deserialized request body\n * - authMeta: Auth mode from @Authentication/@AuthOidc/... decorators\n * - metadata: Request-scoped data for filters to communicate\n */\nexport class MethodMeta {\n /**\n * Route metadata (httpMethod, path, methodName, parameterTypes)\n */\n routeMeta: RouteMetadata;\n\n /**\n * The deserialized request DTO.\n */\n // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary\n requestDto?: unknown;\n\n /**\n * Auth metadata from @Public/@Authenticated/@Roles decorators.\n * Populated by ApiRoutingFactory so filters can read auth requirements.\n */\n authMeta?: AuthMeta;\n\n /**\n * Additional metadata for storing request-scoped data.\n * Used by filters to pass data to other filters/controllers.\n */\n // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data\n metadata: Map<string, unknown>;\n\n constructor(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- request DTO type is erased at the filter boundary\n requestDto?: unknown,\n // webpieces-disable no-any-unknown -- request-scoped bag holds heterogeneous filter data\n metadata?: Map<string, unknown>,\n authMeta?: AuthMeta,\n ) {\n this.routeMeta = routeMeta;\n this.requestDto = requestDto;\n this.metadata = metadata ?? new Map();\n this.authMeta = authMeta ?? routeMeta.authMeta;\n }\n\n /**\n * Get the HTTP method (convenience accessor).\n */\n get httpMethod(): string {\n return this.routeMeta.httpMethod;\n }\n\n /**\n * Get the request path (convenience accessor).\n */\n get path(): string {\n return this.routeMeta.path;\n }\n\n /**\n * Get the method name (convenience accessor).\n */\n get methodName(): string {\n return this.routeMeta.methodName;\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Container } from 'inversify';
|
|
2
2
|
import { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';
|
|
3
3
|
import { RouteHandler } from './RouteHandler';
|
|
4
|
-
import { MethodMeta } from '
|
|
4
|
+
import { MethodMeta } from './MethodMeta';
|
|
5
5
|
import { RouteMetadata } from '@webpieces/core-util';
|
|
6
|
-
import { WpResponse, Service } from '
|
|
6
|
+
import { WpResponse, Service } from './Filter';
|
|
7
7
|
import { HttpFilter } from './FilterMatcher';
|
|
8
|
+
import { ApiClient } from './ApiClient';
|
|
8
9
|
/**
|
|
9
10
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
10
11
|
* Stores both the DI-resolved filter and the metadata needed for matching.
|
|
@@ -111,6 +112,13 @@ export declare class RouteBuilderImpl implements RouteBuilder {
|
|
|
111
112
|
* @returns Map of routes with handlers and definitions, keyed by "METHOD:path"
|
|
112
113
|
*/
|
|
113
114
|
getRoutes(): RouteHandlerWithMeta[];
|
|
115
|
+
/**
|
|
116
|
+
* Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +
|
|
117
|
+
* the composed express-tier impl (filter chain → controller). This is what
|
|
118
|
+
* {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl
|
|
119
|
+
* WITHOUT ever seeing this RouteBuilder.
|
|
120
|
+
*/
|
|
121
|
+
apiClients(): ApiClient[];
|
|
114
122
|
/**
|
|
115
123
|
* Get all filters sorted by priority (highest priority first).
|
|
116
124
|
*
|
|
@@ -136,7 +144,7 @@ export declare class RouteBuilderImpl implements RouteBuilder {
|
|
|
136
144
|
* @param routeWithMeta - Route handler with metadata
|
|
137
145
|
* @returns The service for this route
|
|
138
146
|
*/
|
|
139
|
-
createRouteHandler(routeWithMeta: RouteHandlerWithMeta
|
|
147
|
+
createRouteHandler(routeWithMeta: RouteHandlerWithMeta): Service<MethodMeta, WpResponse<unknown>>;
|
|
140
148
|
/**
|
|
141
149
|
* Create an invoker function for a route (for testing via createApiClient).
|
|
142
150
|
* Uses routeMap for O(1) lookup, sets up the filter chain ONCE,
|
package/src/RouteBuilderImpl.js
CHANGED
|
@@ -4,8 +4,9 @@ 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
|
|
7
|
+
const Filter_1 = require("./Filter");
|
|
8
8
|
const FilterMatcher_1 = require("./FilterMatcher");
|
|
9
|
+
const ApiClient_1 = require("./ApiClient");
|
|
9
10
|
const core_util_1 = require("@webpieces/core-util");
|
|
10
11
|
const log = core_util_1.LogManager.getLogger('RouteBuilder');
|
|
11
12
|
/**
|
|
@@ -168,6 +169,15 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
168
169
|
getRoutes() {
|
|
169
170
|
return this.routes;
|
|
170
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +
|
|
174
|
+
* the composed express-tier impl (filter chain → controller). This is what
|
|
175
|
+
* {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl
|
|
176
|
+
* WITHOUT ever seeing this RouteBuilder.
|
|
177
|
+
*/
|
|
178
|
+
apiClients() {
|
|
179
|
+
return this.routes.map((routeWithMeta) => new ApiClient_1.ApiClient(routeWithMeta.definition.apiClass, routeWithMeta.definition.routeMeta, this.createRouteHandler(routeWithMeta)));
|
|
180
|
+
}
|
|
171
181
|
/**
|
|
172
182
|
* Get all filters sorted by priority (highest priority first).
|
|
173
183
|
*
|
|
@@ -205,14 +215,14 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
205
215
|
* @param routeWithMeta - Route handler with metadata
|
|
206
216
|
* @returns The service for this route
|
|
207
217
|
*/
|
|
208
|
-
createRouteHandler(routeWithMeta
|
|
218
|
+
createRouteHandler(routeWithMeta) {
|
|
209
219
|
const route = routeWithMeta.definition;
|
|
210
220
|
const routeMeta = route.routeMeta;
|
|
211
221
|
log.info(`[RouteBuilder] Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
const filterDefinitions = this.getFilterDefinitions()
|
|
222
|
+
// ONE chain for both HTTP and in-process — no transport tier. The fixed framework
|
|
223
|
+
// filters (ErrorLogFilter, AuthFilter) are auto-installed and read the transport-neutral
|
|
224
|
+
// HttpRequest, so they run identically in both.
|
|
225
|
+
const filterDefinitions = this.getFilterDefinitions();
|
|
216
226
|
// Find matching filters for this route
|
|
217
227
|
const matchingFilters = FilterMatcher_1.FilterMatcher.findMatchingFilters(route.controllerFilepath, filterDefinitions);
|
|
218
228
|
// Create service that wraps the controller execution
|
|
@@ -222,11 +232,11 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
222
232
|
// A void endpoint (e.g. a @PubSub cloud-task handler returning Promise<void>)
|
|
223
233
|
// yields undefined; coerce to {} so the response is a non-null JSON body
|
|
224
234
|
// (downstream LogApiCall/serialization require one), mirroring `result ?? {}`.
|
|
225
|
-
return new
|
|
235
|
+
return new Filter_1.WpResponse(result ?? {});
|
|
226
236
|
},
|
|
227
237
|
};
|
|
228
238
|
if (matchingFilters.length === 0) {
|
|
229
|
-
throw new Error("No filters found for route
|
|
239
|
+
throw new Error("No filters found for route — the framework auto-installs ErrorLogFilter + AuthFilter, so this indicates a wiring problem.");
|
|
230
240
|
}
|
|
231
241
|
// Chain filters: highest priority (first in array) should run first (be outermost)
|
|
232
242
|
// Build from innermost (lowest priority) to outermost (highest priority)
|
|
@@ -257,9 +267,8 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
257
267
|
if (!routeWithMeta) {
|
|
258
268
|
throw new Error(`Route not found: ${method} ${path}`);
|
|
259
269
|
}
|
|
260
|
-
// Setup filter chain ONCE (not on every invocation!).
|
|
261
|
-
|
|
262
|
-
return this.createRouteHandler(routeWithMeta, false);
|
|
270
|
+
// Setup filter chain ONCE (not on every invocation!). Same chain as HTTP — auth included.
|
|
271
|
+
return this.createRouteHandler(routeWithMeta);
|
|
263
272
|
}
|
|
264
273
|
/**
|
|
265
274
|
* Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 './MethodMeta';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { WpResponse, Service } from './Filter';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { ApiClient } from './ApiClient';\nimport { ClassType } from './ApiRoutingFactory';\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 * Reify every registered route as an {@link ApiClient}: its API contract + routeMeta +\n * the composed express-tier impl (filter chain → controller). This is what\n * {@link ApiFactory.apiClients} returns; the express layer binds each ApiClient's impl\n * WITHOUT ever seeing this RouteBuilder.\n */\n apiClients(): ApiClient[] {\n return this.routes.map(\n (routeWithMeta: RouteHandlerWithMeta) =>\n new ApiClient(\n routeWithMeta.definition.apiClass as ClassType,\n routeWithMeta.definition.routeMeta,\n this.createRouteHandler(routeWithMeta),\n ),\n );\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 // ONE chain for both HTTP and in-process — no transport tier. The fixed framework\n // filters (ErrorLogFilter, AuthFilter) are auto-installed and read the transport-neutral\n // HttpRequest, so they run identically in both.\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 — the framework auto-installs ErrorLogFilter + AuthFilter, so this indicates a wiring problem.\");\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!). Same chain as HTTP — auth included.\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"]}
|
package/src/RouteHandler.d.ts
CHANGED
package/src/RouteHandler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteHandler.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteHandler.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAsB,YAAY;CAOjC;AAPD,oCAOC","sourcesContent":["import { MethodMeta } from '
|
|
1
|
+
{"version":3,"file":"RouteHandler.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteHandler.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACH,MAAsB,YAAY;CAOjC;AAPD,oCAOC","sourcesContent":["import { MethodMeta } from './MethodMeta';\n\n/**\n * Handler class for routes.\n * Takes a MethodMeta and returns the controller method result.\n *\n * Generic type parameter TResult represents the return type of the controller method.\n * Example: RouteHandler<SaveResponse> for a method that returns Promise<SaveResponse>\n *\n * Using unknown as default instead of any forces type safety - consumers must\n * handle the result appropriately rather than assuming any type.\n *\n * This is a class instead of a function type to make it easier to trace\n * who is calling what in the debugger/IDE.\n */\nexport abstract class RouteHandler<TResult = unknown> {\n /**\n * Execute the route handler.\n * @param meta - The method metadata containing request info and params\n * @returns Promise of the controller method result\n */\n abstract execute(meta: MethodMeta): Promise<TResult>;\n}\n"]}
|
package/src/WebAppMeta.d.ts
CHANGED
|
@@ -27,17 +27,9 @@ export declare class RouteDefinition {
|
|
|
27
27
|
routeMeta: RouteMetadata;
|
|
28
28
|
controllerClass: any;
|
|
29
29
|
controllerFilepath?: string | undefined;
|
|
30
|
-
|
|
30
|
+
apiClass?: unknown | undefined;
|
|
31
|
+
constructor(routeMeta: RouteMetadata, controllerClass: any, controllerFilepath?: string | undefined, apiClass?: unknown | undefined);
|
|
31
32
|
}
|
|
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';
|
|
41
33
|
/**
|
|
42
34
|
* Definition of a filter with priority.
|
|
43
35
|
*
|
|
@@ -48,8 +40,9 @@ export type FilterTier = 'express' | 'api';
|
|
|
48
40
|
*
|
|
49
41
|
* If filepathPattern is not specified, the filter matches all controllers.
|
|
50
42
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
43
|
+
* Every filter runs for BOTH real HTTP requests AND the in-process createApiClient — there is
|
|
44
|
+
* no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that
|
|
45
|
+
* reads the transport-neutral HttpRequest, so it runs identically in both.
|
|
53
46
|
*/
|
|
54
47
|
export declare class FilterDefinition {
|
|
55
48
|
priority: number;
|
|
@@ -60,7 +53,5 @@ export declare class FilterDefinition {
|
|
|
60
53
|
* If not specified, defaults to matching all controllers.
|
|
61
54
|
*/
|
|
62
55
|
filepathPattern: string;
|
|
63
|
-
|
|
64
|
-
tier: FilterTier;
|
|
65
|
-
constructor(priority: number, filterClass: any, filepathPattern: string, tier?: FilterTier);
|
|
56
|
+
constructor(priority: number, filterClass: any, filepathPattern: string);
|
|
66
57
|
}
|
package/src/WebAppMeta.js
CHANGED
|
@@ -11,10 +11,16 @@ class RouteDefinition {
|
|
|
11
11
|
routeMeta;
|
|
12
12
|
controllerClass;
|
|
13
13
|
controllerFilepath;
|
|
14
|
-
|
|
14
|
+
apiClass;
|
|
15
|
+
constructor(routeMeta,
|
|
16
|
+
// webpieces-disable no-any-unknown -- arbitrary DI controller class used as a container token
|
|
17
|
+
controllerClass, controllerFilepath,
|
|
18
|
+
// The @ApiPath prototype this route belongs to; surfaced on ApiFactory.apiClients().
|
|
19
|
+
apiClass) {
|
|
15
20
|
this.routeMeta = routeMeta;
|
|
16
21
|
this.controllerClass = controllerClass;
|
|
17
22
|
this.controllerFilepath = controllerFilepath;
|
|
23
|
+
this.apiClass = apiClass;
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
exports.RouteDefinition = RouteDefinition;
|
|
@@ -28,8 +34,9 @@ exports.RouteDefinition = RouteDefinition;
|
|
|
28
34
|
*
|
|
29
35
|
* If filepathPattern is not specified, the filter matches all controllers.
|
|
30
36
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
37
|
+
* Every filter runs for BOTH real HTTP requests AND the in-process createApiClient — there is
|
|
38
|
+
* no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that
|
|
39
|
+
* reads the transport-neutral HttpRequest, so it runs identically in both.
|
|
33
40
|
*/
|
|
34
41
|
class FilterDefinition {
|
|
35
42
|
priority;
|
|
@@ -42,14 +49,11 @@ class FilterDefinition {
|
|
|
42
49
|
* If not specified, defaults to matching all controllers.
|
|
43
50
|
*/
|
|
44
51
|
filepathPattern;
|
|
45
|
-
/** Execution tier — see {@link FilterTier}. Defaults to 'api'. */
|
|
46
|
-
tier;
|
|
47
52
|
// webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token
|
|
48
|
-
constructor(priority, filterClass, filepathPattern
|
|
53
|
+
constructor(priority, filterClass, filepathPattern) {
|
|
49
54
|
this.priority = priority;
|
|
50
55
|
this.filterClass = filterClass;
|
|
51
56
|
this.filepathPattern = filepathPattern;
|
|
52
|
-
this.tier = tier;
|
|
53
57
|
this.filter = undefined; // Set later by RouteBuilder
|
|
54
58
|
}
|
|
55
59
|
}
|
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":";;;AAsBA;;;;;GAKG;AACH,MAAa,eAAe;IAEb;
|
|
1
|
+
{"version":3,"file":"WebAppMeta.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebAppMeta.ts"],"names":[],"mappings":";;;AAsBA;;;;;GAKG;AACH,MAAa,eAAe;IAEb;IAEA;IACA;IAEA;IANX,YACW,SAAwB;IAC/B,8FAA8F;IACvF,eAAoB,EACpB,kBAA2B;IAClC,qFAAqF;IAC9E,QAAkB;QALlB,cAAS,GAAT,SAAS,CAAe;QAExB,oBAAe,GAAf,eAAe,CAAK;QACpB,uBAAkB,GAAlB,kBAAkB,CAAS;QAE3B,aAAQ,GAAR,QAAQ,CAAU;IAC1B,CAAC;CACP;AATD,0CASC;AAED;;;;;;;;;;;;;GAaG;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,8FAA8F;IAC9F,YAAY,QAAgB,EAAE,WAAgB,EAAE,eAAuB;QACnE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,4BAA4B;IACzD,CAAC;CACJ;AApBD,4CAoBC;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 // webpieces-disable no-any-unknown -- arbitrary DI controller class used as a container token\n public controllerClass: any,\n public controllerFilepath?: string,\n // The @ApiPath prototype this route belongs to; surfaced on ApiFactory.apiClients().\n public apiClass?: unknown,\n ) {}\n}\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 * Every filter runs for BOTH real HTTP requests AND the in-process createApiClient — there is\n * no transport tier. Transport-boundary auth is a fixed framework filter (AuthFilter) that\n * reads the transport-neutral HttpRequest, so it runs identically in both.\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 // webpieces-disable no-any-unknown -- filterClass param is an arbitrary DI filter class token\n constructor(priority: number, filterClass: any, filepathPattern: string) {\n this.priority = priority;\n this.filterClass = filterClass;\n this.filepathPattern = filepathPattern;\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"]}
|
package/src/WebpiecesRouter.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { RouteBuilderImpl } from './RouteBuilderImpl';
|
|
|
3
3
|
import { ClassType } from './ApiRoutingFactory';
|
|
4
4
|
import { FilterDefinition } from './WebAppMeta';
|
|
5
5
|
import { WebpiecesConfig } from './WebpiecesConfig';
|
|
6
|
+
import { ApiFactory } from './ApiFactory';
|
|
7
|
+
import { ApiClient } from './ApiClient';
|
|
6
8
|
/**
|
|
7
9
|
* Options for {@link WebpiecesRouterFactory.create}.
|
|
8
10
|
*
|
|
@@ -35,10 +37,10 @@ export interface WebpiecesRouterOptions {
|
|
|
35
37
|
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
36
38
|
* });
|
|
37
39
|
* router.addRoutes(SaveApi, SaveController);
|
|
38
|
-
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*'));
|
|
39
|
-
*
|
|
40
|
+
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
|
|
41
|
+
* // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
|
|
40
42
|
*
|
|
41
|
-
* // test (no express): runs the
|
|
43
|
+
* // test (no express): runs the SAME filter chain (incl. auth) -> controller
|
|
42
44
|
* const api = router.createApiClient(SaveApi);
|
|
43
45
|
* await api.save(new SaveRequest(...));
|
|
44
46
|
* ```
|
|
@@ -48,7 +50,7 @@ export interface WebpiecesRouterOptions {
|
|
|
48
50
|
*
|
|
49
51
|
* @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.
|
|
50
52
|
*/
|
|
51
|
-
export declare class WebpiecesRouter {
|
|
53
|
+
export declare class WebpiecesRouter implements ApiFactory {
|
|
52
54
|
private readonly routeBuilder;
|
|
53
55
|
private webpiecesContainer;
|
|
54
56
|
private appContainer;
|
|
@@ -59,6 +61,13 @@ export declare class WebpiecesRouter {
|
|
|
59
61
|
* the factory after this router is resolved from the framework container.
|
|
60
62
|
*/
|
|
61
63
|
initialize(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Auto-install the two fixed framework filters on every route (apps add only their own
|
|
66
|
+
* filters below these): ErrorLogFilter outermost (log + let the transport translate), then
|
|
67
|
+
* AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND
|
|
68
|
+
* in-process — there is no transport tier.
|
|
69
|
+
*/
|
|
70
|
+
private installFixedFilters;
|
|
62
71
|
private loadDIModules;
|
|
63
72
|
/**
|
|
64
73
|
* Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
|
|
@@ -66,8 +75,8 @@ export declare class WebpiecesRouter {
|
|
|
66
75
|
*/
|
|
67
76
|
addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
|
|
68
77
|
/**
|
|
69
|
-
* Register a filter
|
|
70
|
-
*
|
|
78
|
+
* Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed
|
|
79
|
+
* ErrorLogFilter + AuthFilter).
|
|
71
80
|
*/
|
|
72
81
|
addFilter(filter: FilterDefinition): this;
|
|
73
82
|
/**
|
|
@@ -75,12 +84,14 @@ export declare class WebpiecesRouter {
|
|
|
75
84
|
* with NO express/HTTP. The primary path for tests and node-only callers.
|
|
76
85
|
*/
|
|
77
86
|
createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
|
|
87
|
+
/**
|
|
88
|
+
* Reify the registered routes as {@link ApiClient}s (api contract + routeMeta + composed
|
|
89
|
+
* filter-chain→controller impl). This is the ONLY handoff to the express layer — the
|
|
90
|
+
* internal RouteBuilder never leaves this class.
|
|
91
|
+
*/
|
|
92
|
+
apiClients(): ApiClient[];
|
|
78
93
|
/** The application DI container (child of the framework container). */
|
|
79
94
|
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
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors
|
package/src/WebpiecesRouter.js
CHANGED
|
@@ -8,8 +8,11 @@ const core_util_1 = require("@webpieces/core-util");
|
|
|
8
8
|
const core_context_1 = require("@webpieces/core-context");
|
|
9
9
|
const RouteBuilderImpl_1 = require("./RouteBuilderImpl");
|
|
10
10
|
const ApiRoutingFactory_1 = require("./ApiRoutingFactory");
|
|
11
|
+
const WebAppMeta_1 = require("./WebAppMeta");
|
|
11
12
|
const WebpiecesConfig_1 = require("./WebpiecesConfig");
|
|
12
13
|
const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
14
|
+
const ErrorLogFilter_1 = require("./filters/ErrorLogFilter");
|
|
15
|
+
const AuthFilter_1 = require("./filters/AuthFilter");
|
|
13
16
|
/**
|
|
14
17
|
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
15
18
|
* chain + an in-process API client. It has NO express dependency, so it runs anywhere
|
|
@@ -29,10 +32,10 @@ const InProcessApiClientFactory_1 = require("./InProcessApiClientFactory");
|
|
|
29
32
|
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
30
33
|
* });
|
|
31
34
|
* router.addRoutes(SaveApi, SaveController);
|
|
32
|
-
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*'));
|
|
33
|
-
*
|
|
35
|
+
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
|
|
36
|
+
* // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
|
|
34
37
|
*
|
|
35
|
-
* // test (no express): runs the
|
|
38
|
+
* // test (no express): runs the SAME filter chain (incl. auth) -> controller
|
|
36
39
|
* const api = router.createApiClient(SaveApi);
|
|
37
40
|
* await api.save(new SaveRequest(...));
|
|
38
41
|
* ```
|
|
@@ -60,6 +63,17 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
60
63
|
this.appContainer = new inversify_1.Container({ parent: webpiecesContainer });
|
|
61
64
|
this.routeBuilder.setContainer(this.appContainer);
|
|
62
65
|
await this.loadDIModules(options);
|
|
66
|
+
this.installFixedFilters();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Auto-install the two fixed framework filters on every route (apps add only their own
|
|
70
|
+
* filters below these): ErrorLogFilter outermost (log + let the transport translate), then
|
|
71
|
+
* AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND
|
|
72
|
+
* in-process — there is no transport tier.
|
|
73
|
+
*/
|
|
74
|
+
installFixedFilters() {
|
|
75
|
+
this.addFilter(new WebAppMeta_1.FilterDefinition(1_000_000, ErrorLogFilter_1.ErrorLogFilter, '*'));
|
|
76
|
+
this.addFilter(new WebAppMeta_1.FilterDefinition(900_000, AuthFilter_1.AuthFilter, '*'));
|
|
63
77
|
}
|
|
64
78
|
async loadDIModules(options) {
|
|
65
79
|
// Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's
|
|
@@ -86,8 +100,8 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
86
100
|
return this;
|
|
87
101
|
}
|
|
88
102
|
/**
|
|
89
|
-
* Register a filter
|
|
90
|
-
*
|
|
103
|
+
* Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed
|
|
104
|
+
* ErrorLogFilter + AuthFilter).
|
|
91
105
|
*/
|
|
92
106
|
addFilter(filter) {
|
|
93
107
|
this.routeBuilder.addFilter(filter);
|
|
@@ -101,18 +115,18 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
101
115
|
createApiClient(apiPrototype) {
|
|
102
116
|
return new InProcessApiClientFactory_1.InProcessApiClientFactory(this.routeBuilder).createApiClient(apiPrototype);
|
|
103
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Reify the registered routes as {@link ApiClient}s (api contract + routeMeta + composed
|
|
120
|
+
* filter-chain→controller impl). This is the ONLY handoff to the express layer — the
|
|
121
|
+
* internal RouteBuilder never leaves this class.
|
|
122
|
+
*/
|
|
123
|
+
apiClients() {
|
|
124
|
+
return this.routeBuilder.apiClients();
|
|
125
|
+
}
|
|
104
126
|
/** The application DI container (child of the framework container). */
|
|
105
127
|
getContainer() {
|
|
106
128
|
return this.appContainer;
|
|
107
129
|
}
|
|
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
130
|
};
|
|
117
131
|
exports.WebpiecesRouter = WebpiecesRouter;
|
|
118
132
|
exports.WebpiecesRouter = WebpiecesRouter = tslib_1.__decorate([
|