@webpieces/http-routing 0.4.988 → 0.4.990
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 +92 -0
- package/package.json +7 -7
- package/src/ApiClient.d.ts +1 -1
- package/src/ApiClient.js.map +1 -1
- package/src/ApiClientFactory.js.map +1 -1
- package/src/ApiRoutingFactory.d.ts +6 -9
- package/src/ApiRoutingFactory.js +14 -7
- package/src/ApiRoutingFactory.js.map +1 -1
- package/src/BindModule.d.ts +6 -0
- package/src/BindModule.js +3 -0
- package/src/BindModule.js.map +1 -0
- package/src/ClassType.d.ts +4 -0
- package/src/ClassType.js +3 -0
- package/src/ClassType.js.map +1 -0
- package/src/LazyController.d.ts +51 -0
- package/src/LazyController.js +194 -0
- package/src/LazyController.js.map +1 -0
- package/src/LazyControllerCompileAssertions.d.ts +15 -0
- package/src/LazyControllerCompileAssertions.js +29 -0
- package/src/LazyControllerCompileAssertions.js.map +1 -0
- package/src/RouteBuilderImpl.d.ts +4 -0
- package/src/RouteBuilderImpl.js +42 -4
- package/src/RouteBuilderImpl.js.map +1 -1
- package/src/WebAppMeta.d.ts +6 -4
- package/src/WebAppMeta.js +3 -5
- package/src/WebAppMeta.js.map +1 -1
- package/src/WebpiecesRouter.d.ts +10 -3
- package/src/WebpiecesRouter.js +11 -1
- package/src/WebpiecesRouter.js.map +1 -1
- package/src/Wiring.d.ts +2 -6
- package/src/Wiring.js.map +1 -1
- package/src/WiringCompileAssertions.d.ts +2 -1
- package/src/WiringCompileAssertions.js.map +1 -1
- package/src/index.d.ts +5 -2
- package/src/index.js +7 -1
- package/src/index.js.map +1 -1
- package/src/setupRuntime.js.map +1 -1
package/README.md
CHANGED
|
@@ -60,3 +60,95 @@ 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
|
+
Use `new ApplicationImplementation(feature.AdminController)` when the implementation uses the
|
|
101
|
+
application's existing bindings and shared concrete singleton graph. This is an import-only boundary:
|
|
102
|
+
first authorized use imports the class and resolves it in the original application container, in the
|
|
103
|
+
same detached initialization context. Different deferred APIs continue to share their application
|
|
104
|
+
singletons. It accepts no late binding modules and performs no rebinding; binding declarations remain
|
|
105
|
+
at startup. As with existing eager DI, constructing a legitimate shared collaborator before a later
|
|
106
|
+
constructor fails is not an atomic transaction and is not rolled back. Remaining eager binding-barrel
|
|
107
|
+
imports must be measured separately; this mode alone does not remove them.
|
|
108
|
+
|
|
109
|
+
Use `LazyImplementation` for an isolated feature with private bindings. It declares four things: the concrete controller, binding modules, private concrete
|
|
110
|
+
singletons (excluding the controller, which is implicit), and explicitly shared application dependencies.
|
|
111
|
+
In isolated mode, private bindings live in an isolated container with autobinding disabled. Each shared dependency must
|
|
112
|
+
already be explicitly bound in the application; the bridge returns the application's instance and
|
|
113
|
+
preserves its scope instead of creating a second singleton. Bind shared services explicitly at startup
|
|
114
|
+
when no essential route would otherwise resolve them. Merely importing a class does not bind it.
|
|
115
|
+
|
|
116
|
+
An Inversify child with an autobinding parent is insufficient isolation: an `isBound` lookup can
|
|
117
|
+
create a binding in the parent. This implementation never uses such a child. Private `bind` calls
|
|
118
|
+
and private singleton declarations reject collisions with application bindings; a failed candidate
|
|
119
|
+
cannot partially rebind shared auth, logging or clients. RPC/task/external binding helpers inherit an
|
|
120
|
+
existing application edge binding (including test overrides) instead of replacing it. Declare shared
|
|
121
|
+
framework factories, such as `ClientHttpFactory`, when a private binding creates a client using them.
|
|
122
|
+
|
|
123
|
+
The descriptor has one initialization promise per router. Concurrent methods share it; successful
|
|
124
|
+
initialization reuses one controller; failure is retained until the router/process is rebuilt, with
|
|
125
|
+
no retry loop. One descriptor cannot register two different API contracts on the same router. The
|
|
126
|
+
same descriptor can be used by separate routers without sharing their controller or scoped state.
|
|
127
|
+
Import, binding and singleton construction run in a fresh detached `RequestContext`; each invocation
|
|
128
|
+
resumes in its own caller scope after initialization. Never capture a caller in the loader's closure.
|
|
129
|
+
|
|
130
|
+
The required positive timeout bounds asynchronous initialization waiting. A timeout retains failure
|
|
131
|
+
and prevents a late import/configuration from publishing a controller. JavaScript module evaluation
|
|
132
|
+
and synchronous constructors still run on the main thread: timers cannot preempt CPU work. Keep
|
|
133
|
+
loaders and binding modules declarative; they must not start request work, open background resources
|
|
134
|
+
or hide network warmups. This API adds no process signal/shutdown hooks. Host termination retains
|
|
135
|
+
existing behavior; pending imports are not a shutdown flush queue.
|
|
136
|
+
|
|
137
|
+
Existing transport cancellation and client deadlines remain transport-owned. The shared initialization
|
|
138
|
+
is not cancelled because one client stops waiting. Unary disconnects do not currently propagate a
|
|
139
|
+
cancellation signal into `MethodMeta`; this capability does not add one or promise that an already
|
|
140
|
+
accepted write is cancelled when its client disconnects. Initialization timeout is a separate limit.
|
|
141
|
+
|
|
142
|
+
Call `await router.validateLazyRoutesForBuild()` in a **separate build/integration process with fake
|
|
143
|
+
external providers** to import every registered optional implementation and validate its DI before
|
|
144
|
+
shipping. It invokes no API methods, refuses execution inside a request, and never bypasses subsequent
|
|
145
|
+
invocation auth. It warms that validation router, so never call it in the production entry point.
|
|
146
|
+
The production library excludes its `src/__tests__` tree, including the deferred-import fixtures.
|
|
147
|
+
|
|
148
|
+
Feature tests cover real imports, first/concurrent/later methods, distinct principals, shared state,
|
|
149
|
+
critical requests during a held import, retained import/binding/method failures, collision rejection,
|
|
150
|
+
late completion after timeout and explicit build validation. Separate loopback tests prove HTTP and
|
|
151
|
+
MCP discovery/auth/dispatch. No production speedup is claimed from these small fixtures: measure the
|
|
152
|
+
actual consuming image's startup, first optional request, later requests, essential requests during
|
|
153
|
+
initialization and memory separately. The selected Lang migration is
|
|
154
|
+
[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.
|
|
3
|
+
"version": "0.4.990",
|
|
4
4
|
"description": "Decorator-based routing with auto-wiring for WebPieces",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
|
-
"@webpieces/core-context": "0.4.
|
|
26
|
-
"@webpieces/core-util": "0.4.
|
|
27
|
-
"@webpieces/gcp-identity": "0.4.
|
|
25
|
+
"@webpieces/core-context": "0.4.990",
|
|
26
|
+
"@webpieces/core-util": "0.4.990",
|
|
27
|
+
"@webpieces/gcp-identity": "0.4.990",
|
|
28
28
|
"inversify": "7.10.4",
|
|
29
29
|
"jsonwebtoken": "9.0.2",
|
|
30
30
|
"minimatch": "10.0.1",
|
|
31
|
-
"@webpieces/http-client-core": "0.4.
|
|
32
|
-
"@webpieces/http-client-node": "0.4.
|
|
33
|
-
"@webpieces/cloudtasks-client": "0.4.
|
|
31
|
+
"@webpieces/http-client-core": "0.4.990",
|
|
32
|
+
"@webpieces/http-client-node": "0.4.990",
|
|
33
|
+
"@webpieces/cloudtasks-client": "0.4.990"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/ApiClient.d.ts
CHANGED
package/src/ApiClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/ApiClient.ts"],"names":[],"mappings":";;;
|
|
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":";;;;AACA,yCAAmC;AACnC,oDAc8B;AAC9B,0DAIiC;AACjC,6CAA0C;AAG1C,yDAAsD;AACtD,2CAAwD;AAExD,uEAAoE;AAEpE;;;;GAIG;AACH,uGAAuG;AACvG,SAAS,oBAAoB,CAAC,SAAwB;IAClD,IAAI,6BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC5B,OAAO;IACX,CAAC;IACD,MAAM,IAAI,KAAK,CACX,GAAG,SAAS,CAAC,mBAAmB,IAAI,SAAS,CAAC,UAAU,6CAA6C;QACjG,mGAAmG;QACnG,wFAAwF,CAC/F,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC8B;IAAvD,YAAuD,YAA8B;QAA9B,iBAAY,GAAZ,YAAY,CAAkB;IAAG,CAAC;IAEzF;;;OAGG;IACH,yFAAyF;IACzF,eAAe,CAAI,YAAgD;QAC/D,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAM,CAAC;IAC9C,CAAC;IAED,oGAAoG;IACpG,yEAAyE;IACzE,yBAAyB,CACrB,YAAgD,EAChD,cAAwC;QAExC,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,cAAc,CAAM,CAAC;IACzE,CAAC;IAED;;;;OAIG;IACH,UAAU;QACN,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8B,CAAC;QAC1D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC;YAChD,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,QAAqB,CAAC;YACnD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YACxC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,0FAA0F;QAC1F,kEAAkE;QAClE,MAAM,OAAO,GAAgB,EAAE,CAAC;QAChC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,gFAAgF;YAChF,qFAAqF;YACrF,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,IAAI,qBAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,0FAA0F;IAClF,UAAU;IACd,iGAAiG;IACjG,YAAiB,EACjB,gBAA2C,EAC3C,cAAyC;QAEzC,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACnD,MAAM,KAAK,GAAmB,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,gBAAgB;YAChC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC;YAClE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE7B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACnC,MAAM,aAAa,GACf,gBAAgB,EAAE,IAAI,CAAC,CAAC,KAAoB,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,CAAC;gBACjF,gCAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC;YAC5C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;YAEhC,oFAAoF;YACpF,yFAAyF;YACzF,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YACnE,IAAI,CAAC,SAAS,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACX,2BAA2B,YAAY,CAAC,IAAI,IAAI,UAAU,KAAK,UAAU,IAAI,IAAI,4CAA4C,CAChI,CAAC;YACN,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEvE,+FAA+F;YAC/F,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,EAAE,GAAG,IAAe,EAAoB,EAAE;gBAC/D,oBAAoB,CAAC,SAAS,CAAC,CAAC;gBAChC,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;oBACtB,OAAO,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;gBAC7E,CAAC;gBACD,iFAAiF;gBACjF,sFAAsF;gBACtF,MAAM,MAAM,GAAG,8BAAkB,CAAC,MAAM,CACpC,SAAS,CAAC,IAAI,EACd,SAAS,CAAC,iBAAiB,EAC3B,SAAS,CAAC,kBAAkB,EAC5B,IAAI,CACP,CAAC;gBACF,OAAO,IAAI,CAAC,SAAS,CACjB,SAAS,EACT,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAC9C,IAAI,EACJ,OAAO,EACP,cAAc,CACjB,CAAC;YACN,CAAC,CAAC;QACN,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACH,2FAA2F;IACnF,KAAK,CAAC,kBAAkB,CAC5B,SAAwB;IACxB,2FAA2F;IAC3F,WAA+B;IAC/B,0FAA0F;IAC1F,OAAiD,EACjD,cAAyC;QAGzC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;QACtC,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACzE,IAAI,SAAS,CAAC,SAAS,KAAK,2BAAe,CAAC,QAAQ,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC,0BAA0B,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,SAAS,CAAC,SAAS,KAAK,2BAAe,CAAC,OAAO,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC,yBAAyB,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,SAAS,CAAC,oBAAoB,EAAE,CAAC;YACjC,IAAI,gCAAoB,EAAE,CAAC,QAAQ,CAC/B,SAAS,CAAC,oBAAoB,EAC9B,WAAW,CAAC,CAAC,CAAC,EACd,SAAS,CACZ,CAAC;QACN,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,SAAS,GAAG,IAAI,gCAAoB,EAAE,CAAC;QAC7C,MAAM,QAAQ,GAAG,IAAI,iDAAuB,CAAC,cAAc,EAAE,SAAS,CAAC,cAAe,CAAC,CAAC;QACxF,MAAM,cAAc,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAEzC,0FAA0F;QAC1F,2FAA2F;QAC3F,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,MAAM,CACxC,IAAI,uBAAU,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,cAAc,CAAC,CACzG,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;QACrF,MAAM,eAAe,GAAG,aAAa,CAAC,kBAAkB,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,iCAAkB,EAAE,CAAC;QACxC,IAAI,SAAS,CAAC,qBAAqB,EAAE,CAAC;YAClC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QACrF,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC/B,MAAM,aAAa,GAAG,IAAI,wBAAY,CAClC,KAAK,EAAE,QAAkC,EAAiB,EAAE,CACxD,MAAM,CAAC,GAAG,CACN,GAAkB,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAC5E,EACL,CAAC,KAAe,EAAQ,EAAE,CACtB,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,aAAc,EAAE,KAAK,EAAE,SAAS,CAAC,EAClE,eAAe,EACf,CAAC,KAAa,EAAiB,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAC3D,CAAC;QACF,QAAQ,CAAC,aAAa,GAAG,aAAa,CAAC;QACvC,OAAO,aAAa,CAAC;IACzB,CAAC;IAED,qFAAqF;IAC7E,KAAK,CAAC,yBAAyB,CACnC,SAAwB;IACxB,2FAA2F;IAC3F,WAA+B;IAC/B,0FAA0F;IAC1F,OAAiD,EACjD,cAAyC;QAEzC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAU,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,gCAAoB,EAAE,CAAC;QAC7C,IAAI,SAAS,CAAC,oBAAoB,EAAE,CAAC;YACjC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAChC,IAAI,uBAAU,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CACzF,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7E,MAAM,eAAe,GAAG,aAAa,CAAC,kBAAkB,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,iCAAkB,EAAE,CAAC;QACxC,IAAI,SAAS,CAAC,qBAAqB,EAAE,CAAC;YAClC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;QACrF,CAAC;QACD,OAAO,IAAI,wBAAY,CACnB,KAAK,EAAE,QAAkC,EAAiB,EAAE,CACxD,MAAM,CAAC,GAAG,CACN,GAAkB,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAC5E,EACL,CAAC,KAAe,EAAQ,EAAE,CACtB,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,aAAc,EAAE,KAAK,EAAE,SAAS,CAAC,EAClE,eAAe,EACf,CAAC,KAAa,EAAiB,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAChE,CAAC;IACN,CAAC;IAED,qFAAqF;IAC7E,KAAK,CAAC,0BAA0B,CACpC,SAAwB;IACxB,2FAA2F;IAC3F,WAA+B;IAC/B,0FAA0F;IAC1F,OAAiD,EACjD,cAAyC;QAGzC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,gCAAoB,CAC1B,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,UAAU,gEAAgE,CAC/G,CAAC;QACN,CAAC;QACD,MAAM,SAAS,GAAG,SAAS,CAAC,SAAU,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,gCAAoB,EAAE,CAAC;QAC7C,IAAI,SAAS,CAAC,oBAAoB,EAAE,CAAC;YACjC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAAG,IAAI,iDAAuB,CAAC,cAAc,EAAE,SAAS,CAAC,cAAe,CAAC,CAAC;QACxF,MAAM,cAAc,GAAG,QAAQ,CAAC,QAAQ,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAChC,IAAI,uBAAU,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,cAAc,CAAC,CACzG,CAAC;QACF,IAAI,SAAS,CAAC,qBAAqB,EAAE,CAAC;YAClC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,qBAAqB,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACtF,CAAC;QACD,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,QAAQ,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,sBAAsB,CAChC,WAA8C,EAC9C,QAAkC;QAElC,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,OAAO;gBACR,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC/B,MAAM,IAAI,gCAAoB,CAAC,sCAAsC,CAAC,CAAC;gBAC3E,CAAC;gBACD,OAAO,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;YACnE,KAAK,SAAS;gBACV,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;oBAClB,MAAM,IAAI,gCAAoB,CAAC,wCAAwC,CAAC,CAAC;gBAC7E,CAAC;gBACD,OAAO,WAAW,CAAC,MAAM,CAAC,yBAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YACpE,KAAK,UAAU;gBACX,OAAO,WAAW,CAAC,KAAK,EAAE,CAAC;QACnC,CAAC;IACL,CAAC;IAED,8FAA8F;IACtF,qBAAqB,CACzB,SAAwB,EACxB,SAAkB;QAElB,IACI,OAAO,SAAS,KAAK,QAAQ;YAC7B,SAAS,KAAK,IAAI;YAClB,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,UAAU;YACrD,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,UAAU;YACrD,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,UAAU,EACxD,CAAC;YACC,OAAO,SAAqC,CAAC;QACjD,CAAC;QACD,MAAM,IAAI,gCAAoB,CAC1B,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,UAAU,sCAAsC,CACrF,CAAC;IACN,CAAC;IAED,mGAAmG;IAC3F,oBAAoB,CACxB,SAAwB,EACxB,SAAkB;QAElB,IACI,OAAO,SAAS,KAAK,QAAQ;YAC7B,SAAS,KAAK,IAAI;YAClB,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,UAAU;YACrD,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,oBAAoB,CAAC,KAAK,UAAU;YAClE,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,UAAU;YACrD,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,UAAU,EACxD,CAAC;YACC,OAAO,SAA8C,CAAC;QAC1D,CAAC;QACD,MAAM,IAAI,gCAAoB,CAC1B,GAAG,SAAS,CAAC,mBAAmB,IAAI,SAAS,CAAC,UAAU,kCAAkC,CAC7F,CAAC;IACN,CAAC;IAED,+FAA+F;IACvF,KAAK,CAAC,SAAS,CACnB,SAAwB,EACxB,UAAmB,EACnB,WAAsB;IACtB,8FAA8F;IAC9F,OAAiD,EACjD,cAAyC;QAGzC,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,MAAM,CACxC,IAAI,uBAAU,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,CAAC,CAChF,CAAC;QACF,OAAO,eAAe,CAAC,QAAQ,CAAC;IACpC,CAAC;CACJ,CAAA;AApTY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,wCAAyB,GAAE;IAEX,mBAAA,IAAA,kBAAM,EAAC,mCAAgB,CAAC,CAAA;6CAAgC,mCAAgB;GAD5E,gBAAgB,CAoT5B","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 =
|
|
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
|
-
|
|
53
|
+
getControllerImplementation(): ClassType<TController> | LazyController<TController>;
|
|
57
54
|
}
|
package/src/ApiRoutingFactory.js
CHANGED
|
@@ -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 (!
|
|
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
|
-
|
|
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) &&
|
|
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
|
-
|
|
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 @@
|
|
|
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"]}
|
package/src/ClassType.js
ADDED
|
@@ -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,51 @@
|
|
|
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
|
+
/** Import-only boundary: resolve in the existing app container and preserve its singleton graph. */
|
|
22
|
+
export declare class ApplicationImplementation<T extends object> {
|
|
23
|
+
readonly controller: Newable<T>;
|
|
24
|
+
constructor(controller: Newable<T>);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Eager identity/security metadata, deferred implementation. The loader imports one feature and
|
|
28
|
+
* returns declarative bindings; it must not open resources or perform request work. A failure is
|
|
29
|
+
* retained per router until restart. The required deadline bounds waiting, not JavaScript CPU.
|
|
30
|
+
*/
|
|
31
|
+
export declare class LazyController<T extends object> {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly filepath: string;
|
|
34
|
+
readonly timeoutMillis: number;
|
|
35
|
+
readonly load: () => Promise<LazyImplementation<T> | ApplicationImplementation<T>>;
|
|
36
|
+
constructor(name: string, filepath: string, timeoutMillis: number, load: () => Promise<LazyImplementation<T> | ApplicationImplementation<T>>);
|
|
37
|
+
}
|
|
38
|
+
/** One retained initialization promise per descriptor per router; callers keep their own context. */
|
|
39
|
+
export declare class LazyControllerState<T extends object> {
|
|
40
|
+
private readonly parent;
|
|
41
|
+
private readonly api;
|
|
42
|
+
private readonly definition;
|
|
43
|
+
private readonly timer;
|
|
44
|
+
private readonly methods;
|
|
45
|
+
private initialized?;
|
|
46
|
+
constructor(parent: Container, api: ClassType<T>, definition: LazyController<T>, timer: LazyFeatureTimer, methods: readonly string[]);
|
|
47
|
+
matchesApi(api: ClassType<object>): boolean;
|
|
48
|
+
resolve(): Promise<T>;
|
|
49
|
+
private initialize;
|
|
50
|
+
private build;
|
|
51
|
+
}
|