@webpieces/http-routing 0.4.987 → 0.4.988
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/README.md +28 -0
- package/package.json +7 -7
- package/src/RouteBuilderImpl.d.ts +4 -0
- package/src/RouteBuilderImpl.js +10 -2
- package/src/RouteBuilderImpl.js.map +1 -1
- package/src/RuntimeStartupTiming.d.ts +44 -0
- package/src/RuntimeStartupTiming.js +100 -0
- package/src/RuntimeStartupTiming.js.map +1 -0
- package/src/WebpiecesRouter.d.ts +5 -1
- package/src/WebpiecesRouter.js +16 -10
- package/src/WebpiecesRouter.js.map +1 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +4 -1
- package/src/index.js.map +1 -1
- package/src/setupRuntime.d.ts +6 -1
- package/src/setupRuntime.js +15 -8
- package/src/setupRuntime.js.map +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,34 @@ the filter chain without proving deployed IAM or Google signature validation.
|
|
|
23
23
|
See [the Cloud Run OIDC security boundary](../../../docs/architecture/cloud-run-oidc.md) for the
|
|
24
24
|
current verifier behavior and the assumptions required for that deployment model.
|
|
25
25
|
|
|
26
|
+
## Startup timing observer
|
|
27
|
+
|
|
28
|
+
`RuntimeSetupOptions` accepts an optional `startupObserver` after `config`. A host implementing
|
|
29
|
+
`RuntimeStartupObserver.start(operation)` returns a `RuntimeStartupSpan` with `success()` and
|
|
30
|
+
`failure()` methods. The host owns its monotonic clock, aggregation and output; the framework imports
|
|
31
|
+
no telemetry SDK and reads no timing clock when no observer is supplied. Direct router factory users
|
|
32
|
+
can supply the same observer through `WebpiecesRouterOptions.startupObserver`.
|
|
33
|
+
|
|
34
|
+
Operations identify header/logging setup, wiring discovery, framework/application DI module loading,
|
|
35
|
+
application binding configuration, fixed filters, filter resolution, route groups, API route registration
|
|
36
|
+
and controller resolution. Names are code identities, never config values or request data. Binding
|
|
37
|
+
configuration is awaited, including asynchronous modules. The normal API invocation path produces no
|
|
38
|
+
startup events.
|
|
39
|
+
|
|
40
|
+
Times are **inclusive**: route registration includes its controller/dependency resolutions, and container
|
|
41
|
+
creation includes binding/filter setup. Do not sum nested intervals. Repeated `controller-resolution`
|
|
42
|
+
operations can return the same cached singleton; they are not counts of new constructors. The observer
|
|
43
|
+
should aggregate bounded slowest groups, retain failure/incomplete spans, and distinguish this work from
|
|
44
|
+
Node module imports that occurred before runtime setup.
|
|
45
|
+
|
|
46
|
+
Diagnostics do not change eager/lazy behavior. Routes still resolve controllers during registration and
|
|
47
|
+
validate authentication/authorization at startup. No request context or second dispatcher is introduced.
|
|
48
|
+
An observer that throws is disabled for that timing boundary after a bootstrap warning; observer failures
|
|
49
|
+
cannot replace the application result or its original thrown startup error. Application failures retain
|
|
50
|
+
nonzero/failure handling at the host's existing startup boundary. Keep observer callbacks small and
|
|
51
|
+
nonthrowing; JavaScript startup observation runs synchronously in the operation's process, unlike a
|
|
52
|
+
separate tracing agent.
|
|
53
|
+
|
|
26
54
|
## Documentation
|
|
27
55
|
|
|
28
56
|
See the main [WebPieces README](https://github.com/deanhiller/webpieces-ts#readme) for complete documentation and examples.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-routing",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.988",
|
|
4
4
|
"description": "Decorator-based routing with auto-wiring for WebPieces",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
|
-
"@webpieces/core-context": "0.4.
|
|
26
|
-
"@webpieces/core-util": "0.4.
|
|
27
|
-
"@webpieces/gcp-identity": "0.4.
|
|
25
|
+
"@webpieces/core-context": "0.4.988",
|
|
26
|
+
"@webpieces/core-util": "0.4.988",
|
|
27
|
+
"@webpieces/gcp-identity": "0.4.988",
|
|
28
28
|
"inversify": "7.10.4",
|
|
29
29
|
"jsonwebtoken": "9.0.2",
|
|
30
30
|
"minimatch": "10.0.1",
|
|
31
|
-
"@webpieces/http-client-core": "0.4.
|
|
32
|
-
"@webpieces/http-client-node": "0.4.
|
|
33
|
-
"@webpieces/cloudtasks-client": "0.4.
|
|
31
|
+
"@webpieces/http-client-core": "0.4.988",
|
|
32
|
+
"@webpieces/http-client-node": "0.4.988",
|
|
33
|
+
"@webpieces/cloudtasks-client": "0.4.988"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -6,6 +6,7 @@ import { RouteMetadata } from '@webpieces/core-util';
|
|
|
6
6
|
import { Service } from '@webpieces/core-util';
|
|
7
7
|
import { WpResponse } from './WpResponse';
|
|
8
8
|
import { HttpFilter } from './FilterMatcher';
|
|
9
|
+
import { RuntimeStartupObserver } from './RuntimeStartupTiming';
|
|
9
10
|
/**
|
|
10
11
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
11
12
|
* Stores both the DI-resolved filter and the metadata needed for matching.
|
|
@@ -57,6 +58,9 @@ export declare class RouteBuilderImpl implements RouteBuilder {
|
|
|
57
58
|
private routes;
|
|
58
59
|
private filterRegistry;
|
|
59
60
|
private container?;
|
|
61
|
+
private startupTiming;
|
|
62
|
+
/** Installed by the router factory; request dispatch does not resolve controllers again. */
|
|
63
|
+
setStartupObserver(observer?: RuntimeStartupObserver): void;
|
|
60
64
|
/**
|
|
61
65
|
* Map for O(1) route lookup by method:path.
|
|
62
66
|
* Used by both addRoute() and createRouteInvoker() for fast route access.
|
package/src/RouteBuilderImpl.js
CHANGED
|
@@ -8,6 +8,7 @@ const FilterMatcher_1 = require("./FilterMatcher");
|
|
|
8
8
|
const core_util_1 = require("@webpieces/core-util");
|
|
9
9
|
const AuthorizationHook_1 = require("./AuthorizationHook");
|
|
10
10
|
const AuthFilter_1 = require("./filters/AuthFilter");
|
|
11
|
+
const RuntimeStartupTiming_1 = require("./RuntimeStartupTiming");
|
|
11
12
|
const log = core_util_1.LogManager.getLogger('RouteBuilder');
|
|
12
13
|
/**
|
|
13
14
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
@@ -79,6 +80,11 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
79
80
|
routes = [];
|
|
80
81
|
filterRegistry = [];
|
|
81
82
|
container;
|
|
83
|
+
startupTiming = new RuntimeStartupTiming_1.RuntimeStartupTiming();
|
|
84
|
+
/** Installed by the router factory; request dispatch does not resolve controllers again. */
|
|
85
|
+
setStartupObserver(observer) {
|
|
86
|
+
this.startupTiming = new RuntimeStartupTiming_1.RuntimeStartupTiming(observer);
|
|
87
|
+
}
|
|
82
88
|
/**
|
|
83
89
|
* Map for O(1) route lookup by method:path.
|
|
84
90
|
* Used by both addRoute() and createRouteInvoker() for fast route access.
|
|
@@ -159,7 +165,8 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
159
165
|
}
|
|
160
166
|
const routeMeta = route.routeMeta;
|
|
161
167
|
// Resolve controller instance from DI container ONCE (not on every request!)
|
|
162
|
-
const
|
|
168
|
+
const container = this.container;
|
|
169
|
+
const controller = this.startupTiming.run(RuntimeStartupTiming_1.RuntimeStartupPhase.CONTROLLER, route.controllerClass.name, () => container.get(route.controllerClass));
|
|
163
170
|
// Get the controller method
|
|
164
171
|
const method = controller[routeMeta.methodName];
|
|
165
172
|
if (typeof method !== 'function') {
|
|
@@ -186,7 +193,8 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
186
193
|
throw new Error('Container not set. Call setContainer() before registering filters.');
|
|
187
194
|
}
|
|
188
195
|
// Resolve filter instance from DI container
|
|
189
|
-
const
|
|
196
|
+
const container = this.container;
|
|
197
|
+
const filter = this.startupTiming.run(RuntimeStartupTiming_1.RuntimeStartupPhase.FILTER, filterDef.filterClass.name, () => container.get(filterDef.filterClass));
|
|
190
198
|
// Store filter with its definition
|
|
191
199
|
const filterWithMeta = new FilterWithMeta(filter, filterDef);
|
|
192
200
|
this.filterRegistry.push(filterWithMeta);
|
|
@@ -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 } 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 { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\nimport { AuthorizationService } from './AuthorizationHook';\nimport { AuthFilter } from './filters/AuthFilter';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\ninterface NamedClass {\n name?: string;\n}\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 // webpieces-disable no-any-unknown -- route handlers invoke arbitrary API argument shapes\n private method: (this: unknown, ...args: 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.apply(this.controller, [...meta.requestArgs]);\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()\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 * Two STARTUP checks run first, so a wiring mistake refuses to boot instead of 401ing / 403ing\n * the first real request: the authorization policy must be enforceable, and every auth mode the\n * route declares must be verifiable by what is bound (`AuthFilter.assertRouteVerifiable`, #1362).\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const policy = route.routeMeta.authorization;\n if (!route.routeMeta.authMeta || !policy) throw new Error(`Endpoint ${route.routeMeta.path} requires @WpAuth/@WpAuthPublic and @WpAuthorization.`);\n if (!this.container) throw new Error('Set the route container before registering endpoints.');\n this.container.get(AuthorizationService).validate(policy);\n this.installedAuthFilter()?.assertRouteVerifiable(route.routeMeta);\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(route.routeMeta.httpMethod, route.routeMeta.path);\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * The AuthFilter that will actually enforce routes — the instance `WebpiecesRouter` installed as a\n * fixed filter BEFORE any route is added, so it already holds every app binding. Asking IT (not a\n * fresh `container.get`) means the startup check judges exactly the bindings the request path\n * will use. A hand-assembled builder that installed no AuthFilter enforces no auth mode, so there\n * is nothing to verify.\n */\n private installedAuthFilter(): AuthFilter | undefined {\n for (const entry of this.filterRegistry) {\n if (entry.filter instanceof AuthFilter) return entry.filter;\n }\n return undefined;\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 NamedClass).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 // webpieces-disable no-any-unknown -- resolved controller method has the registered API's arguments\n method as (this: unknown, ...args: unknown[]) => Promise<TResult>,\n );\n\n // Return handler with route definition\n // webpieces-disable no-any-unknown -- heterogeneous handlers share one registration collection\n return new RouteHandlerWithMeta(handler as RouteHandler<unknown>, route);\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(`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 (LogApiFilter, 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 `responseBody: NONE` endpoint (Promise<void>) has NO response: every transport\n // sends 0 bytes (HTTP 204) and every client resolves undefined, so the in-process\n // client must too. LogApiCall logs an undefined response as `response=undefined`.\n if (routeMeta.noResponseBody) return new WpResponse(undefined);\n // Any other route that yields undefined/null still answers a JSON body: coerce to {}.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\n 'No filters found for route — the framework auto-installs LogApiFilter + AuthFilter, so this indicates a wiring problem.',\n );\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"]}
|
|
1
|
+
{"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container } 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 { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\nimport { AuthorizationService } from './AuthorizationHook';\nimport { AuthFilter } from './filters/AuthFilter';\nimport {\n RuntimeStartupObserver,\n RuntimeStartupPhase,\n RuntimeStartupTiming,\n} from './RuntimeStartupTiming';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\ninterface NamedClass {\n name?: string;\n}\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 // webpieces-disable no-any-unknown -- route handlers invoke arbitrary API argument shapes\n private method: (this: unknown, ...args: 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.apply(this.controller, [...meta.requestArgs]);\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()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n private startupTiming = new RuntimeStartupTiming();\n\n /** Installed by the router factory; request dispatch does not resolve controllers again. */\n setStartupObserver(observer?: RuntimeStartupObserver): void {\n this.startupTiming = new RuntimeStartupTiming(observer);\n }\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 * Two STARTUP checks run first, so a wiring mistake refuses to boot instead of 401ing / 403ing\n * the first real request: the authorization policy must be enforceable, and every auth mode the\n * route declares must be verifiable by what is bound (`AuthFilter.assertRouteVerifiable`, #1362).\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const policy = route.routeMeta.authorization;\n if (!route.routeMeta.authMeta || !policy)\n throw new Error(\n `Endpoint ${route.routeMeta.path} requires @WpAuth/@WpAuthPublic and @WpAuthorization.`,\n );\n if (!this.container)\n throw new Error('Set the route container before registering endpoints.');\n this.container.get(AuthorizationService).validate(policy);\n this.installedAuthFilter()?.assertRouteVerifiable(route.routeMeta);\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(route.routeMeta.httpMethod, route.routeMeta.path);\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * The AuthFilter that will actually enforce routes — the instance `WebpiecesRouter` installed as a\n * fixed filter BEFORE any route is added, so it already holds every app binding. Asking IT (not a\n * fresh `container.get`) means the startup check judges exactly the bindings the request path\n * will use. A hand-assembled builder that installed no AuthFilter enforces no auth mode, so there\n * is nothing to verify.\n */\n private installedAuthFilter(): AuthFilter | undefined {\n for (const entry of this.filterRegistry) {\n if (entry.filter instanceof AuthFilter) return entry.filter;\n }\n return undefined;\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 container = this.container;\n const controller = this.startupTiming.run(\n RuntimeStartupPhase.CONTROLLER,\n route.controllerClass.name,\n () => container.get(route.controllerClass),\n ) as Record<\n string,\n // webpieces-disable no-any-unknown -- heterogeneous route handlers erase API-specific result and argument shapes\n unknown\n >;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as NamedClass).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 // webpieces-disable no-any-unknown -- resolved controller method has the registered API's arguments\n method as (this: unknown, ...args: unknown[]) => Promise<TResult>,\n );\n\n // Return handler with route definition\n // webpieces-disable no-any-unknown -- heterogeneous handlers share one registration collection\n return new RouteHandlerWithMeta(handler as RouteHandler<unknown>, route);\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 container = this.container;\n const filter = this.startupTiming.run(\n RuntimeStartupPhase.FILTER,\n filterDef.filterClass.name,\n () => container.get<HttpFilter>(filterDef.filterClass),\n );\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(`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 (LogApiFilter, 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 `responseBody: NONE` endpoint (Promise<void>) has NO response: every transport\n // sends 0 bytes (HTTP 204) and every client resolves undefined, so the in-process\n // client must too. LogApiCall logs an undefined response as `response=undefined`.\n if (routeMeta.noResponseBody) return new WpResponse(undefined);\n // Any other route that yields undefined/null still answers a JSON body: coerce to {}.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\n 'No filters found for route — the framework auto-installs LogApiFilter + AuthFilter, so this indicates a wiring problem.',\n );\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"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Startup operations; durations are inclusive, so nested operations must not be added together. */
|
|
2
|
+
export declare enum RuntimeStartupPhase {
|
|
3
|
+
HEADERS = "headers",
|
|
4
|
+
LOGGING = "logging",
|
|
5
|
+
WIRING = "wiring",
|
|
6
|
+
CONTAINER = "container",
|
|
7
|
+
FRAMEWORK_DI = "framework-di",
|
|
8
|
+
APPLICATION_DI = "application-di",
|
|
9
|
+
BIND_MODULE = "binding-module",
|
|
10
|
+
FIXED_FILTERS = "fixed-filters",
|
|
11
|
+
FILTER = "filter-resolution",
|
|
12
|
+
ROUTE_MODULE = "route-module",
|
|
13
|
+
ROUTES = "route-registration",
|
|
14
|
+
CONTROLLER = "controller-resolution"
|
|
15
|
+
}
|
|
16
|
+
/** Code identity only: never configuration, credentials, request data or exception payloads. */
|
|
17
|
+
export declare class RuntimeStartupOperation {
|
|
18
|
+
readonly phase: RuntimeStartupPhase;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
constructor(phase: RuntimeStartupPhase, name: string);
|
|
21
|
+
}
|
|
22
|
+
/** The host owns the monotonic clock/output and closes each started span exactly once. */
|
|
23
|
+
export interface RuntimeStartupSpan {
|
|
24
|
+
success(): void;
|
|
25
|
+
failure(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Optional startup-only diagnostics. Start/finish use the host's clock, so timing requires no SDK.
|
|
29
|
+
* A controller-resolution event measures container.get, including dependencies and singleton reuse;
|
|
30
|
+
* it does not assert that a new controller was constructed. Normal API calls emit no events.
|
|
31
|
+
*/
|
|
32
|
+
export interface RuntimeStartupObserver {
|
|
33
|
+
start(operation: RuntimeStartupOperation): RuntimeStartupSpan;
|
|
34
|
+
}
|
|
35
|
+
/** Internal operation boundary. With no observer it creates no timing records and reads no clock. */
|
|
36
|
+
export declare class RuntimeStartupTiming {
|
|
37
|
+
private readonly observer?;
|
|
38
|
+
private disabled;
|
|
39
|
+
constructor(observer?: RuntimeStartupObserver | undefined);
|
|
40
|
+
run<T>(phase: RuntimeStartupPhase, name: string, operation: () => T): T;
|
|
41
|
+
runAsync<T>(phase: RuntimeStartupPhase, name: string, operation: () => T | Promise<T>): Promise<T>;
|
|
42
|
+
private begin;
|
|
43
|
+
private finish;
|
|
44
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeStartupTiming = exports.RuntimeStartupOperation = exports.RuntimeStartupPhase = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
/** Startup operations; durations are inclusive, so nested operations must not be added together. */
|
|
6
|
+
var RuntimeStartupPhase;
|
|
7
|
+
(function (RuntimeStartupPhase) {
|
|
8
|
+
RuntimeStartupPhase["HEADERS"] = "headers";
|
|
9
|
+
RuntimeStartupPhase["LOGGING"] = "logging";
|
|
10
|
+
RuntimeStartupPhase["WIRING"] = "wiring";
|
|
11
|
+
RuntimeStartupPhase["CONTAINER"] = "container";
|
|
12
|
+
RuntimeStartupPhase["FRAMEWORK_DI"] = "framework-di";
|
|
13
|
+
RuntimeStartupPhase["APPLICATION_DI"] = "application-di";
|
|
14
|
+
RuntimeStartupPhase["BIND_MODULE"] = "binding-module";
|
|
15
|
+
RuntimeStartupPhase["FIXED_FILTERS"] = "fixed-filters";
|
|
16
|
+
RuntimeStartupPhase["FILTER"] = "filter-resolution";
|
|
17
|
+
RuntimeStartupPhase["ROUTE_MODULE"] = "route-module";
|
|
18
|
+
RuntimeStartupPhase["ROUTES"] = "route-registration";
|
|
19
|
+
RuntimeStartupPhase["CONTROLLER"] = "controller-resolution";
|
|
20
|
+
})(RuntimeStartupPhase || (exports.RuntimeStartupPhase = RuntimeStartupPhase = {}));
|
|
21
|
+
/** Code identity only: never configuration, credentials, request data or exception payloads. */
|
|
22
|
+
class RuntimeStartupOperation {
|
|
23
|
+
phase;
|
|
24
|
+
name;
|
|
25
|
+
constructor(phase, name) {
|
|
26
|
+
this.phase = phase;
|
|
27
|
+
this.name = name;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.RuntimeStartupOperation = RuntimeStartupOperation;
|
|
31
|
+
/** Internal operation boundary. With no observer it creates no timing records and reads no clock. */
|
|
32
|
+
class RuntimeStartupTiming {
|
|
33
|
+
observer;
|
|
34
|
+
disabled = false;
|
|
35
|
+
constructor(observer) {
|
|
36
|
+
this.observer = observer;
|
|
37
|
+
}
|
|
38
|
+
run(phase, name, operation) {
|
|
39
|
+
const span = this.begin(phase, name);
|
|
40
|
+
let succeeded = false;
|
|
41
|
+
// webpieces-disable no-unmanaged-exceptions -- finally only closes diagnostics; the original application error propagates unchanged
|
|
42
|
+
try {
|
|
43
|
+
const result = operation();
|
|
44
|
+
succeeded = true;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.finish(span, succeeded);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async runAsync(phase, name, operation) {
|
|
52
|
+
const span = this.begin(phase, name);
|
|
53
|
+
let succeeded = false;
|
|
54
|
+
// webpieces-disable no-unmanaged-exceptions -- finally only closes diagnostics; the original application error propagates unchanged
|
|
55
|
+
try {
|
|
56
|
+
const result = await operation();
|
|
57
|
+
succeeded = true;
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.finish(span, succeeded);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
begin(phase, name) {
|
|
65
|
+
if (!this.observer || this.disabled)
|
|
66
|
+
return undefined;
|
|
67
|
+
// webpieces-disable no-unmanaged-exceptions -- an optional observer must not replace startup's result/error; disable it with a bounded bootstrap diagnostic
|
|
68
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- diagnostic callback boundary preserves the application result and original error
|
|
69
|
+
try {
|
|
70
|
+
return this.observer.start(new RuntimeStartupOperation(phase, name));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const error = (0, core_util_1.toError)(err);
|
|
74
|
+
this.disabled = true;
|
|
75
|
+
// Headers/logging may not exist yet; use one bootstrap diagnostic for this boundary.
|
|
76
|
+
console.warn('[startup] timing observer disabled after failure', error);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finish(span, succeeded) {
|
|
81
|
+
if (!span || this.disabled)
|
|
82
|
+
return;
|
|
83
|
+
// webpieces-disable no-unmanaged-exceptions -- preserve the application's original exception even if diagnostic output fails
|
|
84
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- diagnostic callback failure must not replace the original startup exception
|
|
85
|
+
try {
|
|
86
|
+
if (succeeded)
|
|
87
|
+
span.success();
|
|
88
|
+
else
|
|
89
|
+
span.failure();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const error = (0, core_util_1.toError)(err);
|
|
93
|
+
this.disabled = true;
|
|
94
|
+
// Headers/logging may not exist yet; use one bootstrap diagnostic for this boundary.
|
|
95
|
+
console.warn('[startup] timing observer disabled after failure', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.RuntimeStartupTiming = RuntimeStartupTiming;
|
|
100
|
+
//# sourceMappingURL=RuntimeStartupTiming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RuntimeStartupTiming.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RuntimeStartupTiming.ts"],"names":[],"mappings":"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","sourcesContent":["import { toError } from '@webpieces/core-util';\n\n/** Startup operations; durations are inclusive, so nested operations must not be added together. */\nexport enum RuntimeStartupPhase {\n HEADERS = 'headers',\n LOGGING = 'logging',\n WIRING = 'wiring',\n CONTAINER = 'container',\n FRAMEWORK_DI = 'framework-di',\n APPLICATION_DI = 'application-di',\n BIND_MODULE = 'binding-module',\n FIXED_FILTERS = 'fixed-filters',\n FILTER = 'filter-resolution',\n ROUTE_MODULE = 'route-module',\n ROUTES = 'route-registration',\n CONTROLLER = 'controller-resolution',\n}\n\n/** Code identity only: never configuration, credentials, request data or exception payloads. */\nexport class RuntimeStartupOperation {\n constructor(\n public readonly phase: RuntimeStartupPhase,\n public readonly name: string,\n ) {}\n}\n\n/** The host owns the monotonic clock/output and closes each started span exactly once. */\nexport interface RuntimeStartupSpan {\n success(): void;\n failure(): void;\n}\n\n/**\n * Optional startup-only diagnostics. Start/finish use the host's clock, so timing requires no SDK.\n * A controller-resolution event measures container.get, including dependencies and singleton reuse;\n * it does not assert that a new controller was constructed. Normal API calls emit no events.\n */\nexport interface RuntimeStartupObserver {\n start(operation: RuntimeStartupOperation): RuntimeStartupSpan;\n}\n\n/** Internal operation boundary. With no observer it creates no timing records and reads no clock. */\nexport class RuntimeStartupTiming {\n private disabled = false;\n\n constructor(private readonly observer?: RuntimeStartupObserver) {}\n\n run<T>(phase: RuntimeStartupPhase, name: string, operation: () => T): T {\n const span = this.begin(phase, name);\n let succeeded = false;\n // webpieces-disable no-unmanaged-exceptions -- finally only closes diagnostics; the original application error propagates unchanged\n try {\n const result = operation();\n succeeded = true;\n return result;\n } finally {\n this.finish(span, succeeded);\n }\n }\n\n async runAsync<T>(\n phase: RuntimeStartupPhase,\n name: string,\n operation: () => T | Promise<T>,\n ): Promise<T> {\n const span = this.begin(phase, name);\n let succeeded = false;\n // webpieces-disable no-unmanaged-exceptions -- finally only closes diagnostics; the original application error propagates unchanged\n try {\n const result = await operation();\n succeeded = true;\n return result;\n } finally {\n this.finish(span, succeeded);\n }\n }\n\n private begin(phase: RuntimeStartupPhase, name: string): RuntimeStartupSpan | undefined {\n if (!this.observer || this.disabled) return undefined;\n // webpieces-disable no-unmanaged-exceptions -- an optional observer must not replace startup's result/error; disable it with a bounded bootstrap diagnostic\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- diagnostic callback boundary preserves the application result and original error\n try {\n return this.observer.start(new RuntimeStartupOperation(phase, name));\n } catch (err: unknown) {\n const error = toError(err);\n this.disabled = true;\n // Headers/logging may not exist yet; use one bootstrap diagnostic for this boundary.\n console.warn('[startup] timing observer disabled after failure', error);\n return undefined;\n }\n }\n\n private finish(span: RuntimeStartupSpan | undefined, succeeded: boolean): void {\n if (!span || this.disabled) return;\n // webpieces-disable no-unmanaged-exceptions -- preserve the application's original exception even if diagnostic output fails\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- diagnostic callback failure must not replace the original startup exception\n try {\n if (succeeded) span.success();\n else span.failure();\n } catch (err: unknown) {\n const error = toError(err);\n this.disabled = true;\n // Headers/logging may not exist yet; use one bootstrap diagnostic for this boundary.\n console.warn('[startup] timing observer disabled after failure', error);\n }\n }\n}\n"]}
|
package/src/WebpiecesRouter.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { WebpiecesConfig } from './WebpiecesConfig';
|
|
|
8
8
|
import { ApiClientFactory } from './ApiClientFactory';
|
|
9
9
|
import { ApiFactory } from './ApiFactory';
|
|
10
10
|
import { ApiClient } from './ApiClient';
|
|
11
|
+
import { RuntimeStartupObserver } from './RuntimeStartupTiming';
|
|
11
12
|
/**
|
|
12
13
|
* Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).
|
|
13
14
|
*
|
|
@@ -21,6 +22,8 @@ export interface WebpiecesRouterOptions {
|
|
|
21
22
|
appBindings: ContainerModule[];
|
|
22
23
|
appOverrides?: ContainerModule;
|
|
23
24
|
config?: WebpiecesConfig;
|
|
25
|
+
/** Optional startup spans; no observer means no clock or timing-output work. */
|
|
26
|
+
startupObserver?: RuntimeStartupObserver;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
@@ -58,6 +61,7 @@ export declare class WebpiecesRouter implements ApiFactory {
|
|
|
58
61
|
private readonly apiClientFactory;
|
|
59
62
|
private webpiecesContainer;
|
|
60
63
|
private appContainer;
|
|
64
|
+
private startupTiming;
|
|
61
65
|
constructor(routeBuilder: RouteBuilderImpl, apiClientFactory: ApiClientFactory);
|
|
62
66
|
/**
|
|
63
67
|
* Build the app container (child of the framework container), load the @provideSingleton
|
|
@@ -77,7 +81,7 @@ export declare class WebpiecesRouter implements ApiFactory {
|
|
|
77
81
|
private loadDIModules;
|
|
78
82
|
/**
|
|
79
83
|
* Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
|
|
80
|
-
*
|
|
84
|
+
* Route registration resolves the controller and its dependency graph eagerly, before requests.
|
|
81
85
|
*/
|
|
82
86
|
addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
|
|
83
87
|
/**
|
package/src/WebpiecesRouter.js
CHANGED
|
@@ -15,6 +15,7 @@ const ApiClientFactory_1 = require("./ApiClientFactory");
|
|
|
15
15
|
const LogApiFilter_1 = require("./filters/LogApiFilter");
|
|
16
16
|
const AuthFilter_1 = require("./filters/AuthFilter");
|
|
17
17
|
const AuthorizationFilter_1 = require("./filters/AuthorizationFilter");
|
|
18
|
+
const RuntimeStartupTiming_1 = require("./RuntimeStartupTiming");
|
|
18
19
|
/**
|
|
19
20
|
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
20
21
|
* chain + an in-process API client. It has NO express dependency, so it runs anywhere
|
|
@@ -51,6 +52,7 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
51
52
|
apiClientFactory;
|
|
52
53
|
webpiecesContainer;
|
|
53
54
|
appContainer;
|
|
55
|
+
startupTiming = new RuntimeStartupTiming_1.RuntimeStartupTiming();
|
|
54
56
|
constructor(routeBuilder, apiClientFactory) {
|
|
55
57
|
this.routeBuilder = routeBuilder;
|
|
56
58
|
this.apiClientFactory = apiClientFactory;
|
|
@@ -62,6 +64,8 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
62
64
|
*/
|
|
63
65
|
async initialize(webpiecesContainer, options) {
|
|
64
66
|
this.webpiecesContainer = webpiecesContainer;
|
|
67
|
+
this.startupTiming = new RuntimeStartupTiming_1.RuntimeStartupTiming(options.startupObserver);
|
|
68
|
+
this.routeBuilder.setStartupObserver(options.startupObserver);
|
|
65
69
|
// App container is a child so app bindings see framework bindings while staying separate.
|
|
66
70
|
// autobind:true lets a concrete @injectable(Singleton) app class self-bind on first resolve
|
|
67
71
|
// (inject-by-type, no @provideSingleton). Framework singletons stay explicit in the parent
|
|
@@ -69,7 +73,7 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
69
73
|
this.appContainer = new inversify_1.Container({ parent: webpiecesContainer, autobind: true });
|
|
70
74
|
this.routeBuilder.setContainer(this.appContainer);
|
|
71
75
|
await this.loadDIModules(options);
|
|
72
|
-
this.installFixedFilters();
|
|
76
|
+
this.startupTiming.run(RuntimeStartupTiming_1.RuntimeStartupPhase.FIXED_FILTERS, 'fixed-filters', () => this.installFixedFilters());
|
|
73
77
|
}
|
|
74
78
|
/**
|
|
75
79
|
* Auto-install the two fixed framework filters on every route (apps add only their own
|
|
@@ -88,24 +92,25 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
88
92
|
// Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's
|
|
89
93
|
// own @provideSingleton classes (binding-decorators global). A client's
|
|
90
94
|
// buildProviderModule() only ever contains the client's classes — never framework internals.
|
|
91
|
-
await this.appContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
92
|
-
await this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
95
|
+
await this.startupTiming.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.FRAMEWORK_DI, 'application-framework-bindings', () => this.appContainer.load((0, core_context_1.buildFrameworkModule)()));
|
|
96
|
+
await this.startupTiming.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.APPLICATION_DI, 'provider-bindings', () => this.appContainer.load((0, binding_decorators_1.buildProviderModule)()));
|
|
93
97
|
// Load all app modules into application container
|
|
94
98
|
// (webpiecesContainer is currently empty, reserved for future framework bindings)
|
|
95
|
-
for (const module of options.appBindings) {
|
|
96
|
-
await this.appContainer.load(module);
|
|
99
|
+
for (const [index, module] of options.appBindings.entries()) {
|
|
100
|
+
await this.startupTiming.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.APPLICATION_DI, `app-binding-${index}`, () => this.appContainer.load(module));
|
|
97
101
|
}
|
|
98
102
|
// Load appOverrides LAST so they can override existing bindings
|
|
99
103
|
if (options.appOverrides) {
|
|
100
|
-
|
|
104
|
+
const overrides = options.appOverrides;
|
|
105
|
+
await this.startupTiming.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.APPLICATION_DI, 'app-overrides', () => this.appContainer.load(overrides));
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
/**
|
|
104
109
|
* Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.
|
|
105
|
-
*
|
|
110
|
+
* Route registration resolves the controller and its dependency graph eagerly, before requests.
|
|
106
111
|
*/
|
|
107
112
|
addRoutes(api, controller) {
|
|
108
|
-
new ApiRoutingFactory_1.ApiRoutingFactory(api, controller).configure(this.routeBuilder);
|
|
113
|
+
this.startupTiming.run(RuntimeStartupTiming_1.RuntimeStartupPhase.ROUTES, `${api.name}:${controller.name}`, () => new ApiRoutingFactory_1.ApiRoutingFactory(api, controller).configure(this.routeBuilder));
|
|
109
114
|
return this;
|
|
110
115
|
}
|
|
111
116
|
/**
|
|
@@ -164,13 +169,14 @@ class WebpiecesRouterFactory {
|
|
|
164
169
|
// Platform (framework) container — build via buildFrameworkModule so framework
|
|
165
170
|
// singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,
|
|
166
171
|
// NOT the client's global one.
|
|
172
|
+
const timing = new RuntimeStartupTiming_1.RuntimeStartupTiming(options.startupObserver);
|
|
167
173
|
const webpiecesContainer = new inversify_1.Container();
|
|
168
174
|
webpiecesContainer
|
|
169
175
|
.bind(WebpiecesConfig_1.WEBPIECES_CONFIG_TOKEN)
|
|
170
176
|
.toConstantValue(options.config ?? new WebpiecesConfig_1.WebpiecesConfig());
|
|
171
|
-
await webpiecesContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
177
|
+
await timing.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.FRAMEWORK_DI, 'platform-framework-bindings', () => webpiecesContainer.load((0, core_context_1.buildFrameworkModule)()));
|
|
172
178
|
// Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.
|
|
173
|
-
const router = webpiecesContainer.get(WebpiecesRouter);
|
|
179
|
+
const router = timing.run(RuntimeStartupTiming_1.RuntimeStartupPhase.FRAMEWORK_DI, 'router-resolution', () => webpiecesContainer.get(WebpiecesRouter));
|
|
174
180
|
await router.initialize(webpiecesContainer, options);
|
|
175
181
|
return router;
|
|
176
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthorizationService } from './AuthorizationHook';\nimport { InvocationAuthentication } from './InvocationAuthentication';\nimport { 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 { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { LogApiFilter } from './filters/LogApiFilter';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { AuthorizationFilter } from './filters/AuthorizationFilter';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).\n *\n * appBindings - DI ContainerModules to load (framework + app). 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 * config - Optional {@link WebpiecesConfig} (recording flags, etc.); defaults to a fresh one.\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n config?: WebpiecesConfig;\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({ appBindings: [AppModule] });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, MyFilter, '*')); // your own filters\n * // (LogApiFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven.\n * // LogApiFilter logs request+response for EVERY call — do NOT install it yourself.)\n *\n * // test (no express): runs the SAME filter chain (incl. auth) -> 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 implements ApiFactory {\n private webpiecesContainer!: Container;\n private appContainer!: Container;\n\n constructor(\n @inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl,\n @inject(ApiClientFactory) private readonly apiClientFactory: ApiClientFactory,\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(\n webpiecesContainer: Container,\n options: WebpiecesRouterOptions,\n ): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n // autobind:true lets a concrete @injectable(Singleton) app class self-bind on first resolve\n // (inject-by-type, no @provideSingleton). Framework singletons stay explicit in the parent\n // (provideFrameworkSingleton -> buildFrameworkModule), found before child-autobind kicks in.\n this.appContainer = new Container({ parent: webpiecesContainer, autobind: true });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.installFixedFilters();\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): LogApiFilter outermost (logs request + response/failure for every\n * call and stamps [Controller.method], then re-throws for the transport to translate), then\n * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND\n * in-process — there is no transport tier. Because LogApiFilter is outermost, requests that\n * AuthFilter rejects (401) are still logged with their body + controller identity.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, LogApiFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n this.addFilter(new FilterDefinition(800_000, AuthorizationFilter, '*'));\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 app 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 user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * LogApiFilter + AuthFilter).\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 this.apiClientFactory.createApiClient(apiPrototype);\n }\n\n /** Transport-only credential bridge; ordinary endpoint filters remain mandatory. */\n // webpieces-disable no-any-unknown -- abstract API constructor signature\n createInvocationApiClient<T>(\n apiPrototype: abstract new (...args: any[]) => T,\n authentication: InvocationAuthentication,\n ): T {\n return this.apiClientFactory.createInvocationApiClient(apiPrototype, authentication);\n }\n\n /**\n * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via\n * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the\n * internal RouteBuilder never leaves.\n */\n apiClients(): ApiClient[] {\n return this.apiClientFactory.apiClients();\n }\n\n authorizationService(): AuthorizationService {\n return this.appContainer.get(AuthorizationService);\n }\n\n /** The application DI container (child of the framework container). */\n getContainer(): Container {\n return this.appContainer;\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(options: WebpiecesRouterOptions): 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\n .bind(WEBPIECES_CONFIG_TOKEN)\n .toConstantValue(options.config ?? new WebpiecesConfig());\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"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthorizationService } from './AuthorizationHook';\nimport { InvocationAuthentication } from './InvocationAuthentication';\nimport { 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 { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { LogApiFilter } from './filters/LogApiFilter';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { AuthorizationFilter } from './filters/AuthorizationFilter';\nimport {\n RuntimeStartupObserver,\n RuntimeStartupPhase,\n RuntimeStartupTiming,\n} from './RuntimeStartupTiming';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).\n *\n * appBindings - DI ContainerModules to load (framework + app). 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 * config - Optional {@link WebpiecesConfig} (recording flags, etc.); defaults to a fresh one.\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n config?: WebpiecesConfig;\n /** Optional startup spans; no observer means no clock or timing-output work. */\n startupObserver?: RuntimeStartupObserver;\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({ appBindings: [AppModule] });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, MyFilter, '*')); // your own filters\n * // (LogApiFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven.\n * // LogApiFilter logs request+response for EVERY call — do NOT install it yourself.)\n *\n * // test (no express): runs the SAME filter chain (incl. auth) -> 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 implements ApiFactory {\n private webpiecesContainer!: Container;\n private appContainer!: Container;\n private startupTiming = new RuntimeStartupTiming();\n\n constructor(\n @inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl,\n @inject(ApiClientFactory) private readonly apiClientFactory: ApiClientFactory,\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(\n webpiecesContainer: Container,\n options: WebpiecesRouterOptions,\n ): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n this.startupTiming = new RuntimeStartupTiming(options.startupObserver);\n this.routeBuilder.setStartupObserver(options.startupObserver);\n\n // App container is a child so app bindings see framework bindings while staying separate.\n // autobind:true lets a concrete @injectable(Singleton) app class self-bind on first resolve\n // (inject-by-type, no @provideSingleton). Framework singletons stay explicit in the parent\n // (provideFrameworkSingleton -> buildFrameworkModule), found before child-autobind kicks in.\n this.appContainer = new Container({ parent: webpiecesContainer, autobind: true });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.startupTiming.run(RuntimeStartupPhase.FIXED_FILTERS, 'fixed-filters', () =>\n this.installFixedFilters(),\n );\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): LogApiFilter outermost (logs request + response/failure for every\n * call and stamps [Controller.method], then re-throws for the transport to translate), then\n * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND\n * in-process — there is no transport tier. Because LogApiFilter is outermost, requests that\n * AuthFilter rejects (401) are still logged with their body + controller identity.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, LogApiFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n this.addFilter(new FilterDefinition(800_000, AuthorizationFilter, '*'));\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.startupTiming.runAsync(\n RuntimeStartupPhase.FRAMEWORK_DI,\n 'application-framework-bindings',\n () => this.appContainer.load(buildFrameworkModule()),\n );\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'provider-bindings',\n () => this.appContainer.load(buildProviderModule()),\n );\n\n // Load all app modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const [index, module] of options.appBindings.entries()) {\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n `app-binding-${index}`,\n () => this.appContainer.load(module),\n );\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n const overrides = options.appOverrides;\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'app-overrides',\n () => this.appContainer.load(overrides),\n );\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * Route registration resolves the controller and its dependency graph eagerly, before requests.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n this.startupTiming.run(RuntimeStartupPhase.ROUTES, `${api.name}:${controller.name}`, () =>\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder),\n );\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * LogApiFilter + AuthFilter).\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 this.apiClientFactory.createApiClient(apiPrototype);\n }\n\n /** Transport-only credential bridge; ordinary endpoint filters remain mandatory. */\n // webpieces-disable no-any-unknown -- abstract API constructor signature\n createInvocationApiClient<T>(\n apiPrototype: abstract new (...args: any[]) => T,\n authentication: InvocationAuthentication,\n ): T {\n return this.apiClientFactory.createInvocationApiClient(apiPrototype, authentication);\n }\n\n /**\n * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via\n * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the\n * internal RouteBuilder never leaves.\n */\n apiClients(): ApiClient[] {\n return this.apiClientFactory.apiClients();\n }\n\n authorizationService(): AuthorizationService {\n return this.appContainer.get(AuthorizationService);\n }\n\n /** The application DI container (child of the framework container). */\n getContainer(): Container {\n return this.appContainer;\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(options: WebpiecesRouterOptions): 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 timing = new RuntimeStartupTiming(options.startupObserver);\n const webpiecesContainer = new Container();\n webpiecesContainer\n .bind(WEBPIECES_CONFIG_TOKEN)\n .toConstantValue(options.config ?? new WebpiecesConfig());\n await timing.runAsync(RuntimeStartupPhase.FRAMEWORK_DI, 'platform-framework-bindings', () =>\n webpiecesContainer.load(buildFrameworkModule()),\n );\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = timing.run(RuntimeStartupPhase.FRAMEWORK_DI, 'router-resolution', () =>\n webpiecesContainer.get(WebpiecesRouter),\n );\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -32,3 +32,5 @@ export { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZAT
|
|
|
32
32
|
export { AuthorizedApiDocument } from './AuthorizedApiDocument';
|
|
33
33
|
export type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';
|
|
34
34
|
export { WiringPolicy } from '@webpieces/http-client-core';
|
|
35
|
+
export { RuntimeStartupPhase, RuntimeStartupOperation } from './RuntimeStartupTiming';
|
|
36
|
+
export type { RuntimeStartupObserver, RuntimeStartupSpan } from './RuntimeStartupTiming';
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WRITE_IDEMPOTENT = exports.READ = exports.verbCarriesBody = exports.DELETE = exports.PATCH = exports.PUT = exports.POST = exports.GET = exports.METADATA_KEYS = exports.HttpContractMapper = exports.RouteMetadataFactory = exports.RouteMetadata = exports.AuthMeta = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isRawBody = exports.isFormPost = exports.getHttpParameterDeclarations = exports.getEndpointOperation = exports.getEndpointHttpMethod = exports.getEndpointOptions = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.getAuthorization = exports.AuthorizationType = exports.WpAuthorization = exports.apiKey = exports.vendorAuth = exports.sharedSecret = exports.oidcSvcs = exports.oidcSameProject = exports.jwt = exports.isLocalOnly = exports.WpLocalOnly = exports.WpAuth = exports.WpAuthPublic = exports.QueryParams = exports.QueryString = exports.QueryParam = exports.PathParam = exports.Endpoint = exports.ApiPath = void 0;
|
|
4
4
|
exports.DefaultOidcVerifier = exports.UnverifiableAuthModeError = exports.VENDOR_AUTH_VERIFIER = exports.VendorAuthVerifier = exports.API_KEY_HOOK = exports.ApiKeyHook = exports.OIDC_HOOK = exports.OidcHook = exports.JWT_HOOK = exports.MintedJwt = exports.JwtHook = exports.SharedSecrets = exports.AUTHENTICATED_CALLER_KEY = exports.AuthenticatedMachineIdentity = exports.AuthenticatedCaller = exports.AUTH_CONFIG = exports.AuthConfig = exports.ApiClient = exports.PubSubBindOptions = exports.ClientBindOptions = exports.ContainerBinder = exports.NodeWiringModules = exports.FilterMatcher = exports.LogApiFilter = exports.RouteHandler = exports.MethodMeta = exports.InvocationAuthentication = exports.AuthenticatedCallerContext = exports.WpResponse = exports.RawRequest = exports.HttpRequest = exports.FilterDefinition = exports.RouteDefinition = exports.ApiRoutingFactory = exports.buildFrameworkModule = exports.provideFrameworkSingletonDefaultForApi = exports.provideFrameworkSingleton = exports.provideSingletonDefaultForApi = exports.ROUTING_METADATA_KEYS = exports.SourceFile = exports.isDocumentDesign = exports.DocumentDesign = exports.ExternalContextKeys = exports.EXTERNAL_OUTBOUND = exports.EXTERNAL_INBOUND = exports.CRON = exports.CLOUDTASKS = exports.RPC = exports.NONE = exports.WRITE = void 0;
|
|
5
|
-
exports.WiringPolicy = exports.AuthorizedApiDocument = exports.VerifiedMachineCaller = exports.VERIFIED_MACHINE_CALLER = exports.AUTHORIZATION_HOOK = exports.CanonicalUserRoles = exports.AuthorizationService = exports.AuthorizationHook = exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RuntimeSetupOptions = exports.setupRuntime = exports.WebpiecesRouterFactory = exports.WebpiecesRouter = exports.DefaultJwtMintRequest = exports.DefaultJwtHook = void 0;
|
|
5
|
+
exports.RuntimeStartupOperation = exports.RuntimeStartupPhase = exports.WiringPolicy = exports.AuthorizedApiDocument = exports.VerifiedMachineCaller = exports.VERIFIED_MACHINE_CALLER = exports.AUTHORIZATION_HOOK = exports.CanonicalUserRoles = exports.AuthorizationService = exports.AuthorizationHook = exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RuntimeSetupOptions = exports.setupRuntime = exports.WebpiecesRouterFactory = exports.WebpiecesRouter = exports.DefaultJwtMintRequest = exports.DefaultJwtHook = void 0;
|
|
6
6
|
// Re-export API decorators from core-util for convenience
|
|
7
7
|
var core_util_1 = require("@webpieces/core-util");
|
|
8
8
|
Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return core_util_1.ApiPath; } });
|
|
@@ -180,4 +180,7 @@ var AuthorizedApiDocument_1 = require("./AuthorizedApiDocument");
|
|
|
180
180
|
Object.defineProperty(exports, "AuthorizedApiDocument", { enumerable: true, get: function () { return AuthorizedApiDocument_1.AuthorizedApiDocument; } });
|
|
181
181
|
var http_client_core_1 = require("@webpieces/http-client-core");
|
|
182
182
|
Object.defineProperty(exports, "WiringPolicy", { enumerable: true, get: function () { return http_client_core_1.WiringPolicy; } });
|
|
183
|
+
var RuntimeStartupTiming_1 = require("./RuntimeStartupTiming");
|
|
184
|
+
Object.defineProperty(exports, "RuntimeStartupPhase", { enumerable: true, get: function () { return RuntimeStartupTiming_1.RuntimeStartupPhase; } });
|
|
185
|
+
Object.defineProperty(exports, "RuntimeStartupOperation", { enumerable: true, get: function () { return RuntimeStartupTiming_1.RuntimeStartupOperation; } });
|
|
183
186
|
//# sourceMappingURL=index.js.map
|
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":"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","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n PathParam,\n QueryParam,\n QueryString,\n QueryParams,\n WpAuthPublic,\n WpAuth, WpLocalOnly, isLocalOnly, jwt, oidcSameProject, oidcSvcs, sharedSecret, vendorAuth, apiKey,\n WpAuthorization, AuthorizationType, getAuthorization,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getEndpointHttpMethod,\n getEndpointOperation,\n getHttpParameterDeclarations,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n RouteMetadataFactory,\n HttpContractMapper,\n METADATA_KEYS,\n ValidateImplementation,\n GET,\n POST,\n PUT,\n PATCH,\n DELETE,\n verbCarriesBody,\n READ,\n WRITE_IDEMPOTENT,\n WRITE,\n NONE,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n ExternalContextKeys,\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 {\n AuthMode,\n ApiKind,\n HttpMethod,\n EndpointOperation,\n EndpointKind,\n EndpointOptions,\n EndpointResponseType,\n} from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport { SourceFile, ROUTING_METADATA_KEYS } from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingletonDefaultForApi } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonDefaultForApi,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport { Routes, RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\n\n// The transport-neutral request type (defined in core-context; this is http-routing's\n// public request — a transport adapter builds one and the chain reads it from RequestContext).\nexport { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n\n// The INBOUND chain's response type. `Filter`, `Service` and `FilterChain` are NOT re-exported\n// here: they moved to @webpieces/core-util so the outbound client chain is the SAME abstraction\n// rather than a second spelling of it. Import them from '@webpieces/core-util'.\nexport { WpResponse } from './WpResponse';\nexport { AuthenticatedCallerContext } from './AuthenticatedCallerContext';\nexport { InvocationAuthentication } from './InvocationAuthentication';\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// LogApiFilter: the fixed OUTERMOST framework filter (auto-installed at 1,000,000 above\n// AuthFilter). Exported for reference/testing only — apps must NOT install it themselves.\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// RouteBuilderImpl (the route table + chain composer) is now INTERNAL — it is never\n// handed to upper layers. The express layer consumes ApiFactory.apiClients() instead.\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// The app's server-surface declaration: DI bind modules + route groups + headers.\nexport type { Wiring, AppWiring, BindModule, RouteModule } from './Wiring';\nexport { NodeWiringModules } from './Wiring';\n// The Node binder every BindModule.configure receives.\nexport type { Binder } from './Binder';\nexport { ContainerBinder, ClientBindOptions, PubSubBindOptions } from './Binder';\n\n// The public API-surface abstraction: declare routes/filters, get them back as ApiClient[].\nexport { ApiFactory } from './ApiFactory';\nexport { ApiClient, ApiClientProxy } from './ApiClient';\n\n// Auth: the app-provided, container-bound pieces the framework AuthFilter injects.\n// - AuthConfig: shared-secret STATE (sharedSecret(...) values).\n// - JwtHook / OidcHook / VendorAuthVerifier / ApiKeyHook: OPTIONAL verification mechanisms\n// (bind only what your served routes declare; a served route declaring a mode whose piece is\n// unbound FAILS STARTUP with UnverifiableAuthModeError naming the binding, never open — #1362).\n// - DefaultOidcVerifier: the built-in Google OIDC verifier used when no OidcHook is bound.\nexport {\n AuthConfig,\n AUTH_CONFIG,\n AuthenticatedCaller,\n AuthenticatedMachineIdentity,\n AUTHENTICATED_CALLER_KEY,\n SharedSecrets,\n} from './AuthConfig';\nexport {\n JwtHook,\n MintedJwt,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n ApiKeyHook,\n API_KEY_HOOK,\n} from './AuthHooks';\n// vendorAuth(name), INBOUND role: the verifier this server's AuthFilter calls. Defined in\n// @webpieces/core-context beside its OUTBOUND twin VendorAuthMinter (re-exported by\n// @webpieces/http-client-node); the two are independent DI roles, never one binding (#1358).\nexport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';\nexport { UnverifiableAuthModeError } from './UnverifiableAuthModeError';\nexport { DefaultOidcVerifier } from './DefaultOidcVerifier';\n// DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.\nexport { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';\n\n// Above-boundary context setup shared by every transport adapter.\n\n// Node-only router (the express-free heart: container + filter chain + in-process client)\nexport { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';\n\n// The ONE transport-free startup sequence (headers → logging → router → routes) → ApiFactory.\n// Reusable by any company/app and any framework adapter; a company wraps it with its own headers.\nexport { setupRuntime, RuntimeSetupOptions } from './setupRuntime';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n\n\nexport { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from './AuthorizationHook';\nexport { AuthorizedApiDocument } from './AuthorizedApiDocument';\nexport type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';\n\nexport { WiringPolicy } from '@webpieces/http-client-core';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n PathParam,\n QueryParam,\n QueryString,\n QueryParams,\n WpAuthPublic,\n WpAuth, WpLocalOnly, isLocalOnly, jwt, oidcSameProject, oidcSvcs, sharedSecret, vendorAuth, apiKey,\n WpAuthorization, AuthorizationType, getAuthorization,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getEndpointHttpMethod,\n getEndpointOperation,\n getHttpParameterDeclarations,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n RouteMetadataFactory,\n HttpContractMapper,\n METADATA_KEYS,\n ValidateImplementation,\n GET,\n POST,\n PUT,\n PATCH,\n DELETE,\n verbCarriesBody,\n READ,\n WRITE_IDEMPOTENT,\n WRITE,\n NONE,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n ExternalContextKeys,\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 {\n AuthMode,\n ApiKind,\n HttpMethod,\n EndpointOperation,\n EndpointKind,\n EndpointOptions,\n EndpointResponseType,\n} from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport { SourceFile, ROUTING_METADATA_KEYS } from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingletonDefaultForApi } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonDefaultForApi,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport { Routes, RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\n\n// The transport-neutral request type (defined in core-context; this is http-routing's\n// public request — a transport adapter builds one and the chain reads it from RequestContext).\nexport { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n\n// The INBOUND chain's response type. `Filter`, `Service` and `FilterChain` are NOT re-exported\n// here: they moved to @webpieces/core-util so the outbound client chain is the SAME abstraction\n// rather than a second spelling of it. Import them from '@webpieces/core-util'.\nexport { WpResponse } from './WpResponse';\nexport { AuthenticatedCallerContext } from './AuthenticatedCallerContext';\nexport { InvocationAuthentication } from './InvocationAuthentication';\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// LogApiFilter: the fixed OUTERMOST framework filter (auto-installed at 1,000,000 above\n// AuthFilter). Exported for reference/testing only — apps must NOT install it themselves.\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// RouteBuilderImpl (the route table + chain composer) is now INTERNAL — it is never\n// handed to upper layers. The express layer consumes ApiFactory.apiClients() instead.\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// The app's server-surface declaration: DI bind modules + route groups + headers.\nexport type { Wiring, AppWiring, BindModule, RouteModule } from './Wiring';\nexport { NodeWiringModules } from './Wiring';\n// The Node binder every BindModule.configure receives.\nexport type { Binder } from './Binder';\nexport { ContainerBinder, ClientBindOptions, PubSubBindOptions } from './Binder';\n\n// The public API-surface abstraction: declare routes/filters, get them back as ApiClient[].\nexport { ApiFactory } from './ApiFactory';\nexport { ApiClient, ApiClientProxy } from './ApiClient';\n\n// Auth: the app-provided, container-bound pieces the framework AuthFilter injects.\n// - AuthConfig: shared-secret STATE (sharedSecret(...) values).\n// - JwtHook / OidcHook / VendorAuthVerifier / ApiKeyHook: OPTIONAL verification mechanisms\n// (bind only what your served routes declare; a served route declaring a mode whose piece is\n// unbound FAILS STARTUP with UnverifiableAuthModeError naming the binding, never open — #1362).\n// - DefaultOidcVerifier: the built-in Google OIDC verifier used when no OidcHook is bound.\nexport {\n AuthConfig,\n AUTH_CONFIG,\n AuthenticatedCaller,\n AuthenticatedMachineIdentity,\n AUTHENTICATED_CALLER_KEY,\n SharedSecrets,\n} from './AuthConfig';\nexport {\n JwtHook,\n MintedJwt,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n ApiKeyHook,\n API_KEY_HOOK,\n} from './AuthHooks';\n// vendorAuth(name), INBOUND role: the verifier this server's AuthFilter calls. Defined in\n// @webpieces/core-context beside its OUTBOUND twin VendorAuthMinter (re-exported by\n// @webpieces/http-client-node); the two are independent DI roles, never one binding (#1358).\nexport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';\nexport { UnverifiableAuthModeError } from './UnverifiableAuthModeError';\nexport { DefaultOidcVerifier } from './DefaultOidcVerifier';\n// DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.\nexport { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';\n\n// Above-boundary context setup shared by every transport adapter.\n\n// Node-only router (the express-free heart: container + filter chain + in-process client)\nexport { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';\n\n// The ONE transport-free startup sequence (headers → logging → router → routes) → ApiFactory.\n// Reusable by any company/app and any framework adapter; a company wraps it with its own headers.\nexport { setupRuntime, RuntimeSetupOptions } from './setupRuntime';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n\n\nexport { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from './AuthorizationHook';\nexport { AuthorizedApiDocument } from './AuthorizedApiDocument';\nexport type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';\n\nexport { WiringPolicy } from '@webpieces/http-client-core';\n\nexport { RuntimeStartupPhase, RuntimeStartupOperation } from './RuntimeStartupTiming';\nexport type { RuntimeStartupObserver, RuntimeStartupSpan } from './RuntimeStartupTiming';\n"]}
|
package/src/setupRuntime.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Locality, LoggerFactory } from '@webpieces/core-util';
|
|
|
3
3
|
import { WebpiecesConfig } from './WebpiecesConfig';
|
|
4
4
|
import { AppWiring } from './Wiring';
|
|
5
5
|
import { ApiFactory } from './ApiFactory';
|
|
6
|
+
import { RuntimeStartupObserver } from './RuntimeStartupTiming';
|
|
6
7
|
/**
|
|
7
8
|
* RuntimeSetupOptions - the environment/wiring inputs to {@link setupRuntime} (everything NOT
|
|
8
9
|
* declared by the app's {@link AppWiring}): the logging backend, whether to include the platform
|
|
@@ -38,6 +39,8 @@ export declare class RuntimeSetupOptions {
|
|
|
38
39
|
readonly platformHeaders: boolean;
|
|
39
40
|
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
40
41
|
readonly config?: WebpiecesConfig | undefined;
|
|
42
|
+
/** Optional startup span observer; the host owns its clock and bounded output. */
|
|
43
|
+
readonly startupObserver?: RuntimeStartupObserver | undefined;
|
|
41
44
|
constructor(
|
|
42
45
|
/** This service's name — published to ServiceInfo. Names every log line and stamps
|
|
43
46
|
* `requestIdSource` on request-ids this service mints. Must be non-blank. */
|
|
@@ -62,7 +65,9 @@ export declare class RuntimeSetupOptions {
|
|
|
62
65
|
/** Include the webpieces platform default headers. */
|
|
63
66
|
platformHeaders?: boolean,
|
|
64
67
|
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
65
|
-
config?: WebpiecesConfig | undefined
|
|
68
|
+
config?: WebpiecesConfig | undefined,
|
|
69
|
+
/** Optional startup span observer; the host owns its clock and bounded output. */
|
|
70
|
+
startupObserver?: RuntimeStartupObserver | undefined);
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
68
73
|
* setupRuntime - the ONE canonical, TRANSPORT-FREE startup sequence, reusable by any company/app
|
package/src/setupRuntime.js
CHANGED
|
@@ -8,6 +8,7 @@ const WebpiecesConfig_1 = require("./WebpiecesConfig");
|
|
|
8
8
|
const WebpiecesRouter_1 = require("./WebpiecesRouter");
|
|
9
9
|
const Wiring_1 = require("./Wiring");
|
|
10
10
|
const Binder_1 = require("./Binder");
|
|
11
|
+
const RuntimeStartupTiming_1 = require("./RuntimeStartupTiming");
|
|
11
12
|
/**
|
|
12
13
|
* RuntimeSetupOptions - the environment/wiring inputs to {@link setupRuntime} (everything NOT
|
|
13
14
|
* declared by the app's {@link AppWiring}): the logging backend, whether to include the platform
|
|
@@ -25,6 +26,7 @@ class RuntimeSetupOptions {
|
|
|
25
26
|
loggerFactory;
|
|
26
27
|
platformHeaders;
|
|
27
28
|
config;
|
|
29
|
+
startupObserver;
|
|
28
30
|
constructor(
|
|
29
31
|
/** This service's name — published to ServiceInfo. Names every log line and stamps
|
|
30
32
|
* `requestIdSource` on request-ids this service mints. Must be non-blank. */
|
|
@@ -49,13 +51,16 @@ class RuntimeSetupOptions {
|
|
|
49
51
|
/** Include the webpieces platform default headers. */
|
|
50
52
|
platformHeaders = true,
|
|
51
53
|
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
52
|
-
config
|
|
54
|
+
config,
|
|
55
|
+
/** Optional startup span observer; the host owns its clock and bounded output. */
|
|
56
|
+
startupObserver) {
|
|
53
57
|
this.svcName = svcName;
|
|
54
58
|
this.svcVersion = svcVersion;
|
|
55
59
|
this.locality = locality;
|
|
56
60
|
this.loggerFactory = loggerFactory;
|
|
57
61
|
this.platformHeaders = platformHeaders;
|
|
58
62
|
this.config = config;
|
|
63
|
+
this.startupObserver = startupObserver;
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
exports.RuntimeSetupOptions = RuntimeSetupOptions;
|
|
@@ -83,25 +88,27 @@ appOverrides) {
|
|
|
83
88
|
// that cannot say which build emitted them. Reads of ServiceInfo elsewhere never throw (a missing
|
|
84
89
|
// log field must never 500 live traffic); the "say which build you are" guarantee lives HERE, the
|
|
85
90
|
// one startup every server runs.
|
|
91
|
+
const timing = new RuntimeStartupTiming_1.RuntimeStartupTiming(options.startupObserver);
|
|
86
92
|
core_util_1.ServiceInfo.setInfo(options.svcName, options.svcVersion);
|
|
87
93
|
// 0b. Declare WHERE we run, before any route is built: ApiRoutingFactory reads it in step 4 to
|
|
88
94
|
// decide whether @WpLocalOnly routes are registered at all. Undeclared reads as DEPLOYED, so
|
|
89
95
|
// this call is what lets a local-only endpoint exist — never what hides one.
|
|
90
96
|
core_util_1.RuntimeLocality.declare(options.locality);
|
|
91
97
|
// 1. Register the global HeaderRegistry FIRST (this service's own keys come from AppWiring).
|
|
92
|
-
core_util_1.HeaderRegistry.configure(appModules.getHeaders(), options.platformHeaders);
|
|
98
|
+
timing.run(RuntimeStartupTiming_1.RuntimeStartupPhase.HEADERS, 'HeaderRegistry', () => core_util_1.HeaderRegistry.configure(appModules.getHeaders(), options.platformHeaders));
|
|
93
99
|
// 2. Install the logging backend ONCE, before anything else logs.
|
|
94
|
-
core_util_1.LogManager.setFactory(options.loggerFactory);
|
|
100
|
+
timing.run(RuntimeStartupTiming_1.RuntimeStartupPhase.LOGGING, 'LogManager', () => core_util_1.LogManager.setFactory(options.loggerFactory));
|
|
95
101
|
// 3. Build the node-only router + DI container.
|
|
96
|
-
const modules = new Wiring_1.NodeWiringModules(appModules);
|
|
97
|
-
const router = await WebpiecesRouter_1.WebpiecesRouterFactory.create({
|
|
98
|
-
appBindings: modules.bindModules.map((module) => new inversify_1.ContainerModule((load) => module.configure(new Binder_1.ContainerBinder(load)))),
|
|
102
|
+
const modules = timing.run(RuntimeStartupTiming_1.RuntimeStartupPhase.WIRING, appModules.constructor.name, () => new Wiring_1.NodeWiringModules(appModules));
|
|
103
|
+
const router = await timing.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.CONTAINER, 'WebpiecesRouter', () => WebpiecesRouter_1.WebpiecesRouterFactory.create({
|
|
104
|
+
appBindings: modules.bindModules.map((module) => new inversify_1.ContainerModule((load) => timing.runAsync(RuntimeStartupTiming_1.RuntimeStartupPhase.BIND_MODULE, module.constructor.name, () => module.configure(new Binder_1.ContainerBinder(load))))),
|
|
99
105
|
appOverrides: appOverrides,
|
|
100
106
|
config: options.config ?? new WebpiecesConfig_1.WebpiecesConfig(),
|
|
101
|
-
|
|
107
|
+
startupObserver: options.startupObserver,
|
|
108
|
+
}));
|
|
102
109
|
// 4. Let each route group declare its routes + filters, then hand back the consumer surface.
|
|
103
110
|
for (const routeModule of modules.routeModules) {
|
|
104
|
-
routeModule.configure(router);
|
|
111
|
+
timing.run(RuntimeStartupTiming_1.RuntimeStartupPhase.ROUTE_MODULE, routeModule.constructor.name, () => routeModule.configure(router));
|
|
105
112
|
}
|
|
106
113
|
return router;
|
|
107
114
|
}
|
package/src/setupRuntime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupRuntime.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/setupRuntime.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"setupRuntime.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/setupRuntime.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContainerModule, ContainerModuleLoadOptions } from 'inversify';\nimport {\n HeaderRegistry,\n Locality,\n LoggerFactory,\n LogManager,\n RuntimeLocality,\n ServiceInfo,\n} from '@webpieces/core-util';\nimport { WebpiecesConfig } from './WebpiecesConfig';\nimport { WebpiecesRouterFactory } from './WebpiecesRouter';\nimport { AppWiring, BindModule, NodeWiringModules } from './Wiring';\nimport { ContainerBinder } from './Binder';\nimport { ApiFactory } from './ApiFactory';\nimport {\n RuntimeStartupObserver,\n RuntimeStartupPhase,\n RuntimeStartupTiming,\n} from './RuntimeStartupTiming';\n\n/**\n * RuntimeSetupOptions - the environment/wiring inputs to {@link setupRuntime} (everything NOT\n * declared by the app's {@link AppWiring}): the logging backend, whether to include the platform\n * default headers, and config. Data-only structure (a class, per the webpieces guidelines). The\n * app's own bind modules + route groups + headers come from the AppWiring passed alongside;\n * the test-override module is the separate `appOverrides` param of {@link setupRuntime}.\n *\n * Headers: {@link HeaderRegistry.configure} registers the platform defaults (when\n * `platformHeaders` is true) plus AppWiring.getHeaders() (by convention the company-wide set).\n */\nexport class RuntimeSetupOptions {\n constructor(\n /** This service's name — published to ServiceInfo. Names every log line and stamps\n * `requestIdSource` on request-ids this service mints. Must be non-blank. */\n public readonly svcName: string,\n /** This build's version — published to ServiceInfo alongside svcName. Opaque to webpieces\n * (a git SHA, a semver tag, a CI build number); it just has to identify THIS build so a log\n * line can say which one emitted it. Must be non-blank. */\n public readonly svcVersion: string,\n /**\n * WHERE this process runs — `'local'` (a developer's machine) or `'deployed'` (everything\n * else). Published to {@link RuntimeLocality}; the ONE input to `@WpLocalOnly` enforcement.\n *\n * REQUIRED and POSITIONAL on purpose, exactly like `@Endpoint(path, kind)`: only the app\n * knows how its platform is detected (Cloud Run's `K_SERVICE`, an ECS metadata URL, the\n * absence of both), the framework must not guess, and a defaulted field would mean \"forgot\n * to say\" and \"said deployed\" are the same line of code. Derive it at your startup, e.g.\n * `getServiceName() === 'local' ? 'local' : 'deployed'`.\n */\n public readonly locality: Locality,\n /** Logging backend to install (LogManager.setFactory). */\n public readonly loggerFactory: LoggerFactory,\n /** Include the webpieces platform default headers. */\n public readonly platformHeaders: boolean = true,\n /** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */\n public readonly config?: WebpiecesConfig,\n /** Optional startup span observer; the host owns its clock and bounded output. */\n public readonly startupObserver?: RuntimeStartupObserver,\n ) {}\n}\n\n/**\n * setupRuntime - the ONE canonical, TRANSPORT-FREE startup sequence, reusable by any company/app\n * AND any framework adapter (express, fastify, a serverless handler, ...). It runs, in the correct\n * fail-fast order:\n *\n * 1. HeaderRegistry.configure (filters read it at construction; logging masks off it)\n * 2. LogManager.setFactory (fails fast unless the registry is configured first)\n * 3. build the router + DI container (from appModules.getBindModules(), each handed a Binder)\n * 4. configure each appModules.getRouteModules() onto the router (addRoutes/addFilter)\n *\n * and returns the built {@link ApiFactory} — `apiClients()` for a transport to bind, or\n * `createApiClient()` for in-process tests. There is NO express (or any transport) here; a\n * transport adapter (e.g. WebpiecesExpressRouter in @webpieces/http-server) serves the result.\n */\nexport async function setupRuntime(\n options: RuntimeSetupOptions,\n appModules: AppWiring,\n /** A single DI module loaded LAST so tests can rebind bindings to mocks.\n * Or special case servers that want to override specific things */\n appOverrides?: ContainerModule,\n): Promise<ApiFactory> {\n // 0. IDENTIFY this service (name + build version) FIRST, before anything logs. Name and version\n // are REQUIRED inputs to this call, so a build cannot boot anonymously — setInfo throws on a\n // blank value, which kills the deploy (the revision never goes healthy) rather than shipping logs\n // that cannot say which build emitted them. Reads of ServiceInfo elsewhere never throw (a missing\n // log field must never 500 live traffic); the \"say which build you are\" guarantee lives HERE, the\n // one startup every server runs.\n const timing = new RuntimeStartupTiming(options.startupObserver);\n ServiceInfo.setInfo(options.svcName, options.svcVersion);\n\n // 0b. Declare WHERE we run, before any route is built: ApiRoutingFactory reads it in step 4 to\n // decide whether @WpLocalOnly routes are registered at all. Undeclared reads as DEPLOYED, so\n // this call is what lets a local-only endpoint exist — never what hides one.\n RuntimeLocality.declare(options.locality);\n\n // 1. Register the global HeaderRegistry FIRST (this service's own keys come from AppWiring).\n timing.run(RuntimeStartupPhase.HEADERS, 'HeaderRegistry', () =>\n HeaderRegistry.configure(appModules.getHeaders(), options.platformHeaders),\n );\n\n // 2. Install the logging backend ONCE, before anything else logs.\n timing.run(RuntimeStartupPhase.LOGGING, 'LogManager', () =>\n LogManager.setFactory(options.loggerFactory),\n );\n\n // 3. Build the node-only router + DI container.\n const modules = timing.run(\n RuntimeStartupPhase.WIRING,\n appModules.constructor.name,\n () => new NodeWiringModules(appModules),\n );\n const router = await timing.runAsync(RuntimeStartupPhase.CONTAINER, 'WebpiecesRouter', () =>\n WebpiecesRouterFactory.create({\n appBindings: modules.bindModules.map(\n (module: BindModule) =>\n new ContainerModule((load: ContainerModuleLoadOptions) =>\n timing.runAsync(\n RuntimeStartupPhase.BIND_MODULE,\n module.constructor.name,\n () => module.configure(new ContainerBinder(load)),\n ),\n ),\n ),\n appOverrides: appOverrides,\n config: options.config ?? new WebpiecesConfig(),\n startupObserver: options.startupObserver,\n }),\n );\n\n // 4. Let each route group declare its routes + filters, then hand back the consumer surface.\n for (const routeModule of modules.routeModules) {\n timing.run(RuntimeStartupPhase.ROUTE_MODULE, routeModule.constructor.name, () =>\n routeModule.configure(router),\n );\n }\n return router;\n}\n"]}
|