@webpieces/http-routing 0.3.302 → 0.3.303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-routing",
3
- "version": "0.3.302",
3
+ "version": "0.3.303",
4
4
  "description": "Decorator-based routing with auto-wiring for WebPieces",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@inversifyjs/binding-decorators": "1.1.5",
25
- "@webpieces/core-context": "0.3.302",
26
- "@webpieces/core-util": "0.3.302",
25
+ "@webpieces/core-context": "0.3.303",
26
+ "@webpieces/core-util": "0.3.303",
27
27
  "inversify": "7.10.4",
28
28
  "minimatch": "10.0.1"
29
29
  }
@@ -47,7 +47,13 @@ let ApiClientFactory = class ApiClientFactory {
47
47
  for (const route of this.routeBuilder.getRoutes()) {
48
48
  apis.add(route.definition.apiClass);
49
49
  }
50
- return [...apis].map((api) => new ApiClient_1.ApiClient(api, this.buildProxy(api)));
50
+ // apiClients() just loops createApiClient — the EXACT method tests call — so the platform
51
+ // (HTTP) and tests (in-process) bind the identical proxy, 1-to-1.
52
+ return [...apis].map((api) => {
53
+ // webpieces-disable no-any-unknown -- the registered api is an unconstrained ClassType
54
+ const client = this.createApiClient(api);
55
+ return new ApiClient_1.ApiClient(api, client);
56
+ });
51
57
  }
52
58
  /** Build the proxy record (method name → invoker) from the API prototype's decorators. */
53
59
  // webpieces-disable no-any-unknown -- accepts any ClassType / abstract-constructor API prototype
@@ -1 +1 @@
1
- {"version":3,"file":"ApiClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n getApiPath,\n getEndpoints,\n RouteMetadata,\n ContextMgr,\n} from '@webpieces/core-util';\nimport { provideFrameworkSingleton, RequestContext, HttpRequest, RequestContextReader } from '@webpieces/core-context';\nimport { MethodMeta } from './MethodMeta';\nimport { Service, WpResponse } from './Filter';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiClient, ApiClientProxy } from './ApiClient';\nimport { ClassType } from './ApiRoutingFactory';\nimport { fillContext } from './fillContext';\n\n/**\n * ApiClientFactory - THE piece that wires api → Proxy → filters → controller.\n *\n * For an API prototype (its @ApiPath/@Endpoint decorators) it builds a proxy whose methods\n * invoke the composed filter chain (via RouteBuilder.createRouteInvoker) — that proxy IS what\n * createApiClient() returns. {@link apiClients} reuses the SAME proxy per registered api, so the\n * express layer binds each method through it. There is no express dependency here, so the proxy\n * is the single invocation path for BOTH in-process (tests) and HTTP (the express adapter drives\n * the same proxy after publishing the request).\n *\n * @provideFrameworkSingleton so WebpiecesRouter can inject it (it shares the one RouteBuilder).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class ApiClientFactory {\n // Builds request headers the SAME way the real HTTP client does — from the ambient\n // RequestContext — so a credential a test put in context travels as a real request header.\n private readonly contextMgr = new ContextMgr(new RequestContextReader());\n\n constructor(@inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl) {}\n\n /**\n * Create an API client proxy (cast to the API interface T). The proxy's methods run the full\n * filter chain + controller; used by tests in-process AND driven by the express adapter.\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.buildProxy(apiPrototype) as T;\n }\n\n /**\n * Reify every registered API as an {@link ApiClient} — the contract + its proxy (the\n * createApiClient object). The transport reads the api's decorators to bind each endpoint to\n * the proxy's matching method, so no route metadata needs to leave here.\n */\n apiClients(): ApiClient[] {\n const apis = new Set<ClassType>();\n for (const route of this.routeBuilder.getRoutes()) {\n apis.add(route.definition.apiClass as ClassType);\n }\n return [...apis].map((api: ClassType) => new ApiClient(api, this.buildProxy(api)));\n }\n\n /** Build the proxy record (method name → invoker) from the API prototype's decorators. */\n // webpieces-disable no-any-unknown -- accepts any ClassType / abstract-constructor API prototype\n private buildProxy(apiPrototype: any): ApiClientProxy {\n const basePath = getApiPath(apiPrototype) || '';\n const endpoints = getEndpoints(apiPrototype) || {};\n const proxy: ApiClientProxy = {};\n\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const httpMethod = 'POST';\n const path = basePath + endpointPath;\n\n // Use the REGISTERED route's metadata — it carries the real controller name AND api\n // name (so logging/recording read the right one); createRouteInvoker composes its chain.\n const routeMeta = this.routeBuilder.getRouteMeta(httpMethod, path);\n if (!routeMeta) {\n throw new Error(\n `No registered route for ${apiPrototype.name}.${methodName} (${httpMethod} ${path}) — call addRoutes(api, controller) first.`,\n );\n }\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Auto-activate a RequestContext if the caller (a pure in-process test) did not.\n if (!RequestContext.isActive()) {\n return RequestContext.run(async () => this.runMethod(routeMeta, requestDto, service));\n }\n return this.runMethod(routeMeta, requestDto, service);\n };\n }\n\n return proxy;\n }\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>): Promise<unknown> {\n // Only synthesize the request when NONE was published by a transport. The express adapter\n // publishes the HttpRequest from `req` before calling the proxy, so its request wins; a\n // pure in-process call synthesizes one from the ambient context (client-like).\n if (!RequestContext.getRequest()) {\n const headers = new Map<string, string[]>();\n this.contextMgr.buildOutboundHeaders().forEach((value: string, name: string) => {\n headers.set(name.toLowerCase(), [value]);\n });\n RequestContext.setRequest(new HttpRequest(routeMeta.httpMethod, routeMeta.path, headers));\n fillContext();\n }\n\n const responseWrapper = await service.invoke(new MethodMeta(routeMeta, requestDto));\n return responseWrapper.response;\n }\n}\n"]}
1
+ {"version":3,"file":"ApiClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable } from 'inversify';\nimport {\n getApiPath,\n getEndpoints,\n RouteMetadata,\n ContextMgr,\n} from '@webpieces/core-util';\nimport { provideFrameworkSingleton, RequestContext, HttpRequest, RequestContextReader } from '@webpieces/core-context';\nimport { MethodMeta } from './MethodMeta';\nimport { Service, WpResponse } from './Filter';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiClient, ApiClientProxy } from './ApiClient';\nimport { ClassType } from './ApiRoutingFactory';\nimport { fillContext } from './fillContext';\n\n/**\n * ApiClientFactory - THE piece that wires api → Proxy → filters → controller.\n *\n * For an API prototype (its @ApiPath/@Endpoint decorators) it builds a proxy whose methods\n * invoke the composed filter chain (via RouteBuilder.createRouteInvoker) — that proxy IS what\n * createApiClient() returns. {@link apiClients} reuses the SAME proxy per registered api, so the\n * express layer binds each method through it. There is no express dependency here, so the proxy\n * is the single invocation path for BOTH in-process (tests) and HTTP (the express adapter drives\n * the same proxy after publishing the request).\n *\n * @provideFrameworkSingleton so WebpiecesRouter can inject it (it shares the one RouteBuilder).\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class ApiClientFactory {\n // Builds request headers the SAME way the real HTTP client does — from the ambient\n // RequestContext — so a credential a test put in context travels as a real request header.\n private readonly contextMgr = new ContextMgr(new RequestContextReader());\n\n constructor(@inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl) {}\n\n /**\n * Create an API client proxy (cast to the API interface T). The proxy's methods run the full\n * filter chain + controller; used by tests in-process AND driven by the express adapter.\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.buildProxy(apiPrototype) as T;\n }\n\n /**\n * Reify every registered API as an {@link ApiClient} — the contract + its proxy (the\n * createApiClient object). The transport reads the api's decorators to bind each endpoint to\n * the proxy's matching method, so no route metadata needs to leave here.\n */\n apiClients(): ApiClient[] {\n const apis = new Set<ClassType>();\n for (const route of this.routeBuilder.getRoutes()) {\n apis.add(route.definition.apiClass as ClassType);\n }\n // apiClients() just loops createApiClient — the EXACT method tests call — so the platform\n // (HTTP) and tests (in-process) bind the identical proxy, 1-to-1.\n return [...apis].map((api: ClassType) => {\n // webpieces-disable no-any-unknown -- the registered api is an unconstrained ClassType\n const client = this.createApiClient<ApiClientProxy>(api as any);\n return new ApiClient(api, client);\n });\n }\n\n /** Build the proxy record (method name → invoker) from the API prototype's decorators. */\n // webpieces-disable no-any-unknown -- accepts any ClassType / abstract-constructor API prototype\n private buildProxy(apiPrototype: any): ApiClientProxy {\n const basePath = getApiPath(apiPrototype) || '';\n const endpoints = getEndpoints(apiPrototype) || {};\n const proxy: ApiClientProxy = {};\n\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const httpMethod = 'POST';\n const path = basePath + endpointPath;\n\n // Use the REGISTERED route's metadata — it carries the real controller name AND api\n // name (so logging/recording read the right one); createRouteInvoker composes its chain.\n const routeMeta = this.routeBuilder.getRouteMeta(httpMethod, path);\n if (!routeMeta) {\n throw new Error(\n `No registered route for ${apiPrototype.name}.${methodName} (${httpMethod} ${path}) — call addRoutes(api, controller) first.`,\n );\n }\n const service = this.routeBuilder.createRouteInvoker(httpMethod, path);\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n proxy[methodName] = async (requestDto: unknown): Promise<unknown> => {\n // Auto-activate a RequestContext if the caller (a pure in-process test) did not.\n if (!RequestContext.isActive()) {\n return RequestContext.run(async () => this.runMethod(routeMeta, requestDto, service));\n }\n return this.runMethod(routeMeta, requestDto, service);\n };\n }\n\n return proxy;\n }\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n private async runMethod(routeMeta: RouteMetadata, requestDto: unknown, service: Service<MethodMeta, WpResponse<unknown>>): Promise<unknown> {\n // Only synthesize the request when NONE was published by a transport. The express adapter\n // publishes the HttpRequest from `req` before calling the proxy, so its request wins; a\n // pure in-process call synthesizes one from the ambient context (client-like).\n if (!RequestContext.getRequest()) {\n const headers = new Map<string, string[]>();\n this.contextMgr.buildOutboundHeaders().forEach((value: string, name: string) => {\n headers.set(name.toLowerCase(), [value]);\n });\n RequestContext.setRequest(new HttpRequest(routeMeta.httpMethod, routeMeta.path, headers));\n fillContext();\n }\n\n const responseWrapper = await service.invoke(new MethodMeta(routeMeta, requestDto));\n return responseWrapper.response;\n }\n}\n"]}
@@ -1,25 +1,20 @@
1
- import { Container } from 'inversify';
2
- import { ClassType } from './ApiRoutingFactory';
3
- import { FilterDefinition } from './WebAppMeta';
4
1
  import { ApiClient } from './ApiClient';
5
2
  /**
6
- * ApiFactory - the node-only, EXPRESS-FREE surface for declaring an app's API surface and
7
- * getting it back as data. It is the ONE abstraction upper layers use:
3
+ * ApiFactory - the node-only, EXPRESS-FREE CONSUMER surface of a built app. It is exactly the
4
+ * two things something downstream of the router needs, and nothing else (no RouteBuilder, no
5
+ * container):
8
6
  *
9
- * - {@link addRoutes} / {@link addFilter} declare the surface (api controller, + filters).
10
- * - {@link apiClients} returns each endpoint as an {@link ApiClient} (api + routeMeta +
11
- * composed impl). The express layer (WebpiecesExpressRouter) binds these; the internal
12
- * RouteBuilder is never exposed.
13
- * - {@link createApiClient} builds an in-process proxy (the primary test path, no HTTP).
14
- * - {@link getContainer} exposes the DI container for test rebinds.
7
+ * - {@link apiClients} the PLATFORM path: each registered api as an {@link ApiClient}
8
+ * (api + its client proxy). The express layer (WebpiecesExpressRouter) binds these.
9
+ * - {@link createApiClient} the TEST path: an in-process proxy for one api (no HTTP, no ports).
15
10
  *
16
- * Implemented by {@link WebpiecesRouter} (the node-only heart). Hand an ApiFactory to
17
- * WebpiecesExpressRouter in @webpieces/http-server to serve it over HTTP.
11
+ * apiClients() is literally a loop over createApiClient, so the two are 1-to-1: what the platform
12
+ * serves over HTTP is the exact same proxy a test drives in-process.
13
+ *
14
+ * Implemented by {@link WebpiecesRouter} (the node-only heart, which also owns the BUILD surface
15
+ * addRoutes/addFilter). Hand an ApiFactory to WebpiecesExpressRouter to serve it over HTTP.
18
16
  */
19
17
  export interface ApiFactory {
20
- addRoutes<TApi, TController extends TApi>(api: ClassType<TApi>, controller: ClassType<TController>): this;
21
- addFilter(filter: FilterDefinition): this;
22
18
  apiClients(): ApiClient[];
23
19
  createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
24
- getContainer(): Container;
25
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApiFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiFactory.ts"],"names":[],"mappings":"","sourcesContent":["import { Container } from 'inversify';\nimport { ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { ApiClient } from './ApiClient';\n\n/**\n * ApiFactory - the node-only, EXPRESS-FREE surface for declaring an app's API surface and\n * getting it back as data. It is the ONE abstraction upper layers use:\n *\n * - {@link addRoutes} / {@link addFilter} declare the surface (api → controller, + filters).\n * - {@link apiClients} returns each endpoint as an {@link ApiClient} (api + routeMeta +\n * composed impl). The express layer (WebpiecesExpressRouter) binds these; the internal\n * RouteBuilder is never exposed.\n * - {@link createApiClient} builds an in-process proxy (the primary test path, no HTTP).\n * - {@link getContainer} exposes the DI container for test rebinds.\n *\n * Implemented by {@link WebpiecesRouter} (the node-only heart). Hand an ApiFactory to\n * WebpiecesExpressRouter in @webpieces/http-server to serve it over HTTP.\n */\nexport interface ApiFactory {\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this;\n\n addFilter(filter: FilterDefinition): this;\n\n apiClients(): ApiClient[];\n\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;\n\n getContainer(): Container;\n}\n"]}
1
+ {"version":3,"file":"ApiFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiFactory.ts"],"names":[],"mappings":"","sourcesContent":["import { ApiClient } from './ApiClient';\n\n/**\n * ApiFactory - the node-only, EXPRESS-FREE CONSUMER surface of a built app. It is exactly the\n * two things something downstream of the router needs, and nothing else (no RouteBuilder, no\n * container):\n *\n * - {@link apiClients} the PLATFORM path: each registered api as an {@link ApiClient}\n * (api + its client proxy). The express layer (WebpiecesExpressRouter) binds these.\n * - {@link createApiClient} the TEST path: an in-process proxy for one api (no HTTP, no ports).\n *\n * apiClients() is literally a loop over createApiClient, so the two are 1-to-1: what the platform\n * serves over HTTP is the exact same proxy a test drives in-process.\n *\n * Implemented by {@link WebpiecesRouter} (the node-only heart, which also owns the BUILD surface\n * addRoutes/addFilter). Hand an ApiFactory to WebpiecesExpressRouter to serve it over HTTP.\n */\nexport interface ApiFactory {\n apiClients(): ApiClient[];\n\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;\n}\n"]}
@@ -87,9 +87,9 @@ export declare class WebpiecesRouter implements ApiFactory {
87
87
  */
88
88
  createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T;
89
89
  /**
90
- * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy +
91
- * per-endpoint route metadata) via the shared {@link ApiClientFactory}. This is the ONLY
92
- * handoff to the express layer — the internal RouteBuilder never leaves.
90
+ * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via
91
+ * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the
92
+ * internal RouteBuilder never leaves.
93
93
  */
94
94
  apiClients(): ApiClient[];
95
95
  /** The application DI container (child of the framework container). */
@@ -118,9 +118,9 @@ let WebpiecesRouter = class WebpiecesRouter {
118
118
  return this.apiClientFactory.createApiClient(apiPrototype);
119
119
  }
120
120
  /**
121
- * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy +
122
- * per-endpoint route metadata) via the shared {@link ApiClientFactory}. This is the ONLY
123
- * handoff to the express layer — the internal RouteBuilder never leaves.
121
+ * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via
122
+ * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the
123
+ * internal RouteBuilder never leaves.
124
124
  */
125
125
  apiClients() {
126
126
  return this.apiClientFactory.apiClients();
@@ -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 { 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 { ErrorLogFilter } from './filters/ErrorLogFilter';\nimport { AuthFilter } from './filters/AuthFilter';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create}.\n *\n * appBindings - REQUIRED DI ContainerModules to load (framework + app), e.g.\n * [WebpiecesModule, CompanyHeadersModule, AppModule]. Loaded after the\n * @provideSingleton auto-scan so they can add/override bindings.\n * appOverrides - A single ContainerModule loaded LAST so tests can rebind real\n * controllers/clients to mocks (see @webpieces/core-mock createMock()).\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n}\n\n/**\n * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter\n * chain + an in-process API client. It has NO express dependency, so it runs anywhere\n * node runs and is fully testable with zero HTTP.\n *\n * DI-resolved from the platform container (like the old WebpiecesServerImpl):\n * `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in\n * initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.\n *\n * Two-container pattern (mirrors Java WebPieces):\n * - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)\n * - appContainer : your controllers/filters/modules (a child of the framework one)\n *\n * Usage:\n * ```typescript\n * const router = await WebpiecesRouterFactory.create(new WebpiecesConfig(), {\n * appBindings: [WebpiecesModule, CompanyHeadersModule],\n * });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters\n * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)\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(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n this.appContainer = new Container({ parent: webpiecesContainer });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n 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): ErrorLogFilter outermost (log + let the transport 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.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, ErrorLogFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n }\n\n private async loadDIModules(options: WebpiecesRouterOptions): Promise<void> {\n // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's\n // own @provideSingleton classes (binding-decorators global). A client's\n // buildProviderModule() only ever contains the client's classes — never framework internals.\n await this.appContainer.load(buildFrameworkModule());\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of options.appBindings) {\n await this.appContainer.load(module);\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n await this.appContainer.load(options.appOverrides);\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * The controller is resolved from the container at request time.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder);\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * ErrorLogFilter + 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 /**\n * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy +\n * per-endpoint route metadata) via the shared {@link ApiClientFactory}. This is the ONLY\n * handoff to the express layer — the internal RouteBuilder never leaves.\n */\n apiClients(): ApiClient[] {\n return this.apiClientFactory.apiClients();\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(\n config: WebpiecesConfig,\n options: WebpiecesRouterOptions,\n ): Promise<WebpiecesRouter> {\n // Platform (framework) container — build via buildFrameworkModule so framework\n // singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,\n // NOT the client's global one.\n const webpiecesContainer = new Container();\n webpiecesContainer.bind(WEBPIECES_CONFIG_TOKEN).toConstantValue(config);\n await webpiecesContainer.load(buildFrameworkModule());\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = webpiecesContainer.get(WebpiecesRouter);\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { provideFrameworkSingleton, buildFrameworkModule } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { ErrorLogFilter } from './filters/ErrorLogFilter';\nimport { AuthFilter } from './filters/AuthFilter';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create}.\n *\n * appBindings - REQUIRED DI ContainerModules to load (framework + app), e.g.\n * [WebpiecesModule, CompanyHeadersModule, AppModule]. Loaded after the\n * @provideSingleton auto-scan so they can add/override bindings.\n * appOverrides - A single ContainerModule loaded LAST so tests can rebind real\n * controllers/clients to mocks (see @webpieces/core-mock createMock()).\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n}\n\n/**\n * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter\n * chain + an in-process API client. It has NO express dependency, so it runs anywhere\n * node runs and is fully testable with zero HTTP.\n *\n * DI-resolved from the platform container (like the old WebpiecesServerImpl):\n * `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in\n * initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.\n *\n * Two-container pattern (mirrors Java WebPieces):\n * - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)\n * - appContainer : your controllers/filters/modules (a child of the framework one)\n *\n * Usage:\n * ```typescript\n * const router = await WebpiecesRouterFactory.create(new WebpiecesConfig(), {\n * appBindings: [WebpiecesModule, CompanyHeadersModule],\n * });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters\n * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)\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(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n this.appContainer = new Container({ parent: webpiecesContainer });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n 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): ErrorLogFilter outermost (log + let the transport 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.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, ErrorLogFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n }\n\n private async loadDIModules(options: WebpiecesRouterOptions): Promise<void> {\n // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's\n // own @provideSingleton classes (binding-decorators global). A client's\n // buildProviderModule() only ever contains the client's classes — never framework internals.\n await this.appContainer.load(buildFrameworkModule());\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of options.appBindings) {\n await this.appContainer.load(module);\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n await this.appContainer.load(options.appOverrides);\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * The controller is resolved from the container at request time.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder);\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * ErrorLogFilter + 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 /**\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 /** 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(\n config: WebpiecesConfig,\n options: WebpiecesRouterOptions,\n ): Promise<WebpiecesRouter> {\n // Platform (framework) container — build via buildFrameworkModule so framework\n // singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,\n // NOT the client's global one.\n const webpiecesContainer = new Container();\n webpiecesContainer.bind(WEBPIECES_CONFIG_TOKEN).toConstantValue(config);\n await webpiecesContainer.load(buildFrameworkModule());\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = webpiecesContainer.get(WebpiecesRouter);\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}