@webpieces/http-client-node 0.0.0-dev

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 ADDED
@@ -0,0 +1,28 @@
1
+ # @webpieces/http-client-node
2
+
3
+ The server-side HTTP client. The client and the controller share ONE API contract, exactly like
4
+ the Cloud Tasks twin — calling a method makes the HTTP request that contract describes.
5
+
6
+ ```ts
7
+ // inject the factory (a framework singleton), then one client per contract
8
+ const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));
9
+ const res = await server2.fetchValue(req); // inside a RequestContext
10
+ ```
11
+
12
+ - `svcName` is TYPICALLY the GCP Cloud Run service name, and MUST be when you omit `targetUrl`:
13
+ we look your service up in the same project and region and form the URL from the container's own
14
+ metadata, so you maintain no URL table. That works across demo/qa/prod as long as each
15
+ environment has its own projectId, which is typical.
16
+ - `targetUrl` overrides the lookup for another region, another project, or a non-Cloud-Run host.
17
+ `svcName` is then used only for logging.
18
+
19
+ `ClientHttpFactory` injects a `Provider<NodeProxyClient>` and calls `get()` per contract.
20
+ `NodeProxyClient` is bound TRANSIENT, so each client gets its own — the provider caches nothing,
21
+ the target's scope decides. (Bind the target `@provideFrameworkSingleton` instead and the very same
22
+ provider yields a lazy singleton.)
23
+
24
+ Calls made outside `RequestContext.run(...)` **throw**. An outbound call with no correlation id or
25
+ request-id chain loses the trace, and finding that out in production is worse than a loud error. A
26
+ top-level server filter normally establishes the scope for you.
27
+
28
+ The browser twin is [@webpieces/http-client-browser](../http-client-browser).
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@webpieces/http-client-node",
3
+ "version": "0.0.0-dev",
4
+ "description": "Server-side HTTP client for webpieces: inversify-wired, reads RequestContext directly, mints OIDC/shared-secret delivery auth, resolves Cloud Run URLs from a service name",
5
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "author": "Dean Hiller",
9
+ "license": "Apache-2.0",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/deanhiller/webpieces-ts.git",
13
+ "directory": "packages/http/http-client-node"
14
+ },
15
+ "keywords": [
16
+ "webpieces",
17
+ "http",
18
+ "client",
19
+ "node"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "dependencies": {
25
+ "@webpieces/core-context": "workspace:*",
26
+ "@webpieces/core-util": "workspace:*",
27
+ "@webpieces/gcp-identity": "workspace:*",
28
+ "@webpieces/http-client-core": "workspace:*",
29
+ "inversify": "7.10.4",
30
+ "reflect-metadata": "0.2.2"
31
+ }
32
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Per-client STATE for a server-side HTTP client — nothing else. A plain class; it extends nothing
3
+ * and is unrelated to the browser package's ClientConfig, because the two answer "what URL?"
4
+ * differently and share nothing worth a base class.
5
+ *
6
+ * Collaborators (RequestContextHeaders, Secrets) are NOT config: they are dependencies of
7
+ * {@link NodeProxyClient} and are shared by every client the factory builds. This is the RPC twin
8
+ * of cloudtasks-client's TaskClientConfig, and takes the same two fields.
9
+ */
10
+ export declare class ClientConfig {
11
+ /**
12
+ * TYPICALLY the GCP Cloud Run service name — and it MUST be the Cloud Run service name
13
+ * when you do not supply a `targetUrl`, because we derive the URL from it.
14
+ *
15
+ * We lookup your service in the same project, same region, and form the url from the
16
+ * container information unless you pass in a targetUrl, so you do not have to maintain
17
+ * targetUrls. This works across your demo, qa, prod environments as long as each
18
+ * environment is in its own projectId, which is typical.
19
+ *
20
+ * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
21
+ * name works.
22
+ */
23
+ readonly svcName: string;
24
+ /**
25
+ * Optional explicit base URL, for the cases lookup cannot describe: another region,
26
+ * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
27
+ */
28
+ readonly targetUrl?: string | undefined;
29
+ constructor(
30
+ /**
31
+ * TYPICALLY the GCP Cloud Run service name — and it MUST be the Cloud Run service name
32
+ * when you do not supply a `targetUrl`, because we derive the URL from it.
33
+ *
34
+ * We lookup your service in the same project, same region, and form the url from the
35
+ * container information unless you pass in a targetUrl, so you do not have to maintain
36
+ * targetUrls. This works across your demo, qa, prod environments as long as each
37
+ * environment is in its own projectId, which is typical.
38
+ *
39
+ * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
40
+ * name works.
41
+ */
42
+ svcName: string,
43
+ /**
44
+ * Optional explicit base URL, for the cases lookup cannot describe: another region,
45
+ * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
46
+ */
47
+ targetUrl?: string | undefined);
48
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientConfig = void 0;
4
+ /**
5
+ * Per-client STATE for a server-side HTTP client — nothing else. A plain class; it extends nothing
6
+ * and is unrelated to the browser package's ClientConfig, because the two answer "what URL?"
7
+ * differently and share nothing worth a base class.
8
+ *
9
+ * Collaborators (RequestContextHeaders, Secrets) are NOT config: they are dependencies of
10
+ * {@link NodeProxyClient} and are shared by every client the factory builds. This is the RPC twin
11
+ * of cloudtasks-client's TaskClientConfig, and takes the same two fields.
12
+ */
13
+ class ClientConfig {
14
+ svcName;
15
+ targetUrl;
16
+ constructor(
17
+ /**
18
+ * TYPICALLY the GCP Cloud Run service name — and it MUST be the Cloud Run service name
19
+ * when you do not supply a `targetUrl`, because we derive the URL from it.
20
+ *
21
+ * We lookup your service in the same project, same region, and form the url from the
22
+ * container information unless you pass in a targetUrl, so you do not have to maintain
23
+ * targetUrls. This works across your demo, qa, prod environments as long as each
24
+ * environment is in its own projectId, which is typical.
25
+ *
26
+ * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable
27
+ * name works.
28
+ */
29
+ svcName,
30
+ /**
31
+ * Optional explicit base URL, for the cases lookup cannot describe: another region,
32
+ * another project, or a host that is not Cloud Run at all. It wins over `svcName`.
33
+ */
34
+ targetUrl) {
35
+ this.svcName = svcName;
36
+ this.targetUrl = targetUrl;
37
+ }
38
+ }
39
+ exports.ClientConfig = ClientConfig;
40
+ //# sourceMappingURL=ClientConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/ClientConfig.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,MAAa,YAAY;IAcD;IAMA;IAnBpB;IACI;;;;;;;;;;;OAWG;IACa,OAAe;IAE/B;;;OAGG;IACa,SAAkB;QANlB,YAAO,GAAP,OAAO,CAAQ;QAMf,cAAS,GAAT,SAAS,CAAS;IACnC,CAAC;CACP;AAtBD,oCAsBC","sourcesContent":["/**\n * Per-client STATE for a server-side HTTP client — nothing else. A plain class; it extends nothing\n * and is unrelated to the browser package's ClientConfig, because the two answer \"what URL?\"\n * differently and share nothing worth a base class.\n *\n * Collaborators (RequestContextHeaders, Secrets) are NOT config: they are dependencies of\n * {@link NodeProxyClient} and are shared by every client the factory builds. This is the RPC twin\n * of cloudtasks-client's TaskClientConfig, and takes the same two fields.\n */\nexport class ClientConfig {\n constructor(\n /**\n * TYPICALLY the GCP Cloud Run service name — and it MUST be the Cloud Run service name\n * when you do not supply a `targetUrl`, because we derive the URL from it.\n *\n * We lookup your service in the same project, same region, and form the url from the\n * container information unless you pass in a targetUrl, so you do not have to maintain\n * targetUrls. This works across your demo, qa, prod environments as long as each\n * environment is in its own projectId, which is typical.\n *\n * When you DO supply a `targetUrl`, svcName is used only for logging, so any readable\n * name works.\n */\n public readonly svcName: string,\n\n /**\n * Optional explicit base URL, for the cases lookup cannot describe: another region,\n * another project, or a host that is not Cloud Run at all. It wins over `svcName`.\n */\n public readonly targetUrl?: string,\n ) {}\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import { Provider } from '@webpieces/core-context';
2
+ import type { ApiPrototype } from '@webpieces/http-client-core';
3
+ import { ClientConfig } from './ClientConfig';
4
+ import { NodeProxyClient } from './NodeProxyClient';
5
+ /**
6
+ * ClientHttpFactory - builds type-safe HTTP clients from API prototypes carrying
7
+ * @ApiPath/@Endpoint decorators. The SERVER-side factory.
8
+ *
9
+ * This is the client-side equivalent of ApiRoutingFactory:
10
+ * - Server routing: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers
11
+ * - Server client: ClientHttpFactory reads decorators -> generates HTTP requests from method calls
12
+ *
13
+ * Inject it and ask for a typed client per contract:
14
+ * ```typescript
15
+ * // same project + region as this container; the URL is derived, you maintain nothing
16
+ * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));
17
+ *
18
+ * // or point somewhere lookup cannot describe (other region/project, non-Cloud-Run)
19
+ * const legacy = factory.createClient(LegacyApi, new ClientConfig('legacy', 'https://legacy.corp'));
20
+ *
21
+ * const response = await server2.fetchValue(req); // inside a RequestContext
22
+ * ```
23
+ *
24
+ * Every client it builds shares one {@link NodeProxyClient} *shape* but never one instance: the
25
+ * injected `Provider<NodeProxyClient>` hands out a fresh one per contract, which `createClient`
26
+ * then `init`s. Their collaborators (RequestContextHeaders, Secrets) come from the container, so
27
+ * the whole dependency graph is visible in this package's design.html.
28
+ *
29
+ * Unlike @webpieces/http-client-browser this package is Node-only, so the factory IS the inversify
30
+ * entry point and the magic context is read straight from the RequestContext. A call made outside
31
+ * `RequestContext.run(...)` throws rather than silently dropping the trace.
32
+ */
33
+ export declare class ClientHttpFactory {
34
+ private readonly proxyClientProvider;
35
+ constructor(proxyClientProvider: Provider<NodeProxyClient>);
36
+ /**
37
+ * Create a type-safe HTTP client for one API contract.
38
+ *
39
+ * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators
40
+ * @param config - This client's state (its svcName, and optionally an explicit targetUrl)
41
+ */
42
+ createClient<T extends object>(apiPrototype: ApiPrototype<T>, config: ClientConfig): T;
43
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientHttpFactory = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const core_util_1 = require("@webpieces/core-util");
7
+ const core_context_1 = require("@webpieces/core-context");
8
+ const http_client_core_1 = require("@webpieces/http-client-core");
9
+ const NodeProxyClient_1 = require("./NodeProxyClient");
10
+ // Teach the container how to hand out fresh NodeProxyClients. NodeProxyClient is bound TRANSIENT
11
+ // (@provideFrameworkTransient), so each provider.get() constructs a new one.
12
+ (0, core_context_1.bindFrameworkProvider)(NodeProxyClient_1.NODE_PROXY_CLIENT_PROVIDER, NodeProxyClient_1.NodeProxyClient);
13
+ /**
14
+ * ClientHttpFactory - builds type-safe HTTP clients from API prototypes carrying
15
+ * @ApiPath/@Endpoint decorators. The SERVER-side factory.
16
+ *
17
+ * This is the client-side equivalent of ApiRoutingFactory:
18
+ * - Server routing: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers
19
+ * - Server client: ClientHttpFactory reads decorators -> generates HTTP requests from method calls
20
+ *
21
+ * Inject it and ask for a typed client per contract:
22
+ * ```typescript
23
+ * // same project + region as this container; the URL is derived, you maintain nothing
24
+ * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));
25
+ *
26
+ * // or point somewhere lookup cannot describe (other region/project, non-Cloud-Run)
27
+ * const legacy = factory.createClient(LegacyApi, new ClientConfig('legacy', 'https://legacy.corp'));
28
+ *
29
+ * const response = await server2.fetchValue(req); // inside a RequestContext
30
+ * ```
31
+ *
32
+ * Every client it builds shares one {@link NodeProxyClient} *shape* but never one instance: the
33
+ * injected `Provider<NodeProxyClient>` hands out a fresh one per contract, which `createClient`
34
+ * then `init`s. Their collaborators (RequestContextHeaders, Secrets) come from the container, so
35
+ * the whole dependency graph is visible in this package's design.html.
36
+ *
37
+ * Unlike @webpieces/http-client-browser this package is Node-only, so the factory IS the inversify
38
+ * entry point and the magic context is read straight from the RequestContext. A call made outside
39
+ * `RequestContext.run(...)` throws rather than silently dropping the trace.
40
+ */
41
+ let ClientHttpFactory = class ClientHttpFactory {
42
+ proxyClientProvider;
43
+ constructor(proxyClientProvider) {
44
+ this.proxyClientProvider = proxyClientProvider;
45
+ }
46
+ /**
47
+ * Create a type-safe HTTP client for one API contract.
48
+ *
49
+ * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators
50
+ * @param config - This client's state (its svcName, and optionally an explicit targetUrl)
51
+ */
52
+ createClient(apiPrototype, config) {
53
+ // Fresh instance per contract — NodeProxyClient is transient. init() binds it to this
54
+ // contract + target; the collaborators already came from the container.
55
+ const proxyClient = this.proxyClientProvider.get();
56
+ proxyClient.init(apiPrototype, config);
57
+ return (0, http_client_core_1.buildClientProxy)(apiPrototype, proxyClient);
58
+ }
59
+ };
60
+ exports.ClientHttpFactory = ClientHttpFactory;
61
+ exports.ClientHttpFactory = ClientHttpFactory = tslib_1.__decorate([
62
+ (0, core_util_1.DocumentDesign)(),
63
+ (0, core_context_1.provideFrameworkSingleton)(),
64
+ (0, inversify_1.injectable)(),
65
+ tslib_1.__param(0, (0, inversify_1.inject)(NodeProxyClient_1.NODE_PROXY_CLIENT_PROVIDER)),
66
+ tslib_1.__metadata("design:paramtypes", [core_context_1.Provider])
67
+ ], ClientHttpFactory);
68
+ //# sourceMappingURL=ClientHttpFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientHttpFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/ClientHttpFactory.ts"],"names":[],"mappings":";;;;AAAA,yCAA+C;AAC/C,oDAAsD;AACtD,0DAAqG;AAErG,kEAA+D;AAE/D,uDAAgF;AAEhF,iGAAiG;AACjG,6EAA6E;AAC7E,IAAA,oCAAqB,EAAC,4CAA0B,EAAE,iCAAe,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAE+B;IADzD,YACyD,mBAA8C;QAA9C,wBAAmB,GAAnB,mBAAmB,CAA2B;IACpG,CAAC;IAEJ;;;;;OAKG;IACH,YAAY,CAAmB,YAA6B,EAAE,MAAoB;QAC9E,sFAAsF;QACtF,wEAAwE;QACxE,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC;QACnD,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO,IAAA,mCAAgB,EAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC;CACJ,CAAA;AAlBY,8CAAiB;4BAAjB,iBAAiB;IAH7B,IAAA,0BAAc,GAAE;IAChB,IAAA,wCAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;IAGJ,mBAAA,IAAA,kBAAM,EAAC,4CAA0B,CAAC,CAAA;6CAAuC,uBAAQ;GAF7E,iBAAiB,CAkB7B","sourcesContent":["import { inject, injectable } from 'inversify';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { Provider, bindFrameworkProvider, provideFrameworkSingleton } from '@webpieces/core-context';\nimport type { ApiPrototype } from '@webpieces/http-client-core';\nimport { buildClientProxy } from '@webpieces/http-client-core';\nimport { ClientConfig } from './ClientConfig';\nimport { NODE_PROXY_CLIENT_PROVIDER, NodeProxyClient } from './NodeProxyClient';\n\n// Teach the container how to hand out fresh NodeProxyClients. NodeProxyClient is bound TRANSIENT\n// (@provideFrameworkTransient), so each provider.get() constructs a new one.\nbindFrameworkProvider(NODE_PROXY_CLIENT_PROVIDER, NodeProxyClient);\n\n/**\n * ClientHttpFactory - builds type-safe HTTP clients from API prototypes carrying\n * @ApiPath/@Endpoint decorators. The SERVER-side factory.\n *\n * This is the client-side equivalent of ApiRoutingFactory:\n * - Server routing: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers\n * - Server client: ClientHttpFactory reads decorators -> generates HTTP requests from method calls\n *\n * Inject it and ask for a typed client per contract:\n * ```typescript\n * // same project + region as this container; the URL is derived, you maintain nothing\n * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));\n *\n * // or point somewhere lookup cannot describe (other region/project, non-Cloud-Run)\n * const legacy = factory.createClient(LegacyApi, new ClientConfig('legacy', 'https://legacy.corp'));\n *\n * const response = await server2.fetchValue(req); // inside a RequestContext\n * ```\n *\n * Every client it builds shares one {@link NodeProxyClient} *shape* but never one instance: the\n * injected `Provider<NodeProxyClient>` hands out a fresh one per contract, which `createClient`\n * then `init`s. Their collaborators (RequestContextHeaders, Secrets) come from the container, so\n * the whole dependency graph is visible in this package's design.html.\n *\n * Unlike @webpieces/http-client-browser this package is Node-only, so the factory IS the inversify\n * entry point and the magic context is read straight from the RequestContext. A call made outside\n * `RequestContext.run(...)` throws rather than silently dropping the trace.\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\n@injectable()\nexport class ClientHttpFactory {\n constructor(\n @inject(NODE_PROXY_CLIENT_PROVIDER) private readonly proxyClientProvider: Provider<NodeProxyClient>,\n ) {}\n\n /**\n * Create a type-safe HTTP client for one API contract.\n *\n * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators\n * @param config - This client's state (its svcName, and optionally an explicit targetUrl)\n */\n createClient<T extends object>(apiPrototype: ApiPrototype<T>, config: ClientConfig): T {\n // Fresh instance per contract — NodeProxyClient is transient. init() binds it to this\n // contract + target; the collaborators already came from the container.\n const proxyClient = this.proxyClientProvider.get();\n proxyClient.init(apiPrototype, config);\n return buildClientProxy(apiPrototype, proxyClient);\n }\n}\n"]}
@@ -0,0 +1,60 @@
1
+ import { AuthMeta, RouteMetadata, Secrets } from '@webpieces/core-util';
2
+ import { RequestContextHeaders } from '@webpieces/core-context';
3
+ import { ApiPrototype, ProxyClient } from '@webpieces/http-client-core';
4
+ import { ClientConfig } from './ClientConfig';
5
+ /**
6
+ * The server-side {@link ProxyClient}. Everything a browser cannot do lives here: reading the
7
+ * ambient RequestContext, minting OIDC tokens, holding shared secrets, and recording test cases.
8
+ *
9
+ * TRANSIENT on purpose. Every `createClient(api, config)` needs its own instance, because `init()`
10
+ * binds one instance to exactly one API contract and one target. {@link ProxyClientProvider} hands
11
+ * them out — see its doc.
12
+ */
13
+ export declare class NodeProxyClient extends ProxyClient {
14
+ private readonly headers;
15
+ private readonly secrets?;
16
+ private config;
17
+ constructor(headers: RequestContextHeaders, secrets?: Secrets | undefined);
18
+ /** Bind this client to one API contract + target. */
19
+ init(apiPrototype: ApiPrototype<object>, config: ClientConfig): void;
20
+ /**
21
+ * Resolved per call, never at construction, so building a client stays synchronous. Every GCP
22
+ * metadata read beneath resolveTargetUrl is memoized process-wide, so only the first call pays.
23
+ */
24
+ protected resolveBaseUrl(): Promise<string>;
25
+ /** Straight from the RequestContext. Throws when there is no active request scope. */
26
+ protected outboundHeaders(): Map<string, string>;
27
+ /**
28
+ * Attach the outbound credential for the endpoint's AuthMode: an @AuthOidc bearer minted as
29
+ * this caller's runtime SA (audience = the callee base URL — the server verifies the signature
30
+ * + caller allow-list), or the @AuthSharedSecret(key) value THIS client sends from its bound
31
+ * {@link Secrets}. Both ride in the ONE `Authorization` header under their own scheme —
32
+ * `Bearer <oidc>` / `Webpieces <secret>` — which is never a context key, so it cannot leak onto
33
+ * the next hop. Never reads process.env.
34
+ */
35
+ protected attachOutboundAuth(route: RouteMetadata, baseUrl: string, httpHeaders: Record<string, string>): Promise<void>;
36
+ /**
37
+ * Test-case recording hook (mirror of Java HttpsJsonClientInvokeHandler): if a recorder is
38
+ * travelling in the magic context, capture this outbound call + its result so it becomes a mock
39
+ * in the generated test. Absent a recorder this is exactly the base behavior.
40
+ */
41
+ protected execute(route: RouteMetadata, requestDto: unknown, method: () => Promise<unknown>): Promise<unknown>;
42
+ /**
43
+ * Execute the call while recording it (args + masked ctx snapshot + result).
44
+ *
45
+ * The snapshot is a FIXTURE field, not a log line, so it is built here rather than handed down
46
+ * from the call path — a logging backend stamps its own fields and never sees this.
47
+ */
48
+ private recordCall;
49
+ /** A server can satisfy every auth mode, so nothing is rejected at bind time. */
50
+ protected assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string): void;
51
+ }
52
+ /**
53
+ * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.
54
+ * `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.
55
+ *
56
+ * Because NodeProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound
57
+ * `@provideFrameworkSingleton`, the very same Provider would instead hand back one lazily-created
58
+ * instance — the provider caches nothing, so the target's scope decides.)
59
+ */
60
+ export declare const NODE_PROXY_CLIENT_PROVIDER: unique symbol;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NODE_PROXY_CLIENT_PROVIDER = exports.NodeProxyClient = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const core_util_1 = require("@webpieces/core-util");
7
+ const core_context_1 = require("@webpieces/core-context");
8
+ const gcp_identity_1 = require("@webpieces/gcp-identity");
9
+ const http_client_core_1 = require("@webpieces/http-client-core");
10
+ /**
11
+ * The server-side {@link ProxyClient}. Everything a browser cannot do lives here: reading the
12
+ * ambient RequestContext, minting OIDC tokens, holding shared secrets, and recording test cases.
13
+ *
14
+ * TRANSIENT on purpose. Every `createClient(api, config)` needs its own instance, because `init()`
15
+ * binds one instance to exactly one API contract and one target. {@link ProxyClientProvider} hands
16
+ * them out — see its doc.
17
+ */
18
+ let NodeProxyClient = class NodeProxyClient extends http_client_core_1.ProxyClient {
19
+ headers;
20
+ secrets;
21
+ config;
22
+ constructor(headers, secrets) {
23
+ super();
24
+ this.headers = headers;
25
+ this.secrets = secrets;
26
+ }
27
+ /** Bind this client to one API contract + target. */
28
+ init(apiPrototype, config) {
29
+ this.config = config;
30
+ this.initRoutes(apiPrototype);
31
+ }
32
+ /**
33
+ * Resolved per call, never at construction, so building a client stays synchronous. Every GCP
34
+ * metadata read beneath resolveTargetUrl is memoized process-wide, so only the first call pays.
35
+ */
36
+ resolveBaseUrl() {
37
+ return (0, gcp_identity_1.resolveTargetUrl)(this.config.svcName, this.config.targetUrl);
38
+ }
39
+ /** Straight from the RequestContext. Throws when there is no active request scope. */
40
+ outboundHeaders() {
41
+ return this.headers.buildOutboundHeaders();
42
+ }
43
+ /**
44
+ * Attach the outbound credential for the endpoint's AuthMode: an @AuthOidc bearer minted as
45
+ * this caller's runtime SA (audience = the callee base URL — the server verifies the signature
46
+ * + caller allow-list), or the @AuthSharedSecret(key) value THIS client sends from its bound
47
+ * {@link Secrets}. Both ride in the ONE `Authorization` header under their own scheme —
48
+ * `Bearer <oidc>` / `Webpieces <secret>` — which is never a context key, so it cannot leak onto
49
+ * the next hop. Never reads process.env.
50
+ */
51
+ async attachOutboundAuth(route, baseUrl, httpHeaders) {
52
+ const mode = route.authMeta?.mode;
53
+ if (mode?.kind === 'oidc') {
54
+ httpHeaders['Authorization'] = `Bearer ${await (0, gcp_identity_1.mintIdToken)(baseUrl)}`;
55
+ }
56
+ else if (mode?.kind === 'shared-secret') {
57
+ const secret = this.secrets?.get(mode.secretKey);
58
+ if (!secret) {
59
+ throw new Error(`No shared secret configured for @AuthSharedSecret('${mode.secretKey}') endpoint ${route.methodName}`);
60
+ }
61
+ // Same header as a JWT/OIDC token, but its OWN scheme, so a secret can never be
62
+ // mistaken for a token nor accepted where one was expected.
63
+ httpHeaders['Authorization'] = `Webpieces ${secret}`;
64
+ }
65
+ }
66
+ /**
67
+ * Test-case recording hook (mirror of Java HttpsJsonClientInvokeHandler): if a recorder is
68
+ * travelling in the magic context, capture this outbound call + its result so it becomes a mock
69
+ * in the generated test. Absent a recorder this is exactly the base behavior.
70
+ */
71
+ // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary
72
+ async execute(route, requestDto,
73
+ // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary
74
+ method) {
75
+ const recorder = this.headers.findRecorder();
76
+ if (!recorder) {
77
+ return super.execute(route, requestDto, method);
78
+ }
79
+ return this.recordCall(recorder, route, requestDto, method);
80
+ }
81
+ /**
82
+ * Execute the call while recording it (args + masked ctx snapshot + result).
83
+ *
84
+ * The snapshot is a FIXTURE field, not a log line, so it is built here rather than handed down
85
+ * from the call path — a logging backend stamps its own fields and never sees this.
86
+ */
87
+ // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary
88
+ async recordCall(recorder, route, requestDto,
89
+ // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary
90
+ method) {
91
+ const ctxSnapshot = {};
92
+ for (const entry of core_context_1.RequestContext.buildLogFields().entries()) {
93
+ ctxSnapshot[entry[0]] = entry[1];
94
+ }
95
+ const recorded = new core_util_1.RecordedEndpoint(this.contractName(), route.methodName, [requestDto], ctxSnapshot);
96
+ recorder.addEndpointInfo(recorded);
97
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged
98
+ try {
99
+ const response = await super.execute(route, requestDto, method);
100
+ recorded.successResponse = response;
101
+ return response;
102
+ }
103
+ catch (err) {
104
+ const error = (0, core_util_1.toError)(err);
105
+ recorded.failureResponse = new core_util_1.RecordedError(error.name, error.message);
106
+ throw err;
107
+ }
108
+ }
109
+ /** A server can satisfy every auth mode, so nothing is rejected at bind time. */
110
+ assertEndpointSupported(_authMeta, _methodName) { }
111
+ };
112
+ exports.NodeProxyClient = NodeProxyClient;
113
+ exports.NodeProxyClient = NodeProxyClient = tslib_1.__decorate([
114
+ (0, core_context_1.provideFrameworkTransient)(),
115
+ (0, inversify_1.injectable)(),
116
+ tslib_1.__param(0, (0, inversify_1.inject)(core_context_1.RequestContextHeaders)),
117
+ tslib_1.__param(1, (0, inversify_1.optional)()),
118
+ tslib_1.__param(1, (0, inversify_1.inject)(core_util_1.Secrets)),
119
+ tslib_1.__metadata("design:paramtypes", [core_context_1.RequestContextHeaders,
120
+ core_util_1.Secrets])
121
+ ], NodeProxyClient);
122
+ /**
123
+ * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.
124
+ * `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.
125
+ *
126
+ * Because NodeProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound
127
+ * `@provideFrameworkSingleton`, the very same Provider would instead hand back one lazily-created
128
+ * instance — the provider caches nothing, so the target's scope decides.)
129
+ */
130
+ // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T
131
+ exports.NODE_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<NodeProxyClient>');
132
+ //# sourceMappingURL=NodeProxyClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeProxyClient.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/NodeProxyClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable, optional } from 'inversify';\nimport {\n AuthMeta,\n RecordedEndpoint,\n RecordedError,\n RouteMetadata,\n Secrets,\n TestCaseRecorder,\n toError,\n} from '@webpieces/core-util';\nimport { RequestContext, RequestContextHeaders, provideFrameworkTransient } from '@webpieces/core-context';\nimport { mintIdToken, resolveTargetUrl } from '@webpieces/gcp-identity';\nimport { ApiPrototype, ProxyClient } from '@webpieces/http-client-core';\nimport { ClientConfig } from './ClientConfig';\n\n/**\n * The server-side {@link ProxyClient}. Everything a browser cannot do lives here: reading the\n * ambient RequestContext, minting OIDC tokens, holding shared secrets, and recording test cases.\n *\n * TRANSIENT on purpose. Every `createClient(api, config)` needs its own instance, because `init()`\n * binds one instance to exactly one API contract and one target. {@link ProxyClientProvider} hands\n * them out — see its doc.\n */\n@provideFrameworkTransient()\n@injectable()\nexport class NodeProxyClient extends ProxyClient {\n private config!: ClientConfig;\n\n constructor(\n @inject(RequestContextHeaders) private readonly headers: RequestContextHeaders,\n // @optional: only @AuthSharedSecret endpoints need it; the client sends its bound value.\n @optional() @inject(Secrets) private readonly secrets?: Secrets,\n ) {\n super();\n }\n\n /** Bind this client to one API contract + target. */\n init(apiPrototype: ApiPrototype<object>, config: ClientConfig): void {\n this.config = config;\n this.initRoutes(apiPrototype);\n }\n\n /**\n * Resolved per call, never at construction, so building a client stays synchronous. Every GCP\n * metadata read beneath resolveTargetUrl is memoized process-wide, so only the first call pays.\n */\n protected override resolveBaseUrl(): Promise<string> {\n return resolveTargetUrl(this.config.svcName, this.config.targetUrl);\n }\n\n /** Straight from the RequestContext. Throws when there is no active request scope. */\n protected override outboundHeaders(): Map<string, string> {\n return this.headers.buildOutboundHeaders();\n }\n\n /**\n * Attach the outbound credential for the endpoint's AuthMode: an @AuthOidc bearer minted as\n * this caller's runtime SA (audience = the callee base URL — the server verifies the signature\n * + caller allow-list), or the @AuthSharedSecret(key) value THIS client sends from its bound\n * {@link Secrets}. Both ride in the ONE `Authorization` header under their own scheme —\n * `Bearer <oidc>` / `Webpieces <secret>` — which is never a context key, so it cannot leak onto\n * the next hop. Never reads process.env.\n */\n protected override async attachOutboundAuth(\n route: RouteMetadata,\n baseUrl: string,\n httpHeaders: Record<string, string>,\n ): Promise<void> {\n const mode = route.authMeta?.mode;\n if (mode?.kind === 'oidc') {\n httpHeaders['Authorization'] = `Bearer ${await mintIdToken(baseUrl)}`;\n } else if (mode?.kind === 'shared-secret') {\n const secret = this.secrets?.get(mode.secretKey);\n if (!secret) {\n throw new Error(\n `No shared secret configured for @AuthSharedSecret('${mode.secretKey}') endpoint ${route.methodName}`,\n );\n }\n // Same header as a JWT/OIDC token, but its OWN scheme, so a secret can never be\n // mistaken for a token nor accepted where one was expected.\n httpHeaders['Authorization'] = `Webpieces ${secret}`;\n }\n }\n\n /**\n * Test-case recording hook (mirror of Java HttpsJsonClientInvokeHandler): if a recorder is\n * travelling in the magic context, capture this outbound call + its result so it becomes a mock\n * in the generated test. Absent a recorder this is exactly the base behavior.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n protected override async execute(\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n const recorder = this.headers.findRecorder();\n if (!recorder) {\n return super.execute(route, requestDto, method);\n }\n return this.recordCall(recorder, route, requestDto, method);\n }\n\n /**\n * Execute the call while recording it (args + masked ctx snapshot + result).\n *\n * The snapshot is a FIXTURE field, not a log line, so it is built here rather than handed down\n * from the call path — a logging backend stamps its own fields and never sees this.\n */\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n private async recordCall(\n recorder: TestCaseRecorder,\n route: RouteMetadata,\n requestDto: unknown,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n method: () => Promise<unknown>,\n // webpieces-disable no-any-unknown -- DTO types are erased at the proxy boundary\n ): Promise<unknown> {\n const ctxSnapshot: Record<string, string> = {};\n for (const entry of RequestContext.buildLogFields().entries()) {\n ctxSnapshot[entry[0]] = entry[1];\n }\n const recorded = new RecordedEndpoint(this.contractName(), route.methodName, [requestDto], ctxSnapshot);\n recorder.addEndpointInfo(recorded);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- capture failure into the recording, then rethrow unchanged\n try {\n const response = await super.execute(route, requestDto, method);\n recorded.successResponse = response;\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n recorded.failureResponse = new RecordedError(error.name, error.message);\n throw err;\n }\n }\n\n /** A server can satisfy every auth mode, so nothing is rejected at bind time. */\n protected override assertEndpointSupported(_authMeta: AuthMeta | undefined, _methodName: string): void {}\n}\n\n/**\n * DI token for the `Provider<NodeProxyClient>` that hands out RPC clients — one per API contract.\n * `Provider<T>` is erased at runtime, so it cannot be its own token; this Symbol names T.\n *\n * Because NodeProxyClient is bound TRANSIENT, every `get()` constructs a new one. (Were it bound\n * `@provideFrameworkSingleton`, the very same Provider would instead hand back one lazily-created\n * instance — the provider caches nothing, so the target's scope decides.)\n */\n// webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; the Symbol names T\nexport const NODE_PROXY_CLIENT_PROVIDER = Symbol.for('Provider<NodeProxyClient>');\n"]}
package/src/index.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @webpieces/http-client-node
3
+ *
4
+ * The SERVER-side HTTP client. Reads an API contract's decorators and generates type-safe HTTP
5
+ * clients from it — the same contract the callee's controller implements.
6
+ *
7
+ * Node-only, so unlike @webpieces/http-client-browser it is fully inversify-wired and reads the
8
+ * magic context straight out of the AsyncLocalStorage-backed RequestContext. There is no
9
+ * ContextReader indirection, because a server has exactly one right answer, and a call made
10
+ * OUTSIDE `RequestContext.run(...)` throws instead of silently dropping the trace.
11
+ *
12
+ * Usage:
13
+ * ```typescript
14
+ * import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client-node';
15
+ *
16
+ * // inject the factory, then one client per contract
17
+ * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));
18
+ * const response = await server2.fetchValue(req);
19
+ * ```
20
+ */
21
+ export { ClientHttpFactory } from './ClientHttpFactory';
22
+ export { NodeProxyClient, NODE_PROXY_CLIENT_PROVIDER } from './NodeProxyClient';
23
+ export { ClientConfig } from './ClientConfig';
24
+ export { ProxyClient, ClientErrorTranslator } from '@webpieces/http-client-core';
25
+ export type { ApiPrototype } from '@webpieces/http-client-core';
package/src/index.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * @webpieces/http-client-node
4
+ *
5
+ * The SERVER-side HTTP client. Reads an API contract's decorators and generates type-safe HTTP
6
+ * clients from it — the same contract the callee's controller implements.
7
+ *
8
+ * Node-only, so unlike @webpieces/http-client-browser it is fully inversify-wired and reads the
9
+ * magic context straight out of the AsyncLocalStorage-backed RequestContext. There is no
10
+ * ContextReader indirection, because a server has exactly one right answer, and a call made
11
+ * OUTSIDE `RequestContext.run(...)` throws instead of silently dropping the trace.
12
+ *
13
+ * Usage:
14
+ * ```typescript
15
+ * import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client-node';
16
+ *
17
+ * // inject the factory, then one client per contract
18
+ * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));
19
+ * const response = await server2.fetchValue(req);
20
+ * ```
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.ClientErrorTranslator = exports.ProxyClient = exports.ClientConfig = exports.NODE_PROXY_CLIENT_PROVIDER = exports.NodeProxyClient = exports.ClientHttpFactory = void 0;
24
+ var ClientHttpFactory_1 = require("./ClientHttpFactory");
25
+ Object.defineProperty(exports, "ClientHttpFactory", { enumerable: true, get: function () { return ClientHttpFactory_1.ClientHttpFactory; } });
26
+ var NodeProxyClient_1 = require("./NodeProxyClient");
27
+ Object.defineProperty(exports, "NodeProxyClient", { enumerable: true, get: function () { return NodeProxyClient_1.NodeProxyClient; } });
28
+ Object.defineProperty(exports, "NODE_PROXY_CLIENT_PROVIDER", { enumerable: true, get: function () { return NodeProxyClient_1.NODE_PROXY_CLIENT_PROVIDER; } });
29
+ var ClientConfig_1 = require("./ClientConfig");
30
+ Object.defineProperty(exports, "ClientConfig", { enumerable: true, get: function () { return ClientConfig_1.ClientConfig; } });
31
+ // The isomorphic engine, re-exported so a server app needs one import.
32
+ var http_client_core_1 = require("@webpieces/http-client-core");
33
+ Object.defineProperty(exports, "ProxyClient", { enumerable: true, get: function () { return http_client_core_1.ProxyClient; } });
34
+ Object.defineProperty(exports, "ClientErrorTranslator", { enumerable: true, get: function () { return http_client_core_1.ClientErrorTranslator; } });
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client-node/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAEH,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,qDAAgF;AAAvE,kHAAA,eAAe,OAAA;AAAE,6HAAA,0BAA0B,OAAA;AACpD,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,uEAAuE;AACvE,gEAAiF;AAAxE,+GAAA,WAAW,OAAA;AAAE,yHAAA,qBAAqB,OAAA","sourcesContent":["/**\n * @webpieces/http-client-node\n *\n * The SERVER-side HTTP client. Reads an API contract's decorators and generates type-safe HTTP\n * clients from it — the same contract the callee's controller implements.\n *\n * Node-only, so unlike @webpieces/http-client-browser it is fully inversify-wired and reads the\n * magic context straight out of the AsyncLocalStorage-backed RequestContext. There is no\n * ContextReader indirection, because a server has exactly one right answer, and a call made\n * OUTSIDE `RequestContext.run(...)` throws instead of silently dropping the trace.\n *\n * Usage:\n * ```typescript\n * import { ClientHttpFactory, ClientConfig } from '@webpieces/http-client-node';\n *\n * // inject the factory, then one client per contract\n * const server2 = factory.createClient(Server2Api, new ClientConfig('server2'));\n * const response = await server2.fetchValue(req);\n * ```\n */\n\nexport { ClientHttpFactory } from './ClientHttpFactory';\nexport { NodeProxyClient, NODE_PROXY_CLIENT_PROVIDER } from './NodeProxyClient';\nexport { ClientConfig } from './ClientConfig';\n\n// The isomorphic engine, re-exported so a server app needs one import.\nexport { ProxyClient, ClientErrorTranslator } from '@webpieces/http-client-core';\nexport type { ApiPrototype } from '@webpieces/http-client-core';\n"]}