@webpieces/http-routing 0.4.988 → 0.4.989

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 CHANGED
@@ -60,3 +60,86 @@ See the main [WebPieces README](https://github.com/deanhiller/webpieces-ts#readm
60
60
  Apache-2.0
61
61
 
62
62
  Authentication and operation authorization are separate declarations. See [the migration guide](../../../docs/authorization-migration.md) for policies, canonical context, token-free MCP invocation, and validator release ordering.
63
+
64
+ ## First-use controller implementations
65
+
66
+ Pass a `LazyController<Api>` as the second argument to the existing `router.addRoutes` call to
67
+ retain the API contract now and import its implementation on its first permitted invocation:
68
+
69
+ ```typescript
70
+ const admin = new LazyController<AdminApi>(
71
+ 'AdminController',
72
+ '**/AdminController.ts',
73
+ 10_000,
74
+ async () => {
75
+ const feature = await import('./admin/implementation');
76
+ return new LazyImplementation(
77
+ feature.AdminController,
78
+ [new feature.AdminBindings(config)],
79
+ [feature.AdminState],
80
+ [SharedAccountStore],
81
+ );
82
+ },
83
+ );
84
+ router.addRoutes(AdminApi, admin);
85
+ ```
86
+
87
+ Keep the descriptor's module lightweight: import contracts, prepared configuration and shared
88
+ identities there; import implementation classes and exclusive bindings INSIDE its loader. A static
89
+ implementation import, implementation barrel export or decorator `design:paramtypes` reference can
90
+ undo the boundary. A class passed directly to `addRoutes` retains eager registration.
91
+
92
+ Registration still expands API endpoints, validates their authentication and authorization, installs
93
+ normal filters, and publishes transport/tool metadata. The name and filter filepath are stable
94
+ metadata: the imported controller must retain that name, extend the API, implement every method,
95
+ and match explicit `@SourceFile` metadata. HTTP, in-process clients and local MCP dispatch reach the
96
+ same final handler. Denied calls do not import the implementation. A mixed API is one controller
97
+ feature: its first permitted method loads that controller, rather than independently loading each
98
+ method's JavaScript.
99
+
100
+ `LazyImplementation` declares four things: the concrete controller, binding modules, private concrete
101
+ singletons (excluding the controller, which is implicit), and explicitly shared application dependencies.
102
+ Private bindings live in an isolated container with autobinding disabled. Each shared dependency must
103
+ already be explicitly bound in the application; the bridge returns the application's instance and
104
+ preserves its scope instead of creating a second singleton. Bind shared services explicitly at startup
105
+ when no essential route would otherwise resolve them. Merely importing a class does not bind it.
106
+
107
+ An Inversify child with an autobinding parent is insufficient isolation: an `isBound` lookup can
108
+ create a binding in the parent. This implementation never uses such a child. Private `bind` calls
109
+ and private singleton declarations reject collisions with application bindings; a failed candidate
110
+ cannot partially rebind shared auth, logging or clients. RPC/task/external binding helpers inherit an
111
+ existing application edge binding (including test overrides) instead of replacing it. Declare shared
112
+ framework factories, such as `ClientHttpFactory`, when a private binding creates a client using them.
113
+
114
+ The descriptor has one initialization promise per router. Concurrent methods share it; successful
115
+ initialization reuses one controller; failure is retained until the router/process is rebuilt, with
116
+ no retry loop. One descriptor cannot register two different API contracts on the same router. The
117
+ same descriptor can be used by separate routers without sharing their controller or scoped state.
118
+ Import, binding and singleton construction run in a fresh detached `RequestContext`; each invocation
119
+ resumes in its own caller scope after initialization. Never capture a caller in the loader's closure.
120
+
121
+ The required positive timeout bounds asynchronous initialization waiting. A timeout retains failure
122
+ and prevents a late import/configuration from publishing a controller. JavaScript module evaluation
123
+ and synchronous constructors still run on the main thread: timers cannot preempt CPU work. Keep
124
+ loaders and binding modules declarative; they must not start request work, open background resources
125
+ or hide network warmups. This API adds no process signal/shutdown hooks. Host termination retains
126
+ existing behavior; pending imports are not a shutdown flush queue.
127
+
128
+ Existing transport cancellation and client deadlines remain transport-owned. The shared initialization
129
+ is not cancelled because one client stops waiting. Unary disconnects do not currently propagate a
130
+ cancellation signal into `MethodMeta`; this capability does not add one or promise that an already
131
+ accepted write is cancelled when its client disconnects. Initialization timeout is a separate limit.
132
+
133
+ Call `await router.validateLazyRoutesForBuild()` in a **separate build/integration process with fake
134
+ external providers** to import every registered optional implementation and validate its DI before
135
+ shipping. It invokes no API methods, refuses execution inside a request, and never bypasses subsequent
136
+ invocation auth. It warms that validation router, so never call it in the production entry point.
137
+ The production library excludes its `src/__tests__` tree, including the deferred-import fixtures.
138
+
139
+ Feature tests cover real imports, first/concurrent/later methods, distinct principals, shared state,
140
+ critical requests during a held import, retained import/binding/method failures, collision rejection,
141
+ late completion after timeout and explicit build validation. Separate loopback tests prove HTTP and
142
+ MCP discovery/auth/dispatch. No production speedup is claimed from these small fixtures: measure the
143
+ actual consuming image's startup, first optional request, later requests, essential requests during
144
+ initialization and memory separately. The selected Lang migration is
145
+ [ctoteachings/monorepo#3036](https://github.com/ctoteachings/monorepo/issues/3036).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-routing",
3
- "version": "0.4.988",
3
+ "version": "0.4.989",
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.988",
26
- "@webpieces/core-util": "0.4.988",
27
- "@webpieces/gcp-identity": "0.4.988",
25
+ "@webpieces/core-context": "0.4.989",
26
+ "@webpieces/core-util": "0.4.989",
27
+ "@webpieces/gcp-identity": "0.4.989",
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.988",
32
- "@webpieces/http-client-node": "0.4.988",
33
- "@webpieces/cloudtasks-client": "0.4.988"
31
+ "@webpieces/http-client-core": "0.4.989",
32
+ "@webpieces/http-client-node": "0.4.989",
33
+ "@webpieces/cloudtasks-client": "0.4.989"
34
34
  }
35
35
  }
@@ -1,4 +1,4 @@
1
- import { ClassType } from './ApiRoutingFactory';
1
+ import type { ClassType } from './ClassType';
2
2
  import { RouteMetadata } from '@webpieces/core-util';
3
3
  /**
4
4
  * ApiClientProxy - the in-process client createApiClient(api) returns: a map of method name →
@@ -1 +1 @@
1
- {"version":3,"file":"ApiClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClient.ts"],"names":[],"mappings":";;;AAUA;;;;;;;;;;GAUG;AACH,MAAa,SAAS;IAEE;IACA;IAEA;IAJpB,YACoB,GAAc,EACd,MAAsB;IACtC,+EAA+E;IAC/D,MAAgC;QAHhC,QAAG,GAAH,GAAG,CAAW;QACd,WAAM,GAAN,MAAM,CAAgB;QAEtB,WAAM,GAAN,MAAM,CAA0B;IACjD,CAAC;CACP;AAPD,8BAOC","sourcesContent":["import { ClassType } from './ApiRoutingFactory';\nimport { RouteMetadata } from '@webpieces/core-util';\n\n/**\n * ApiClientProxy - the in-process client createApiClient(api) returns: a map of method name →\n * invoker(dto) that runs the filter chain → controller. (Cast to the API interface T for callers.)\n */\n// webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes; DTOs are erased\nexport type ApiClientProxy = Record<string, (...args: unknown[]) => Promise<unknown>>;\n\n/**\n * ApiClient - one registered API surface, reified by {@link ApiClientFactory}:\n * - `api` : the contract class passed to addRoutes,\n * - `client` : exactly what createApiClient(api) returns — a proxy whose methods run the\n * filter chain → controller.\n *\n * That is ALL a transport needs: the express layer reads the api's @ApiPath/@Endpoint decorators\n * to bind each method's HTTP route to the matching `client` method — one-to-one with a test call\n * `client.method(dto)`. The proxy forms the RouteMetadata and drives it through the filters, so\n * the internal RouteBuilder never leaks out. Data-only structure (a class, per the guidelines).\n */\nexport class ApiClient {\n constructor(\n public readonly api: ClassType,\n public readonly client: ApiClientProxy,\n /** The exact registered routes this transport must expose for the contract. */\n public readonly routes: readonly RouteMetadata[],\n ) {}\n}\n"]}
1
+ {"version":3,"file":"ApiClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClient.ts"],"names":[],"mappings":";;;AAWA;;;;;;;;;;GAUG;AACH,MAAa,SAAS;IAEE;IACA;IAEA;IAJpB,YACoB,GAAc,EACd,MAAsB;IACtC,+EAA+E;IAC/D,MAAgC;QAHhC,QAAG,GAAH,GAAG,CAAW;QACd,WAAM,GAAN,MAAM,CAAgB;QAEtB,WAAM,GAAN,MAAM,CAA0B;IACjD,CAAC;CACP;AAPD,8BAOC","sourcesContent":["\nimport type { ClassType } from './ClassType';\nimport { RouteMetadata } from '@webpieces/core-util';\n\n/**\n * ApiClientProxy - the in-process client createApiClient(api) returns: a map of method name →\n * invoker(dto) that runs the filter chain → controller. (Cast to the API interface T for callers.)\n */\n// webpieces-disable no-any-unknown -- proxy holds methods of arbitrary API shapes; DTOs are erased\nexport type ApiClientProxy = Record<string, (...args: unknown[]) => Promise<unknown>>;\n\n/**\n * ApiClient - one registered API surface, reified by {@link ApiClientFactory}:\n * - `api` : the contract class passed to addRoutes,\n * - `client` : exactly what createApiClient(api) returns — a proxy whose methods run the\n * filter chain → controller.\n *\n * That is ALL a transport needs: the express layer reads the api's @ApiPath/@Endpoint decorators\n * to bind each method's HTTP route to the matching `client` method — one-to-one with a test call\n * `client.method(dto)`. The proxy forms the RouteMetadata and drives it through the filters, so\n * the internal RouteBuilder never leaks out. Data-only structure (a class, per the guidelines).\n */\nexport class ApiClient {\n constructor(\n public readonly api: ClassType,\n public readonly client: ApiClientProxy,\n /** The exact registered routes this transport must expose for the contract. */\n public readonly routes: readonly RouteMetadata[],\n ) {}\n}\n"]}
@@ -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 { InvocationAuthentication } from './InvocationAuthentication';\nimport { inject } from 'inversify';\nimport {\n ApiErrorCodec,\n DtoValue,\n getEndpoints,\n HttpContractMapper,\n RequestStream,\n ResponseStream,\n StreamDirection,\n RouteMetadata,\n RouteMetadataFactory,\n StreamEnvelope,\n StreamEventValidator,\n StreamTransportError,\n StreamWriter,\n} from '@webpieces/core-util';\nimport {\n provideFrameworkSingleton,\n RequestContext,\n StreamEventContext,\n} from '@webpieces/core-context';\nimport { MethodMeta } from './MethodMeta';\nimport { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiClient, ApiClientProxy } from './ApiClient';\nimport { ClassType } from './ApiRoutingFactory';\nimport { InProcessStreamExchange } from './InProcessStreamExchange';\n\n/**\n * Every call through the proxy needs an ambient RequestContext, established ABOVE the api boundary.\n * It is NOT auto-created here: manufacturing one silently would hide a missing top-level filter, and\n * every log line, outbound call, and enqueued task under it would quietly lose its request id.\n */\n// webpieces-disable no-function-outside-class -- a guard over ambient state; a class would own nothing\nfunction requireActiveContext(routeMeta: RouteMetadata): void {\n if (RequestContext.isActive()) {\n return;\n }\n throw new Error(\n `${routeMeta.controllerClassName}.${routeMeta.methodName} was called with no active RequestContext. ` +\n `A server transport must wrap each request in RequestContext.run(...) (WebpiecesMiddleware does). ` +\n `In a test, wrap the call yourself: await RequestContext.run(async () => api.foo(req));`,\n );\n}\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.\n *\n * Establishing the request scope is a PRECONDITION of calling in here, never this class's job. The\n * caller above the api boundary opens `RequestContext.run(...)`, publishes the inbound\n * `HttpRequest`, and calls `RequestContextHeaders.fillFromRequest()` to move its headers into the\n * context. `WebpiecesMiddleware` does all three for you; a non-webpieces transport (or a test\n * driving `createApiClient` directly) must do the same. This proxy only CHECKS that it happened —\n * manufacturing a context here would hide a missing filter and silently strip every request id.\n *\n * @provideFrameworkSingleton so WebpiecesRouter can inject it (it shares the one RouteBuilder).\n */\n@provideFrameworkSingleton()\nexport class ApiClientFactory {\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 /** In-process transport bridge; each proxy captures its own credential without ambient mutation. */\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.buildProxy(apiPrototype, undefined, authentication) 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 routesByApi = new Map<ClassType, RouteMetadata[]>();\n for (const route of this.routeBuilder.getRoutes()) {\n const api = route.definition.apiClass as ClassType;\n const routes = routesByApi.get(api) ?? [];\n routes.push(route.definition.routeMeta);\n routesByApi.set(api, routes);\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 const clients: ApiClient[] = [];\n for (const entry of routesByApi.entries()) {\n const api = entry[0];\n const routes = entry[1];\n // Build only the routes that were actually registered. A @WpLocalOnly method is\n // deliberately absent off-local and must not make mounting the rest of its API fail.\n const client = this.buildProxy(api, routes);\n clients.push(new ApiClient(api, client, routes));\n }\n return clients;\n }\n\n /** Build the proxy record (method name → invoker) from the API prototype's decorators. */\n private buildProxy(\n // webpieces-disable no-any-unknown -- accepts any ClassType / abstract-constructor API prototype\n apiPrototype: any,\n registeredRoutes?: readonly RouteMetadata[],\n authentication?: InvocationAuthentication,\n ): ApiClientProxy {\n const endpoints = getEndpoints(apiPrototype) || {};\n const proxy: ApiClientProxy = {};\n const methodNames = registeredRoutes\n ? registeredRoutes.map((route: RouteMetadata) => route.methodName)\n : Object.keys(endpoints);\n\n for (const methodName of methodNames) {\n const contractRoute =\n registeredRoutes?.find((route: RouteMetadata) => route.methodName === methodName) ??\n RouteMetadataFactory.create(apiPrototype, methodName);\n const httpMethod = contractRoute.httpMethod;\n const path = contractRoute.path;\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 (...args: unknown[]): Promise<unknown> => {\n requireActiveContext(routeMeta);\n if (routeMeta.streaming) {\n return this.runStreamingMethod(routeMeta, args, service, authentication);\n }\n // Run the shared mapping even in-process. It validates the exact path/query/body\n // shape production clients use, then the controller receives the original typed args.\n const mapped = HttpContractMapper.toWire(\n routeMeta.path,\n routeMeta.parameterBindings,\n routeMeta.bodyParameterIndex,\n args,\n );\n return this.runMethod(\n routeMeta,\n mapped.body === undefined ? args : mapped.body,\n args,\n service,\n authentication,\n );\n };\n }\n\n return proxy;\n }\n\n /**\n * Join the two in-memory halves through the same envelopes used by wire transports. This is\n * intentionally not a controller shortcut: opening the stream still invokes `service`, so the\n * ordinary logging/auth/application filter chain runs before a writer is returned.\n */\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n private async runStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- one of the three streaming contract return shapes\n ): Promise<unknown> {\n const streaming = routeMeta.streaming;\n if (!streaming) throw new Error('Streaming route metadata is required.');\n if (streaming.direction === StreamDirection.RESPONSE) {\n return this.runResponseStreamingMethod(routeMeta, requestArgs, service, authentication);\n }\n if (streaming.direction === StreamDirection.REQUEST) {\n return this.runRequestStreamingMethod(routeMeta, requestArgs, service, authentication);\n }\n if (streaming.initialRequestSchema) {\n new StreamEventValidator().validate(\n streaming.initialRequestSchema,\n requestArgs[0],\n 'request',\n );\n }\n const clientResponse = this.requireResponseStream(routeMeta, requestArgs[1]);\n const validator = new StreamEventValidator();\n const exchange = new InProcessStreamExchange(clientResponse, streaming.responseSchema!);\n const serverResponse = exchange.response;\n\n // A rejected invocation is the open/handshake failure. It deliberately escapes unchanged,\n // matching unary in-process calls and allowing an HTTP adapter to apply its normal mapper.\n const responseWrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0], serverResponse], authentication),\n );\n const serverRequest = this.requireRequestStream(routeMeta, responseWrapper.response);\n const initialResponse = serverRequest.getInitialResponse();\n const events = new StreamEventContext();\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, initialResponse, 'response');\n }\n exchange.accept(serverRequest);\n const clientRequest = new StreamWriter<DtoValue>(\n async (envelope: StreamEnvelope<DtoValue>): Promise<void> =>\n events.run(\n (): Promise<void> => this.deliverRequestEnvelope(serverRequest, envelope),\n ),\n (value: DtoValue): void =>\n validator.validate(streaming.requestSchema!, value, 'request'),\n initialResponse,\n (error?: Error): Promise<void> => exchange.cancel(error),\n );\n exchange.clientRequest = clientRequest;\n return clientRequest;\n }\n\n // webpieces-disable no-any-unknown -- stream DTOs are erased at the routing boundary\n private async runRequestStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream initial DTO is erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n ): Promise<RequestStream<DtoValue, DtoValue>> {\n const streaming = routeMeta.streaming!;\n const validator = new StreamEventValidator();\n if (streaming.initialRequestSchema) {\n validator.validate(streaming.initialRequestSchema, requestArgs[0], 'request');\n }\n const wrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0]], authentication),\n );\n const serverRequest = this.requireRequestStream(routeMeta, wrapper.response);\n const initialResponse = serverRequest.getInitialResponse();\n const events = new StreamEventContext();\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, initialResponse, 'response');\n }\n return new StreamWriter<DtoValue>(\n async (envelope: StreamEnvelope<DtoValue>): Promise<void> =>\n events.run(\n (): Promise<void> => this.deliverRequestEnvelope(serverRequest, envelope),\n ),\n (value: DtoValue): void =>\n validator.validate(streaming.requestSchema!, value, 'request'),\n initialResponse,\n (error?: Error): Promise<void> => serverRequest.cancel(error),\n );\n }\n\n // webpieces-disable no-any-unknown -- stream DTOs are erased at the routing boundary\n private async runResponseStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- response initial DTO is contract-erased\n ): Promise<unknown> {\n if (requestArgs.length !== 2) {\n throw new StreamTransportError(\n `${routeMeta.apiName}.${routeMeta.methodName} RESPONSE streaming requires (InitialRequest, ResponseStream).`,\n );\n }\n const streaming = routeMeta.streaming!;\n const validator = new StreamEventValidator();\n if (streaming.initialRequestSchema) {\n validator.validate(streaming.initialRequestSchema, requestArgs[0], 'request');\n }\n const clientResponse = this.requireResponseStream(routeMeta, requestArgs[1]);\n const exchange = new InProcessStreamExchange(clientResponse, streaming.responseSchema!);\n const serverResponse = exchange.response;\n const wrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0], serverResponse], authentication),\n );\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, wrapper.response, 'response');\n }\n exchange.accept();\n return wrapper.response;\n }\n\n private async deliverRequestEnvelope(\n destination: RequestStream<DtoValue, DtoValue>,\n envelope: StreamEnvelope<DtoValue>,\n ): Promise<void> {\n switch (envelope.kind) {\n case 'event':\n if (envelope.value === undefined) {\n throw new StreamTransportError('Request event envelope has no value.');\n }\n return destination.event(envelope.value, envelope.correlation);\n case 'failure':\n if (!envelope.error) {\n throw new StreamTransportError('Request failure envelope has no error.');\n }\n return destination.cancel(ApiErrorCodec.decode(envelope.error));\n case 'complete':\n return destination.close();\n }\n }\n\n // webpieces-disable no-any-unknown -- runtime structural check narrows an erased API argument\n private requireResponseStream(\n routeMeta: RouteMetadata,\n candidate: unknown,\n ): ResponseStream<DtoValue> {\n if (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof Reflect.get(candidate, 'event') === 'function' &&\n typeof Reflect.get(candidate, 'close') === 'function' &&\n typeof Reflect.get(candidate, 'cancel') === 'function'\n ) {\n return candidate as ResponseStream<DtoValue>;\n }\n throw new StreamTransportError(\n `${routeMeta.apiName}.${routeMeta.methodName} requires a ResponseStream argument.`,\n );\n }\n\n // webpieces-disable no-any-unknown -- runtime structural check narrows an erased controller result\n private requireRequestStream(\n routeMeta: RouteMetadata,\n candidate: unknown,\n ): RequestStream<DtoValue, DtoValue> {\n if (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof Reflect.get(candidate, 'event') === 'function' &&\n typeof Reflect.get(candidate, 'getInitialResponse') === 'function' &&\n typeof Reflect.get(candidate, 'close') === 'function' &&\n typeof Reflect.get(candidate, 'cancel') === 'function'\n ) {\n return candidate as RequestStream<DtoValue, DtoValue>;\n }\n throw new StreamTransportError(\n `${routeMeta.controllerClassName}.${routeMeta.methodName} did not return a RequestStream.`,\n );\n }\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n private async runMethod(\n routeMeta: RouteMetadata,\n requestDto: unknown,\n requestArgs: unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract response DTOs\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- API return type is erased at the routing proxy boundary\n ): Promise<unknown> {\n const responseWrapper = await service.invoke(\n new MethodMeta(routeMeta, requestDto, undefined, requestArgs, authentication),\n );\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 { InvocationAuthentication } from './InvocationAuthentication';\nimport { inject } from 'inversify';\nimport {\n ApiErrorCodec,\n DtoValue,\n getEndpoints,\n HttpContractMapper,\n RequestStream,\n ResponseStream,\n StreamDirection,\n RouteMetadata,\n RouteMetadataFactory,\n StreamEnvelope,\n StreamEventValidator,\n StreamTransportError,\n StreamWriter,\n} from '@webpieces/core-util';\nimport {\n provideFrameworkSingleton,\n RequestContext,\n StreamEventContext,\n} from '@webpieces/core-context';\nimport { MethodMeta } from './MethodMeta';\nimport { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiClient, ApiClientProxy } from './ApiClient';\n\nimport type { ClassType } from './ClassType';\nimport { InProcessStreamExchange } from './InProcessStreamExchange';\n\n/**\n * Every call through the proxy needs an ambient RequestContext, established ABOVE the api boundary.\n * It is NOT auto-created here: manufacturing one silently would hide a missing top-level filter, and\n * every log line, outbound call, and enqueued task under it would quietly lose its request id.\n */\n// webpieces-disable no-function-outside-class -- a guard over ambient state; a class would own nothing\nfunction requireActiveContext(routeMeta: RouteMetadata): void {\n if (RequestContext.isActive()) {\n return;\n }\n throw new Error(\n `${routeMeta.controllerClassName}.${routeMeta.methodName} was called with no active RequestContext. ` +\n `A server transport must wrap each request in RequestContext.run(...) (WebpiecesMiddleware does). ` +\n `In a test, wrap the call yourself: await RequestContext.run(async () => api.foo(req));`,\n );\n}\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.\n *\n * Establishing the request scope is a PRECONDITION of calling in here, never this class's job. The\n * caller above the api boundary opens `RequestContext.run(...)`, publishes the inbound\n * `HttpRequest`, and calls `RequestContextHeaders.fillFromRequest()` to move its headers into the\n * context. `WebpiecesMiddleware` does all three for you; a non-webpieces transport (or a test\n * driving `createApiClient` directly) must do the same. This proxy only CHECKS that it happened —\n * manufacturing a context here would hide a missing filter and silently strip every request id.\n *\n * @provideFrameworkSingleton so WebpiecesRouter can inject it (it shares the one RouteBuilder).\n */\n@provideFrameworkSingleton()\nexport class ApiClientFactory {\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 /** In-process transport bridge; each proxy captures its own credential without ambient mutation. */\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.buildProxy(apiPrototype, undefined, authentication) 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 routesByApi = new Map<ClassType, RouteMetadata[]>();\n for (const route of this.routeBuilder.getRoutes()) {\n const api = route.definition.apiClass as ClassType;\n const routes = routesByApi.get(api) ?? [];\n routes.push(route.definition.routeMeta);\n routesByApi.set(api, routes);\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 const clients: ApiClient[] = [];\n for (const entry of routesByApi.entries()) {\n const api = entry[0];\n const routes = entry[1];\n // Build only the routes that were actually registered. A @WpLocalOnly method is\n // deliberately absent off-local and must not make mounting the rest of its API fail.\n const client = this.buildProxy(api, routes);\n clients.push(new ApiClient(api, client, routes));\n }\n return clients;\n }\n\n /** Build the proxy record (method name → invoker) from the API prototype's decorators. */\n private buildProxy(\n // webpieces-disable no-any-unknown -- accepts any ClassType / abstract-constructor API prototype\n apiPrototype: any,\n registeredRoutes?: readonly RouteMetadata[],\n authentication?: InvocationAuthentication,\n ): ApiClientProxy {\n const endpoints = getEndpoints(apiPrototype) || {};\n const proxy: ApiClientProxy = {};\n const methodNames = registeredRoutes\n ? registeredRoutes.map((route: RouteMetadata) => route.methodName)\n : Object.keys(endpoints);\n\n for (const methodName of methodNames) {\n const contractRoute =\n registeredRoutes?.find((route: RouteMetadata) => route.methodName === methodName) ??\n RouteMetadataFactory.create(apiPrototype, methodName);\n const httpMethod = contractRoute.httpMethod;\n const path = contractRoute.path;\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 (...args: unknown[]): Promise<unknown> => {\n requireActiveContext(routeMeta);\n if (routeMeta.streaming) {\n return this.runStreamingMethod(routeMeta, args, service, authentication);\n }\n // Run the shared mapping even in-process. It validates the exact path/query/body\n // shape production clients use, then the controller receives the original typed args.\n const mapped = HttpContractMapper.toWire(\n routeMeta.path,\n routeMeta.parameterBindings,\n routeMeta.bodyParameterIndex,\n args,\n );\n return this.runMethod(\n routeMeta,\n mapped.body === undefined ? args : mapped.body,\n args,\n service,\n authentication,\n );\n };\n }\n\n return proxy;\n }\n\n /**\n * Join the two in-memory halves through the same envelopes used by wire transports. This is\n * intentionally not a controller shortcut: opening the stream still invokes `service`, so the\n * ordinary logging/auth/application filter chain runs before a writer is returned.\n */\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n private async runStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- one of the three streaming contract return shapes\n ): Promise<unknown> {\n const streaming = routeMeta.streaming;\n if (!streaming) throw new Error('Streaming route metadata is required.');\n if (streaming.direction === StreamDirection.RESPONSE) {\n return this.runResponseStreamingMethod(routeMeta, requestArgs, service, authentication);\n }\n if (streaming.direction === StreamDirection.REQUEST) {\n return this.runRequestStreamingMethod(routeMeta, requestArgs, service, authentication);\n }\n if (streaming.initialRequestSchema) {\n new StreamEventValidator().validate(\n streaming.initialRequestSchema,\n requestArgs[0],\n 'request',\n );\n }\n const clientResponse = this.requireResponseStream(routeMeta, requestArgs[1]);\n const validator = new StreamEventValidator();\n const exchange = new InProcessStreamExchange(clientResponse, streaming.responseSchema!);\n const serverResponse = exchange.response;\n\n // A rejected invocation is the open/handshake failure. It deliberately escapes unchanged,\n // matching unary in-process calls and allowing an HTTP adapter to apply its normal mapper.\n const responseWrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0], serverResponse], authentication),\n );\n const serverRequest = this.requireRequestStream(routeMeta, responseWrapper.response);\n const initialResponse = serverRequest.getInitialResponse();\n const events = new StreamEventContext();\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, initialResponse, 'response');\n }\n exchange.accept(serverRequest);\n const clientRequest = new StreamWriter<DtoValue>(\n async (envelope: StreamEnvelope<DtoValue>): Promise<void> =>\n events.run(\n (): Promise<void> => this.deliverRequestEnvelope(serverRequest, envelope),\n ),\n (value: DtoValue): void =>\n validator.validate(streaming.requestSchema!, value, 'request'),\n initialResponse,\n (error?: Error): Promise<void> => exchange.cancel(error),\n );\n exchange.clientRequest = clientRequest;\n return clientRequest;\n }\n\n // webpieces-disable no-any-unknown -- stream DTOs are erased at the routing boundary\n private async runRequestStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream initial DTO is erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n ): Promise<RequestStream<DtoValue, DtoValue>> {\n const streaming = routeMeta.streaming!;\n const validator = new StreamEventValidator();\n if (streaming.initialRequestSchema) {\n validator.validate(streaming.initialRequestSchema, requestArgs[0], 'request');\n }\n const wrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0]], authentication),\n );\n const serverRequest = this.requireRequestStream(routeMeta, wrapper.response);\n const initialResponse = serverRequest.getInitialResponse();\n const events = new StreamEventContext();\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, initialResponse, 'response');\n }\n return new StreamWriter<DtoValue>(\n async (envelope: StreamEnvelope<DtoValue>): Promise<void> =>\n events.run(\n (): Promise<void> => this.deliverRequestEnvelope(serverRequest, envelope),\n ),\n (value: DtoValue): void =>\n validator.validate(streaming.requestSchema!, value, 'request'),\n initialResponse,\n (error?: Error): Promise<void> => serverRequest.cancel(error),\n );\n }\n\n // webpieces-disable no-any-unknown -- stream DTOs are erased at the routing boundary\n private async runResponseStreamingMethod(\n routeMeta: RouteMetadata,\n // webpieces-disable no-any-unknown -- stream event DTOs are erased at the routing boundary\n requestArgs: readonly unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract responses\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- response initial DTO is contract-erased\n ): Promise<unknown> {\n if (requestArgs.length !== 2) {\n throw new StreamTransportError(\n `${routeMeta.apiName}.${routeMeta.methodName} RESPONSE streaming requires (InitialRequest, ResponseStream).`,\n );\n }\n const streaming = routeMeta.streaming!;\n const validator = new StreamEventValidator();\n if (streaming.initialRequestSchema) {\n validator.validate(streaming.initialRequestSchema, requestArgs[0], 'request');\n }\n const clientResponse = this.requireResponseStream(routeMeta, requestArgs[1]);\n const exchange = new InProcessStreamExchange(clientResponse, streaming.responseSchema!);\n const serverResponse = exchange.response;\n const wrapper = await service.invoke(\n new MethodMeta(routeMeta, requestArgs[0], undefined, [requestArgs[0], serverResponse], authentication),\n );\n if (streaming.initialResponseSchema) {\n validator.validate(streaming.initialResponseSchema, wrapper.response, 'response');\n }\n exchange.accept();\n return wrapper.response;\n }\n\n private async deliverRequestEnvelope(\n destination: RequestStream<DtoValue, DtoValue>,\n envelope: StreamEnvelope<DtoValue>,\n ): Promise<void> {\n switch (envelope.kind) {\n case 'event':\n if (envelope.value === undefined) {\n throw new StreamTransportError('Request event envelope has no value.');\n }\n return destination.event(envelope.value, envelope.correlation);\n case 'failure':\n if (!envelope.error) {\n throw new StreamTransportError('Request failure envelope has no error.');\n }\n return destination.cancel(ApiErrorCodec.decode(envelope.error));\n case 'complete':\n return destination.close();\n }\n }\n\n // webpieces-disable no-any-unknown -- runtime structural check narrows an erased API argument\n private requireResponseStream(\n routeMeta: RouteMetadata,\n candidate: unknown,\n ): ResponseStream<DtoValue> {\n if (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof Reflect.get(candidate, 'event') === 'function' &&\n typeof Reflect.get(candidate, 'close') === 'function' &&\n typeof Reflect.get(candidate, 'cancel') === 'function'\n ) {\n return candidate as ResponseStream<DtoValue>;\n }\n throw new StreamTransportError(\n `${routeMeta.apiName}.${routeMeta.methodName} requires a ResponseStream argument.`,\n );\n }\n\n // webpieces-disable no-any-unknown -- runtime structural check narrows an erased controller result\n private requireRequestStream(\n routeMeta: RouteMetadata,\n candidate: unknown,\n ): RequestStream<DtoValue, DtoValue> {\n if (\n typeof candidate === 'object' &&\n candidate !== null &&\n typeof Reflect.get(candidate, 'event') === 'function' &&\n typeof Reflect.get(candidate, 'getInitialResponse') === 'function' &&\n typeof Reflect.get(candidate, 'close') === 'function' &&\n typeof Reflect.get(candidate, 'cancel') === 'function'\n ) {\n return candidate as RequestStream<DtoValue, DtoValue>;\n }\n throw new StreamTransportError(\n `${routeMeta.controllerClassName}.${routeMeta.methodName} did not return a RequestStream.`,\n );\n }\n\n // webpieces-disable no-any-unknown -- request/response DTOs are erased at the routing boundary\n private async runMethod(\n routeMeta: RouteMetadata,\n requestDto: unknown,\n requestArgs: unknown[],\n // webpieces-disable no-any-unknown -- filter service carries arbitrary contract response DTOs\n service: Service<MethodMeta, WpResponse<unknown>>,\n authentication?: InvocationAuthentication,\n // webpieces-disable no-any-unknown -- API return type is erased at the routing proxy boundary\n ): Promise<unknown> {\n const responseWrapper = await service.invoke(\n new MethodMeta(routeMeta, requestDto, undefined, requestArgs, authentication),\n );\n return responseWrapper.response;\n }\n}\n"]}
@@ -1,12 +1,8 @@
1
+ import type { ClassType } from './ClassType';
2
+ import { LazyController } from './LazyController';
1
3
  import { Routes, RouteBuilder } from './WebAppMeta';
2
4
  import { AuthMeta } from '@webpieces/core-util';
3
5
  import 'reflect-metadata';
4
- /**
5
- * Type representing a class constructor (abstract or concrete).
6
- */
7
- export type ClassType<T = unknown> = Function & {
8
- prototype: T;
9
- };
10
6
  /**
11
7
  * ApiRoutingFactory - Automatically wire API interfaces to controllers.
12
8
  * Reads @ApiPath/@Endpoint decorators from an API prototype class and
@@ -24,19 +20,20 @@ export type ClassType<T = unknown> = Function & {
24
20
  * }
25
21
  * ```
26
22
  */
27
- export declare class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {
23
+ export declare class ApiRoutingFactory<TApi extends object = object, TController extends TApi = TApi> implements Routes {
28
24
  private apiMetaClass;
29
25
  private controllerClass;
30
26
  /**
31
27
  * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators
32
28
  * @param controllerClass - The controller class that implements the API
33
29
  */
34
- constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>);
30
+ constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController> | LazyController<TController>);
35
31
  /**
36
32
  * Configure routes by reading @ApiPath + @Endpoint metadata.
37
33
  * Validates controller methods and auth decorators in single loop.
38
34
  */
39
35
  configure(routeBuilder: RouteBuilder): void;
36
+ private assertControllerMethod;
40
37
  /**
41
38
  * Get the filepath of the controller source file.
42
39
  * Uses a heuristic based on the controller class name.
@@ -53,5 +50,5 @@ export declare class ApiRoutingFactory<TApi = unknown, TController extends TApi
53
50
  /**
54
51
  * Get the controller class.
55
52
  */
56
- getControllerClass(): ClassType<TController>;
53
+ getControllerImplementation(): ClassType<TController> | LazyController<TController>;
57
54
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ApiRoutingFactory = void 0;
4
+ const LazyController_1 = require("./LazyController");
4
5
  const WebAppMeta_1 = require("./WebAppMeta");
5
6
  const core_util_1 = require("@webpieces/core-util");
6
7
  require("reflect-metadata");
@@ -47,7 +48,8 @@ class ApiRoutingFactory {
47
48
  // confusing routing or method-not-found error.
48
49
  const apiName = apiMetaClass.name || 'Unknown';
49
50
  const controllerName = controllerClass.name || 'Unknown';
50
- if (!apiMetaClass.prototype.isPrototypeOf(controllerClass.prototype)) {
51
+ if (!(controllerClass instanceof LazyController_1.LazyController) &&
52
+ !apiMetaClass.prototype.isPrototypeOf(controllerClass.prototype)) {
51
53
  throw new Error(`Controller ${controllerName} must extend ${apiName}. ` +
52
54
  `Change the class declaration to: ` +
53
55
  `'export class ${controllerName} extends ${apiName} { ... }'`);
@@ -75,10 +77,7 @@ class ApiRoutingFactory {
75
77
  const apiName = this.apiMetaClass.name || 'Unknown';
76
78
  const controllerName = this.controllerClass.name || 'Unknown';
77
79
  for (const methodName of Object.keys(endpoints)) {
78
- // Validate controller implements this method
79
- if (typeof this.controllerClass.prototype[methodName] !== 'function') {
80
- throw new Error(`Controller ${controllerName} must implement method ${methodName} from API ${apiName}`);
81
- }
80
+ this.assertControllerMethod(methodName);
82
81
  // Validate auth decorator exists (class-level or method-level)
83
82
  const authMeta = (0, core_util_1.getAuthMeta)(this.apiMetaClass, methodName);
84
83
  if (!authMeta) {
@@ -90,7 +89,8 @@ class ApiRoutingFactory {
90
89
  // the backstop for routes added by hand through RouteBuilder. One decorator drives both
91
90
  // — the point of moving this into the framework was that apps were hand-syncing exactly
92
91
  // these two halves across two files with a comment.
93
- if ((0, core_util_1.isLocalOnly)(this.apiMetaClass, methodName) && !core_util_1.RuntimeLocality.isLocalDevelopment()) {
92
+ if ((0, core_util_1.isLocalOnly)(this.apiMetaClass, methodName) &&
93
+ !core_util_1.RuntimeLocality.isLocalDevelopment()) {
94
94
  log.info(`Skipping @WpLocalOnly endpoint ${apiName}.${methodName} — this process is not ` +
95
95
  `a local developer machine, so the route is not registered at all.`);
96
96
  continue;
@@ -99,11 +99,18 @@ class ApiRoutingFactory {
99
99
  routeBuilder.addRoute(new WebAppMeta_1.RouteDefinition(routeMeta, this.controllerClass, controllerFilepath, this.apiMetaClass));
100
100
  }
101
101
  }
102
+ assertControllerMethod(methodName) {
103
+ if (!(this.controllerClass instanceof LazyController_1.LazyController) &&
104
+ typeof this.controllerClass.prototype[methodName] !== 'function')
105
+ throw new Error(`Controller ${this.controllerClass.name} must implement method ${methodName} from API ${this.apiMetaClass.name}`);
106
+ }
102
107
  /**
103
108
  * Get the filepath of the controller source file.
104
109
  * Uses a heuristic based on the controller class name.
105
110
  */
106
111
  getControllerFilepath() {
112
+ if (this.controllerClass instanceof LazyController_1.LazyController)
113
+ return this.controllerClass.filepath;
107
114
  // Check for explicit @SourceFile decorator metadata
108
115
  const filepath = Reflect.getMetadata(decorators_1.ROUTING_METADATA_KEYS.SOURCE_FILEPATH, this.controllerClass);
109
116
  if (filepath) {
@@ -128,7 +135,7 @@ class ApiRoutingFactory {
128
135
  /**
129
136
  * Get the controller class.
130
137
  */
131
- getControllerClass() {
138
+ getControllerImplementation() {
132
139
  return this.controllerClass;
133
140
  }
134
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApiRoutingFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiRoutingFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport {\n isApiPath,\n getEndpoints,\n getAuthMeta,\n assertEveryVendorAuthEndpointRetainsRawBody,\n assertNotInternalApi,\n LogManager,\n RouteMetadataFactory,\n AuthMeta,\n MISSING_AUTH_DECORATOR_FIX,\n RuntimeLocality,\n isLocalOnly,\n isExternalOutboundApi,\n} from '@webpieces/core-util';\nimport 'reflect-metadata';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\nconst log = LogManager.getLogger('ApiRoutingFactory');\n\n/**\n * Type representing a class constructor (abstract or concrete).\n */\n// webpieces-disable no-any-unknown -- generic type alias requires unconstrained default\nexport type ClassType<T = unknown> = Function & { prototype: T };\n\n/**\n * ApiRoutingFactory - Automatically wire API interfaces to controllers.\n * Reads @ApiPath/@Endpoint decorators from an API prototype class and\n * registers POST routes for each endpoint.\n *\n * Replaces the old RESTApiRoutes class.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new ApiRoutingFactory(SaveApi, SaveController),\n * ];\n * }\n * ```\n */\n// webpieces-disable no-any-unknown -- generic class requires unconstrained default type params\nexport class ApiRoutingFactory<TApi = unknown, TController extends TApi = TApi> implements Routes {\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController>;\n\n /**\n * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators\n * @param controllerClass - The controller class that implements the API\n */\n constructor(apiMetaClass: ClassType<TApi>, controllerClass: ClassType<TController>) {\n assertNotInternalApi(apiMetaClass, 'ApiRoutingFactory');\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked with @ApiPath\n if (!isApiPath(apiMetaClass)) {\n const className = apiMetaClass.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n // Validate that controllerClass actually extends apiMetaClass.\n // TypeScript's structural typing won't catch a missing `extends` here, so we check\n // the runtime prototype chain. Without this, a controller can silently drift from\n // the API contract (wrong method names, wrong signatures) and only fail later as a\n // confusing routing or method-not-found error.\n const apiName = apiMetaClass.name || 'Unknown';\n const controllerName = controllerClass.name || 'Unknown';\n if (\n !(apiMetaClass.prototype as object).isPrototypeOf(controllerClass.prototype as object)\n ) {\n throw new Error(\n `Controller ${controllerName} must extend ${apiName}. ` +\n `Change the class declaration to: ` +\n `'export class ${controllerName} extends ${apiName} { ... }'`,\n );\n }\n }\n\n /**\n * Configure routes by reading @ApiPath + @Endpoint metadata.\n * Validates controller methods and auth decorators in single loop.\n */\n configure(routeBuilder: RouteBuilder): void {\n // An EXTERNAL_OUTBOUND contract is an outside system's API: nothing in this repo serves it.\n if (isExternalOutboundApi(this.apiMetaClass)) {\n throw new Error(\n `${this.apiMetaClass.name || 'Unknown'} is an EXTERNAL_OUTBOUND contract — an outside system's API that ` +\n `this repo only CALLS — so no server may addRoutes it. Bind a client to it instead ` +\n `(createRpcClientAndBind), or change its endpoints to RPC / EXTERNAL_INBOUND if this service serves them.`,\n );\n }\n // A vendorAuth route whose transport keeps no bytes has nothing to verify. That is a wiring\n // mistake, so it dies at STARTUP naming the fix — not as a 401 in production on exactly the\n // traffic the endpoint exists for.\n assertEveryVendorAuthEndpointRetainsRawBody(this.apiMetaClass);\n // Two endpoints on one method + path would silently shadow each other in the route map.\n RouteMetadataFactory.assertNoDuplicateRoutes(this.apiMetaClass);\n\n const endpoints = getEndpoints(this.apiMetaClass) || {};\n const controllerFilepath = this.getControllerFilepath();\n const apiName = this.apiMetaClass.name || 'Unknown';\n const controllerName = this.controllerClass.name || 'Unknown';\n\n for (const methodName of Object.keys(endpoints)) {\n // Validate controller implements this method\n if (typeof this.controllerClass.prototype[methodName] !== 'function') {\n throw new Error(\n `Controller ${controllerName} must implement method ${methodName} from API ${apiName}`,\n );\n }\n\n // Validate auth decorator exists (class-level or method-level)\n const authMeta = getAuthMeta(this.apiMetaClass, methodName);\n if (!authMeta) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no auth decorator. ` +\n MISSING_AUTH_DECORATOR_FIX,\n );\n }\n\n // @WpLocalOnly: off-local the route is never registered, so the endpoint does not\n // exist rather than existing-and-refusing. This is the PRIMARY gate; AuthFilter's 404 is\n // the backstop for routes added by hand through RouteBuilder. One decorator drives both\n // — the point of moving this into the framework was that apps were hand-syncing exactly\n // these two halves across two files with a comment.\n if (isLocalOnly(this.apiMetaClass, methodName) && !RuntimeLocality.isLocalDevelopment()) {\n log.info(\n `Skipping @WpLocalOnly endpoint ${apiName}.${methodName} — this process is not ` +\n `a local developer machine, so the route is not registered at all.`,\n );\n continue;\n }\n\n const routeMeta = RouteMetadataFactory.create(\n this.apiMetaClass,\n methodName,\n controllerName,\n );\n\n routeBuilder.addRoute(\n new RouteDefinition(\n routeMeta,\n this.controllerClass,\n controllerFilepath,\n this.apiMetaClass,\n ),\n );\n }\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n */\n private getControllerFilepath(): string | undefined {\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = this.controllerClass.name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get auth metadata for a specific method, falling back to class-level.\n */\n getAuthMetaForMethod(methodName: string): AuthMeta | undefined {\n return getAuthMeta(this.apiMetaClass, methodName);\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): ClassType<TApi> {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerClass(): ClassType<TController> {\n return this.controllerClass;\n }\n}\n"]}
1
+ {"version":3,"file":"ApiRoutingFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiRoutingFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClassType } from './ClassType';\nimport { LazyController } from './LazyController';\nimport { Routes, RouteBuilder, RouteDefinition } from './WebAppMeta';\nimport {\n isApiPath,\n getEndpoints,\n getAuthMeta,\n assertEveryVendorAuthEndpointRetainsRawBody,\n assertNotInternalApi,\n LogManager,\n RouteMetadataFactory,\n AuthMeta,\n MISSING_AUTH_DECORATOR_FIX,\n RuntimeLocality,\n isLocalOnly,\n isExternalOutboundApi,\n} from '@webpieces/core-util';\nimport 'reflect-metadata';\nimport { ROUTING_METADATA_KEYS } from './decorators';\n\nconst log = LogManager.getLogger('ApiRoutingFactory');\n\n/**\n * ApiRoutingFactory - Automatically wire API interfaces to controllers.\n * Reads @ApiPath/@Endpoint decorators from an API prototype class and\n * registers POST routes for each endpoint.\n *\n * Replaces the old RESTApiRoutes class.\n *\n * Usage:\n * ```typescript\n * // In your ServerMeta:\n * getRoutes(): Routes[] {\n * return [\n * new ApiRoutingFactory(SaveApi, SaveController),\n * ];\n * }\n * ```\n */\n// webpieces-disable no-any-unknown -- generic class requires unconstrained default type params\nexport class ApiRoutingFactory<TApi extends object = object, TController extends TApi = TApi>\n implements Routes\n{\n private apiMetaClass: ClassType<TApi>;\n private controllerClass: ClassType<TController> | LazyController<TController>;\n\n /**\n * @param apiMetaClass - The API prototype class with @ApiPath/@Endpoint decorators\n * @param controllerClass - The controller class that implements the API\n */\n constructor(\n apiMetaClass: ClassType<TApi>,\n controllerClass: ClassType<TController> | LazyController<TController>,\n ) {\n assertNotInternalApi(apiMetaClass, 'ApiRoutingFactory');\n this.apiMetaClass = apiMetaClass;\n this.controllerClass = controllerClass;\n\n // Validate that apiMetaClass is marked with @ApiPath\n if (!isApiPath(apiMetaClass)) {\n const className = apiMetaClass.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n // Validate that controllerClass actually extends apiMetaClass.\n // TypeScript's structural typing won't catch a missing `extends` here, so we check\n // the runtime prototype chain. Without this, a controller can silently drift from\n // the API contract (wrong method names, wrong signatures) and only fail later as a\n // confusing routing or method-not-found error.\n const apiName = apiMetaClass.name || 'Unknown';\n const controllerName = controllerClass.name || 'Unknown';\n if (\n !(controllerClass instanceof LazyController) &&\n !(apiMetaClass.prototype as object).isPrototypeOf(controllerClass.prototype as object)\n ) {\n throw new Error(\n `Controller ${controllerName} must extend ${apiName}. ` +\n `Change the class declaration to: ` +\n `'export class ${controllerName} extends ${apiName} { ... }'`,\n );\n }\n }\n\n /**\n * Configure routes by reading @ApiPath + @Endpoint metadata.\n * Validates controller methods and auth decorators in single loop.\n */\n configure(routeBuilder: RouteBuilder): void {\n // An EXTERNAL_OUTBOUND contract is an outside system's API: nothing in this repo serves it.\n if (isExternalOutboundApi(this.apiMetaClass)) {\n throw new Error(\n `${this.apiMetaClass.name || 'Unknown'} is an EXTERNAL_OUTBOUND contract — an outside system's API that ` +\n `this repo only CALLS — so no server may addRoutes it. Bind a client to it instead ` +\n `(createRpcClientAndBind), or change its endpoints to RPC / EXTERNAL_INBOUND if this service serves them.`,\n );\n }\n // A vendorAuth route whose transport keeps no bytes has nothing to verify. That is a wiring\n // mistake, so it dies at STARTUP naming the fix — not as a 401 in production on exactly the\n // traffic the endpoint exists for.\n assertEveryVendorAuthEndpointRetainsRawBody(this.apiMetaClass);\n // Two endpoints on one method + path would silently shadow each other in the route map.\n RouteMetadataFactory.assertNoDuplicateRoutes(this.apiMetaClass);\n\n const endpoints = getEndpoints(this.apiMetaClass) || {};\n const controllerFilepath = this.getControllerFilepath();\n const apiName = this.apiMetaClass.name || 'Unknown';\n const controllerName = this.controllerClass.name || 'Unknown';\n\n for (const methodName of Object.keys(endpoints)) {\n this.assertControllerMethod(methodName);\n\n // Validate auth decorator exists (class-level or method-level)\n const authMeta = getAuthMeta(this.apiMetaClass, methodName);\n if (!authMeta) {\n throw new Error(\n `Endpoint '${methodName}' in ${apiName} has no auth decorator. ` +\n MISSING_AUTH_DECORATOR_FIX,\n );\n }\n\n // @WpLocalOnly: off-local the route is never registered, so the endpoint does not\n // exist rather than existing-and-refusing. This is the PRIMARY gate; AuthFilter's 404 is\n // the backstop for routes added by hand through RouteBuilder. One decorator drives both\n // — the point of moving this into the framework was that apps were hand-syncing exactly\n // these two halves across two files with a comment.\n if (\n isLocalOnly(this.apiMetaClass, methodName) &&\n !RuntimeLocality.isLocalDevelopment()\n ) {\n log.info(\n `Skipping @WpLocalOnly endpoint ${apiName}.${methodName} — this process is not ` +\n `a local developer machine, so the route is not registered at all.`,\n );\n continue;\n }\n\n const routeMeta = RouteMetadataFactory.create(\n this.apiMetaClass,\n methodName,\n controllerName,\n );\n\n routeBuilder.addRoute(\n new RouteDefinition(\n routeMeta,\n this.controllerClass,\n controllerFilepath,\n this.apiMetaClass,\n ),\n );\n }\n }\n\n private assertControllerMethod(methodName: string): void {\n if (\n !(this.controllerClass instanceof LazyController) &&\n typeof this.controllerClass.prototype[methodName] !== 'function'\n )\n throw new Error(\n `Controller ${this.controllerClass.name} must implement method ${methodName} from API ${this.apiMetaClass.name}`,\n );\n }\n\n /**\n * Get the filepath of the controller source file.\n * Uses a heuristic based on the controller class name.\n */\n private getControllerFilepath(): string | undefined {\n if (this.controllerClass instanceof LazyController) return this.controllerClass.filepath;\n // Check for explicit @SourceFile decorator metadata\n const filepath = Reflect.getMetadata(\n ROUTING_METADATA_KEYS.SOURCE_FILEPATH,\n this.controllerClass,\n );\n if (filepath) {\n return filepath;\n }\n\n // Fallback to class name pattern\n const className = this.controllerClass.name;\n return className ? `**/${className}.ts` : undefined;\n }\n\n /**\n * Get auth metadata for a specific method, falling back to class-level.\n */\n getAuthMetaForMethod(methodName: string): AuthMeta | undefined {\n return getAuthMeta(this.apiMetaClass, methodName);\n }\n\n /**\n * Get the API interface class.\n */\n getApiClass(): ClassType<TApi> {\n return this.apiMetaClass;\n }\n\n /**\n * Get the controller class.\n */\n getControllerImplementation(): ClassType<TController> | LazyController<TController> {\n return this.controllerClass;\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { BindModule as HostBindModule } from '@webpieces/http-client-core';
2
+ import type { Binder } from './Binder';
3
+ /** Named DI declaration. setupRuntime hands it the Node {@link Binder} over one Inversify load. */
4
+ export interface BindModule extends HostBindModule<Binder> {
5
+ configure(binder: Binder): void | Promise<void>;
6
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=BindModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BindModule.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/BindModule.ts"],"names":[],"mappings":"","sourcesContent":["import type { BindModule as HostBindModule } from '@webpieces/http-client-core';\nimport type { Binder } from './Binder';\n\n/** Named DI declaration. setupRuntime hands it the Node {@link Binder} over one Inversify load. */\nexport interface BindModule extends HostBindModule<Binder> {\n configure(binder: Binder): void | Promise<void>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ /** Abstract or concrete API/controller constructor identity. */
2
+ export type ClassType<T = unknown> = Function & {
3
+ prototype: T;
4
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ClassType.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClassType.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ClassType.ts"],"names":[],"mappings":"","sourcesContent":["/** Abstract or concrete API/controller constructor identity. */\n// webpieces-disable no-any-unknown -- API constructor identity is generic across unrelated contracts\nexport type ClassType<T = unknown> = Function & { prototype: T };\n"]}
@@ -0,0 +1,46 @@
1
+ import { Container, type Newable, type ServiceIdentifier } from 'inversify';
2
+ import type { ClassType } from './ClassType';
3
+ import type { BindModule } from './BindModule';
4
+ /** The cancellable native deadline edge; feature tests replace only this clock. */
5
+ export declare class LazyFeatureDeadline {
6
+ readonly cancel: () => void;
7
+ constructor(cancel: () => void);
8
+ }
9
+ /** Schedules a bounded first-use initialization; never a per-request authorization cache. */
10
+ export declare abstract class LazyFeatureTimer {
11
+ abstract schedule(timeoutMillis: number, expire: () => void): Promise<LazyFeatureDeadline>;
12
+ }
13
+ /** Imported only on first permitted use. Explicitly list private concrete singletons. */
14
+ export declare class LazyImplementation<T extends object> {
15
+ readonly controller: Newable<T>;
16
+ readonly bindings: readonly BindModule[];
17
+ readonly singletons: readonly Newable<object>[];
18
+ readonly shared: readonly ServiceIdentifier[];
19
+ constructor(controller: Newable<T>, bindings: readonly BindModule[], singletons: readonly Newable<object>[], shared: readonly ServiceIdentifier[]);
20
+ }
21
+ /**
22
+ * Eager identity/security metadata, deferred implementation. The loader imports one feature and
23
+ * returns declarative bindings; it must not open resources or perform request work. A failure is
24
+ * retained per router until restart. The required deadline bounds waiting, not JavaScript CPU.
25
+ */
26
+ export declare class LazyController<T extends object> {
27
+ readonly name: string;
28
+ readonly filepath: string;
29
+ readonly timeoutMillis: number;
30
+ readonly load: () => Promise<LazyImplementation<T>>;
31
+ constructor(name: string, filepath: string, timeoutMillis: number, load: () => Promise<LazyImplementation<T>>);
32
+ }
33
+ /** One retained initialization promise per descriptor per router; callers keep their own context. */
34
+ export declare class LazyControllerState<T extends object> {
35
+ private readonly parent;
36
+ private readonly api;
37
+ private readonly definition;
38
+ private readonly timer;
39
+ private readonly methods;
40
+ private initialized?;
41
+ constructor(parent: Container, api: ClassType<T>, definition: LazyController<T>, timer: LazyFeatureTimer, methods: readonly string[]);
42
+ matchesApi(api: ClassType<object>): boolean;
43
+ resolve(): Promise<T>;
44
+ private initialize;
45
+ private build;
46
+ }