@webpieces/http-client 0.2.17 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ClientFactory.d.ts +57 -2
- package/src/ClientFactory.js +110 -45
- package/src/ClientFactory.js.map +1 -1
- package/src/ContextMgr.d.ts +66 -0
- package/src/ContextMgr.js +81 -0
- package/src/ContextMgr.js.map +1 -0
- package/src/ContextReader.d.ts +44 -0
- package/src/ContextReader.js +62 -0
- package/src/ContextReader.js.map +1 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +7 -1
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
4
4
|
"description": "HTTP client for WebPieces framework",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/http-api": "0.2.
|
|
24
|
+
"@webpieces/http-api": "0.2.21"
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/ClientFactory.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { RouteMetadata, HeaderMethods, LogApiCall } from '@webpieces/http-api';
|
|
2
|
+
import { ContextMgr } from './ContextMgr';
|
|
1
3
|
/**
|
|
2
4
|
* Configuration options for HTTP client.
|
|
3
5
|
*/
|
|
4
6
|
export declare class ClientConfig {
|
|
5
7
|
/** Base URL for all requests (e.g., 'http://localhost:3000') */
|
|
6
8
|
baseUrl: string;
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Optional context manager for automatic header propagation.
|
|
11
|
+
* When provided, headers will be read from the ContextReader and added to requests.
|
|
12
|
+
*/
|
|
13
|
+
contextMgr?: ContextMgr;
|
|
14
|
+
constructor(baseUrl: string, contextMgr?: ContextMgr);
|
|
8
15
|
}
|
|
9
16
|
/**
|
|
10
17
|
* Creates a type-safe HTTP client from an API interface prototype.
|
|
@@ -22,8 +29,56 @@ export declare class ClientConfig {
|
|
|
22
29
|
*
|
|
23
30
|
* @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)
|
|
24
31
|
* @param config - Client configuration with baseUrl
|
|
32
|
+
* @param logApiCall - Optional LogApiCall instance (creates new one if not provided)
|
|
25
33
|
* @returns A proxy object that implements the API interface
|
|
26
34
|
*/
|
|
27
35
|
export declare function createClient<T extends object>(apiPrototype: Function & {
|
|
28
36
|
prototype: T;
|
|
29
|
-
}, config: ClientConfig): T;
|
|
37
|
+
}, config: ClientConfig, contextMgr?: ContextMgr): T;
|
|
38
|
+
/**
|
|
39
|
+
* ProxyClient - HTTP client implementation with logging.
|
|
40
|
+
*
|
|
41
|
+
* This class handles:
|
|
42
|
+
* - Making HTTP requests based on route metadata
|
|
43
|
+
* - Header propagation via ContextMgr
|
|
44
|
+
* - Logging via LogApiCall
|
|
45
|
+
* - Error translation via ClientErrorTranslator
|
|
46
|
+
*
|
|
47
|
+
* LogApiCall is injected for consistent logging across the framework.
|
|
48
|
+
*/
|
|
49
|
+
export declare class ProxyClient {
|
|
50
|
+
private config;
|
|
51
|
+
private logApiCall;
|
|
52
|
+
private headerMethods;
|
|
53
|
+
private contextMgr?;
|
|
54
|
+
private routeMap;
|
|
55
|
+
constructor(config: ClientConfig, logApiCall: LogApiCall, headerMethods: HeaderMethods, routes: RouteMetadata[], contextMgr?: ContextMgr | undefined);
|
|
56
|
+
/**
|
|
57
|
+
* Get route metadata for a method name.
|
|
58
|
+
* @throws Error if no route found
|
|
59
|
+
*/
|
|
60
|
+
getRoute(methodName: string): RouteMetadata;
|
|
61
|
+
/**
|
|
62
|
+
* Make an HTTP request based on route metadata and arguments.
|
|
63
|
+
*
|
|
64
|
+
* Uses plain JSON.stringify/parse - no serialization library needed!
|
|
65
|
+
*
|
|
66
|
+
* Error handling:
|
|
67
|
+
* - Server: Throws HttpError → translates to ProtocolError JSON
|
|
68
|
+
* - Client: Receives ProtocolError JSON → reconstructs HttpError
|
|
69
|
+
*
|
|
70
|
+
* Automatic header propagation via ContextMgr:
|
|
71
|
+
* - If config.contextMgr is provided, reads headers from ContextReader
|
|
72
|
+
* - Adds headers to request before fetch()
|
|
73
|
+
*
|
|
74
|
+
* Logging via LogApiCall.execute():
|
|
75
|
+
* - [API-CLIENT-req] logs outgoing requests with headers (secure ones masked)
|
|
76
|
+
* - [API-CLIENT-resp-SUCCESS] logs successful responses
|
|
77
|
+
* - [API-CLIENT-resp-FAIL] logs failed responses
|
|
78
|
+
*/
|
|
79
|
+
makeRequest(route: RouteMetadata, args: any[]): Promise<any>;
|
|
80
|
+
/**
|
|
81
|
+
* Execute the fetch request and handle response.
|
|
82
|
+
*/
|
|
83
|
+
private executeFetch;
|
|
84
|
+
}
|
package/src/ClientFactory.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ClientConfig = void 0;
|
|
3
|
+
exports.ProxyClient = exports.ClientConfig = void 0;
|
|
4
4
|
exports.createClient = createClient;
|
|
5
5
|
const http_api_1 = require("@webpieces/http-api");
|
|
6
6
|
const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
|
|
@@ -8,8 +8,9 @@ const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
|
|
|
8
8
|
* Configuration options for HTTP client.
|
|
9
9
|
*/
|
|
10
10
|
class ClientConfig {
|
|
11
|
-
constructor(baseUrl) {
|
|
11
|
+
constructor(baseUrl, contextMgr) {
|
|
12
12
|
this.baseUrl = baseUrl;
|
|
13
|
+
this.contextMgr = contextMgr;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
exports.ClientConfig = ClientConfig;
|
|
@@ -29,9 +30,10 @@ exports.ClientConfig = ClientConfig;
|
|
|
29
30
|
*
|
|
30
31
|
* @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)
|
|
31
32
|
* @param config - Client configuration with baseUrl
|
|
33
|
+
* @param logApiCall - Optional LogApiCall instance (creates new one if not provided)
|
|
32
34
|
* @returns A proxy object that implements the API interface
|
|
33
35
|
*/
|
|
34
|
-
function createClient(apiPrototype, config) {
|
|
36
|
+
function createClient(apiPrototype, config, contextMgr) {
|
|
35
37
|
// Validate that the API prototype is marked with @ApiInterface
|
|
36
38
|
if (!(0, http_api_1.isApiInterface)(apiPrototype)) {
|
|
37
39
|
const className = apiPrototype.name || 'Unknown';
|
|
@@ -39,11 +41,9 @@ function createClient(apiPrototype, config) {
|
|
|
39
41
|
}
|
|
40
42
|
// Get all routes from the API prototype
|
|
41
43
|
const routes = (0, http_api_1.getRoutes)(apiPrototype);
|
|
42
|
-
// Create
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
routeMap.set(route.methodName, route);
|
|
46
|
-
}
|
|
44
|
+
// Create ProxyClient with injected LogApiCall (or create new one)
|
|
45
|
+
//CRAP our own little DI going on here as angular and nodejs are using 2 different DI systems!!! fuck!!
|
|
46
|
+
const proxyClient = new ProxyClient(config, new http_api_1.LogApiCall(), new http_api_1.HeaderMethods(), routes, contextMgr);
|
|
47
47
|
// Create a proxy that intercepts method calls and makes HTTP requests
|
|
48
48
|
return new Proxy({}, {
|
|
49
49
|
get(target, prop) {
|
|
@@ -52,54 +52,119 @@ function createClient(apiPrototype, config) {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
// Get the route metadata for this method
|
|
55
|
-
const route =
|
|
56
|
-
if (!route) {
|
|
57
|
-
throw new Error(`No route found for method ${prop}`);
|
|
58
|
-
}
|
|
55
|
+
const route = proxyClient.getRoute(prop);
|
|
59
56
|
// Return a function that makes the HTTP request
|
|
60
57
|
return async (...args) => {
|
|
61
|
-
return makeRequest(
|
|
58
|
+
return proxyClient.makeRequest(route, args);
|
|
62
59
|
};
|
|
63
60
|
},
|
|
64
61
|
});
|
|
65
62
|
}
|
|
66
63
|
/**
|
|
67
|
-
*
|
|
64
|
+
* ProxyClient - HTTP client implementation with logging.
|
|
68
65
|
*
|
|
69
|
-
*
|
|
66
|
+
* This class handles:
|
|
67
|
+
* - Making HTTP requests based on route metadata
|
|
68
|
+
* - Header propagation via ContextMgr
|
|
69
|
+
* - Logging via LogApiCall
|
|
70
|
+
* - Error translation via ClientErrorTranslator
|
|
70
71
|
*
|
|
71
|
-
*
|
|
72
|
-
* - Server: Throws HttpError → translates to ProtocolError JSON
|
|
73
|
-
* - Client: Receives ProtocolError JSON → reconstructs HttpError
|
|
72
|
+
* LogApiCall is injected for consistent logging across the framework.
|
|
74
73
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
class ProxyClient {
|
|
75
|
+
constructor(config, logApiCall, headerMethods, routes, contextMgr) {
|
|
76
|
+
this.config = config;
|
|
77
|
+
this.logApiCall = logApiCall;
|
|
78
|
+
this.headerMethods = headerMethods;
|
|
79
|
+
this.contextMgr = contextMgr;
|
|
80
|
+
// Create a map of method name -> route metadata for fast lookup
|
|
81
|
+
this.routeMap = new Map();
|
|
82
|
+
for (const route of routes) {
|
|
83
|
+
this.routeMap.set(route.methodName, route);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get route metadata for a method name.
|
|
88
|
+
* @throws Error if no route found
|
|
89
|
+
*/
|
|
90
|
+
getRoute(methodName) {
|
|
91
|
+
const route = this.routeMap.get(methodName);
|
|
92
|
+
if (!route) {
|
|
93
|
+
throw new Error(`No route found for method ${methodName}`);
|
|
94
|
+
}
|
|
95
|
+
return route;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Make an HTTP request based on route metadata and arguments.
|
|
99
|
+
*
|
|
100
|
+
* Uses plain JSON.stringify/parse - no serialization library needed!
|
|
101
|
+
*
|
|
102
|
+
* Error handling:
|
|
103
|
+
* - Server: Throws HttpError → translates to ProtocolError JSON
|
|
104
|
+
* - Client: Receives ProtocolError JSON → reconstructs HttpError
|
|
105
|
+
*
|
|
106
|
+
* Automatic header propagation via ContextMgr:
|
|
107
|
+
* - If config.contextMgr is provided, reads headers from ContextReader
|
|
108
|
+
* - Adds headers to request before fetch()
|
|
109
|
+
*
|
|
110
|
+
* Logging via LogApiCall.execute():
|
|
111
|
+
* - [API-CLIENT-req] logs outgoing requests with headers (secure ones masked)
|
|
112
|
+
* - [API-CLIENT-resp-SUCCESS] logs successful responses
|
|
113
|
+
* - [API-CLIENT-resp-FAIL] logs failed responses
|
|
114
|
+
*/
|
|
115
|
+
async makeRequest(route, args) {
|
|
116
|
+
const { httpMethod, path } = route;
|
|
117
|
+
// Build the full URL
|
|
118
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
119
|
+
// Build base headers for the HTTP request
|
|
120
|
+
const httpHeaders = {
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
};
|
|
123
|
+
// Add context headers to httpHeaders (unmasked, for actual HTTP request)
|
|
124
|
+
if (this.contextMgr) {
|
|
125
|
+
for (const header of this.contextMgr.headerSet) {
|
|
126
|
+
const value = this.contextMgr.contextReader.read(header);
|
|
127
|
+
if (value) {
|
|
128
|
+
httpHeaders[header.headerName] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Build masked headers map for logging
|
|
133
|
+
const headersForLogging = this.contextMgr
|
|
134
|
+
? this.headerMethods.buildSecureMapForLogs(this.contextMgr.headerSet, this.contextMgr.contextReader)
|
|
135
|
+
: new Map();
|
|
136
|
+
// Build request options
|
|
137
|
+
const options = {
|
|
138
|
+
method: httpMethod,
|
|
139
|
+
headers: httpHeaders,
|
|
140
|
+
};
|
|
141
|
+
// For POST/PUT/PATCH, include the body (first argument) as JSON
|
|
142
|
+
let requestDto;
|
|
143
|
+
if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {
|
|
144
|
+
requestDto = args[0];
|
|
145
|
+
// Plain JSON stringify - works with plain objects and our DateTimeDto classes
|
|
146
|
+
options.body = JSON.stringify(requestDto);
|
|
147
|
+
}
|
|
148
|
+
// Wrap fetch in a method for LogApiCall.execute
|
|
149
|
+
const method = async () => {
|
|
150
|
+
return this.executeFetch(url, options);
|
|
151
|
+
};
|
|
152
|
+
return await this.logApiCall.execute("CLIENT", route, requestDto, headersForLogging, method);
|
|
93
153
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Execute the fetch request and handle response.
|
|
156
|
+
*/
|
|
157
|
+
async executeFetch(url, options) {
|
|
158
|
+
const response = await fetch(url, options);
|
|
159
|
+
if (response.ok) {
|
|
160
|
+
return await response.json();
|
|
161
|
+
}
|
|
162
|
+
// Handle errors (non-2xx responses)
|
|
163
|
+
// Try to parse ProtocolError from response body
|
|
164
|
+
const protocolError = (await response.json());
|
|
165
|
+
// Reconstruct appropriate HttpError subclass and throw
|
|
166
|
+
throw ClientErrorTranslator_1.ClientErrorTranslator.translateError(response, protocolError);
|
|
98
167
|
}
|
|
99
|
-
// Handle errors (non-2xx responses)
|
|
100
|
-
// Try to parse ProtocolError from response body
|
|
101
|
-
const protocolError = (await response.json());
|
|
102
|
-
// Reconstruct appropriate HttpError subclass
|
|
103
|
-
throw ClientErrorTranslator_1.ClientErrorTranslator.translateError(response, protocolError);
|
|
104
168
|
}
|
|
169
|
+
exports.ProxyClient = ProxyClient;
|
|
105
170
|
//# sourceMappingURL=ClientFactory.js.map
|
package/src/ClientFactory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRoutes, isApiInterface, RouteMetadata, ProtocolError, HttpError } from '@webpieces/http-api';\nimport { ClientErrorTranslator } from './ClientErrorTranslator';\n\n/**\n * Configuration options for HTTP client.\n */\nexport class ClientConfig {\n /** Base URL for all requests (e.g., 'http://localhost:3000') */\n baseUrl: string;\n\n constructor(baseUrl: string) {\n this.baseUrl = baseUrl;\n }\n}\n\n/**\n * Creates a type-safe HTTP client from an API interface prototype.\n *\n * This is the client-side equivalent of RESTApiRoutes.\n * - Server: RESTApiRoutes reads decorators → routes HTTP requests to controllers\n * - Client: createClient reads decorators → generates HTTP requests from method calls\n *\n * Usage:\n * ```typescript\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createClient(SaveApiPrototype, config);\n * const response = await client.save({ query: 'test' }); // Type-safe!\n * ```\n *\n * @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)\n * @param config - Client configuration with baseUrl\n * @returns A proxy object that implements the API interface\n */\nexport function createClient<T extends object>(\n apiPrototype: Function & { prototype: T },\n config: ClientConfig,\n): T {\n // Validate that the API prototype is marked with @ApiInterface\n if (!isApiInterface(apiPrototype)) {\n const className = apiPrototype.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiInterface()`);\n }\n\n // Get all routes from the API prototype\n const routes = getRoutes(apiPrototype);\n\n // Create a map of method name -> route metadata for fast lookup\n const routeMap = new Map<string, RouteMetadata>();\n for (const route of routes) {\n routeMap.set(route.methodName, route);\n }\n\n // Create a proxy that intercepts method calls and makes HTTP requests\n return new Proxy({} as T, {\n get(target, prop: string | symbol) {\n // Only handle string properties (method names)\n if (typeof prop !== 'string') {\n return undefined;\n }\n\n // Get the route metadata for this method\n const route = routeMap.get(prop);\n if (!route) {\n throw new Error(`No route found for method ${prop}`);\n }\n\n // Return a function that makes the HTTP request\n return async (...args: any[]) => {\n return makeRequest(config, route, args);\n };\n },\n });\n}\n\n/**\n * Make an HTTP request based on route metadata and arguments.\n *\n * Uses plain JSON.stringify/parse - no serialization library needed!\n *\n * Error handling:\n * - Server: Throws HttpError → translates to ProtocolError JSON\n * - Client: Receives ProtocolError JSON → reconstructs HttpError\n */\nasync function makeRequest(config: ClientConfig, route: RouteMetadata, args: any[]): Promise<any> {\n const { httpMethod, path } = route;\n\n // Build the full URL\n const url = `${config.baseUrl}${path}`;\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // Build request options\n const options: RequestInit = {\n method: httpMethod,\n headers,\n };\n\n // For POST/PUT/PATCH, include the body (first argument) as JSON\n if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {\n const requestDto = args[0];\n // Plain JSON stringify - works with plain objects and our DateTimeDto classes\n options.body = JSON.stringify(requestDto);\n }\n\n // Make the HTTP request\n const response = await fetch(url, options);\n\n if(response.ok) {\n return response.json();\n }\n\n // Handle errors (non-2xx responses)\n\n // Try to parse ProtocolError from response body\n const protocolError = (await response.json()) as ProtocolError;\n // Reconstruct appropriate HttpError subclass\n throw ClientErrorTranslator.translateError(response, protocolError);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getRoutes,\n isApiInterface,\n RouteMetadata,\n ProtocolError,\n HeaderMethods,\n LogApiCall,\n} from '@webpieces/http-api';\nimport { ClientErrorTranslator } from './ClientErrorTranslator';\nimport { ContextMgr } from './ContextMgr';\n\n/**\n * Configuration options for HTTP client.\n */\nexport class ClientConfig {\n /** Base URL for all requests (e.g., 'http://localhost:3000') */\n baseUrl: string;\n\n /**\n * Optional context manager for automatic header propagation.\n * When provided, headers will be read from the ContextReader and added to requests.\n */\n contextMgr?: ContextMgr;\n\n constructor(baseUrl: string, contextMgr?: ContextMgr) {\n this.baseUrl = baseUrl;\n this.contextMgr = contextMgr;\n }\n}\n\n/**\n * Creates a type-safe HTTP client from an API interface prototype.\n *\n * This is the client-side equivalent of RESTApiRoutes.\n * - Server: RESTApiRoutes reads decorators → routes HTTP requests to controllers\n * - Client: createClient reads decorators → generates HTTP requests from method calls\n *\n * Usage:\n * ```typescript\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createClient(SaveApiPrototype, config);\n * const response = await client.save({ query: 'test' }); // Type-safe!\n * ```\n *\n * @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)\n * @param config - Client configuration with baseUrl\n * @param logApiCall - Optional LogApiCall instance (creates new one if not provided)\n * @returns A proxy object that implements the API interface\n */\nexport function createClient<T extends object>(\n apiPrototype: Function & { prototype: T },\n config: ClientConfig,\n contextMgr?: ContextMgr\n): T {\n // Validate that the API prototype is marked with @ApiInterface\n if (!isApiInterface(apiPrototype)) {\n const className = apiPrototype.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiInterface()`);\n }\n\n // Get all routes from the API prototype\n const routes = getRoutes(apiPrototype);\n\n // Create ProxyClient with injected LogApiCall (or create new one)\n //CRAP our own little DI going on here as angular and nodejs are using 2 different DI systems!!! fuck!!\n const proxyClient = new ProxyClient(\n config, new LogApiCall(), new HeaderMethods(), routes, contextMgr);\n\n // Create a proxy that intercepts method calls and makes HTTP requests\n return new Proxy({} as T, {\n get(target, prop: string | symbol) {\n // Only handle string properties (method names)\n if (typeof prop !== 'string') {\n return undefined;\n }\n\n // Get the route metadata for this method\n const route = proxyClient.getRoute(prop);\n\n // Return a function that makes the HTTP request\n return async (...args: any[]) => {\n return proxyClient.makeRequest(route, args);\n };\n },\n });\n}\n\n/**\n * ProxyClient - HTTP client implementation with logging.\n *\n * This class handles:\n * - Making HTTP requests based on route metadata\n * - Header propagation via ContextMgr\n * - Logging via LogApiCall\n * - Error translation via ClientErrorTranslator\n *\n * LogApiCall is injected for consistent logging across the framework.\n */\nexport class ProxyClient {\n private routeMap: Map<string, RouteMetadata>;\n\n constructor(\n private config: ClientConfig,\n private logApiCall: LogApiCall,\n private headerMethods: HeaderMethods,\n routes: RouteMetadata[],\n private contextMgr?: ContextMgr,\n ) {\n // Create a map of method name -> route metadata for fast lookup\n this.routeMap = new Map<string, RouteMetadata>();\n for (const route of routes) {\n this.routeMap.set(route.methodName, route);\n }\n }\n\n /**\n * Get route metadata for a method name.\n * @throws Error if no route found\n */\n getRoute(methodName: string): RouteMetadata {\n const route = this.routeMap.get(methodName);\n if (!route) {\n throw new Error(`No route found for method ${methodName}`);\n }\n return route;\n }\n\n /**\n * Make an HTTP request based on route metadata and arguments.\n *\n * Uses plain JSON.stringify/parse - no serialization library needed!\n *\n * Error handling:\n * - Server: Throws HttpError → translates to ProtocolError JSON\n * - Client: Receives ProtocolError JSON → reconstructs HttpError\n *\n * Automatic header propagation via ContextMgr:\n * - If config.contextMgr is provided, reads headers from ContextReader\n * - Adds headers to request before fetch()\n *\n * Logging via LogApiCall.execute():\n * - [API-CLIENT-req] logs outgoing requests with headers (secure ones masked)\n * - [API-CLIENT-resp-SUCCESS] logs successful responses\n * - [API-CLIENT-resp-FAIL] logs failed responses\n */\n async makeRequest(route: RouteMetadata, args: any[]): Promise<any> {\n const { httpMethod, path } = route;\n\n // Build the full URL\n const url = `${this.config.baseUrl}${path}`;\n\n\n // Build base headers for the HTTP request\n const httpHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n // Add context headers to httpHeaders (unmasked, for actual HTTP request)\n if (this.contextMgr) {\n for (const header of this.contextMgr.headerSet) {\n const value = this.contextMgr.contextReader.read(header);\n if (value) {\n httpHeaders[header.headerName] = value;\n }\n }\n }\n\n // Build masked headers map for logging\n const headersForLogging = this.contextMgr\n ? this.headerMethods.buildSecureMapForLogs(this.contextMgr.headerSet, this.contextMgr.contextReader)\n : new Map<string, any>();\n\n // Build request options\n const options: RequestInit = {\n method: httpMethod,\n headers: httpHeaders,\n };\n\n // For POST/PUT/PATCH, include the body (first argument) as JSON\n let requestDto: unknown;\n if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {\n requestDto = args[0];\n // Plain JSON stringify - works with plain objects and our DateTimeDto classes\n options.body = JSON.stringify(requestDto);\n }\n\n // Wrap fetch in a method for LogApiCall.execute\n const method = async (): Promise<unknown> => {\n return this.executeFetch(url, options);\n };\n\n return await this.logApiCall.execute(\"CLIENT\", route, requestDto, headersForLogging, method);\n }\n\n /**\n * Execute the fetch request and handle response.\n */\n private async executeFetch(url: string, options: RequestInit): Promise<unknown> {\n const response = await fetch(url, options);\n\n if (response.ok) {\n return await response.json();\n }\n\n // Handle errors (non-2xx responses)\n // Try to parse ProtocolError from response body\n const protocolError = (await response.json()) as ProtocolError;\n\n // Reconstruct appropriate HttpError subclass and throw\n throw ClientErrorTranslator.translateError(response, protocolError);\n }\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PlatformHeader, ContextReader } from '@webpieces/http-api';
|
|
2
|
+
/**
|
|
3
|
+
* ContextMgr - Manages context reader and header set for HTTP clients.
|
|
4
|
+
*
|
|
5
|
+
* Passed to createClient() via ClientConfig to enable automatic header propagation.
|
|
6
|
+
* Combines a ContextReader (how to read header values) with a header set
|
|
7
|
+
* (which headers to propagate).
|
|
8
|
+
*
|
|
9
|
+
* Example usage:
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Node.js server-side (reads from RequestContext)
|
|
12
|
+
* const contextMgr = new ContextMgr(
|
|
13
|
+
* new RequestContextReader(),
|
|
14
|
+
* [...WebpiecesCoreHeaders.getAllHeaders(), ...CompanyHeaders.getAllHeaders()]
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* // Browser client-side (reads from static map)
|
|
18
|
+
* const headers = new Map([['Authorization', getToken()]]);
|
|
19
|
+
* const contextMgr = new ContextMgr(
|
|
20
|
+
* new StaticContextReader(headers),
|
|
21
|
+
* [WebpiecesCoreHeaders.REQUEST_ID]
|
|
22
|
+
* );
|
|
23
|
+
*
|
|
24
|
+
* // Both cases
|
|
25
|
+
* const config = new ClientConfig('http://api.example.com', contextMgr);
|
|
26
|
+
* const client = createClient(SaveApiPrototype, config);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class ContextMgr {
|
|
30
|
+
/**
|
|
31
|
+
* The context reader that provides header values.
|
|
32
|
+
* Different implementations for Node.js vs browser.
|
|
33
|
+
*/
|
|
34
|
+
readonly contextReader: ContextReader;
|
|
35
|
+
/**
|
|
36
|
+
* The set of platform headers to read and propagate.
|
|
37
|
+
* Only headers in this set will be added to requests.
|
|
38
|
+
*/
|
|
39
|
+
readonly headerSet: PlatformHeader[];
|
|
40
|
+
constructor(
|
|
41
|
+
/**
|
|
42
|
+
* The context reader that provides header values.
|
|
43
|
+
* Different implementations for Node.js vs browser.
|
|
44
|
+
*/
|
|
45
|
+
contextReader: ContextReader,
|
|
46
|
+
/**
|
|
47
|
+
* The set of platform headers to read and propagate.
|
|
48
|
+
* Only headers in this set will be added to requests.
|
|
49
|
+
*/
|
|
50
|
+
headerSet: PlatformHeader[]);
|
|
51
|
+
/**
|
|
52
|
+
* Read a single header value by header name.
|
|
53
|
+
*
|
|
54
|
+
* Returns undefined if:
|
|
55
|
+
* - Header name not found in headerSet
|
|
56
|
+
* - Header has isWantTransferred=false
|
|
57
|
+
* - contextReader returns undefined/null/empty string
|
|
58
|
+
*
|
|
59
|
+
* This method is called by ClientFactory for each header in the headerSet,
|
|
60
|
+
* allowing a single loop instead of the previous double-loop pattern.
|
|
61
|
+
*
|
|
62
|
+
* @param headerName - The HTTP header name (e.g., 'x-request-id')
|
|
63
|
+
* @returns The header value, or undefined if not available/transferable
|
|
64
|
+
*/
|
|
65
|
+
read(headerName: string): string | undefined;
|
|
66
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextMgr = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* ContextMgr - Manages context reader and header set for HTTP clients.
|
|
6
|
+
*
|
|
7
|
+
* Passed to createClient() via ClientConfig to enable automatic header propagation.
|
|
8
|
+
* Combines a ContextReader (how to read header values) with a header set
|
|
9
|
+
* (which headers to propagate).
|
|
10
|
+
*
|
|
11
|
+
* Example usage:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Node.js server-side (reads from RequestContext)
|
|
14
|
+
* const contextMgr = new ContextMgr(
|
|
15
|
+
* new RequestContextReader(),
|
|
16
|
+
* [...WebpiecesCoreHeaders.getAllHeaders(), ...CompanyHeaders.getAllHeaders()]
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* // Browser client-side (reads from static map)
|
|
20
|
+
* const headers = new Map([['Authorization', getToken()]]);
|
|
21
|
+
* const contextMgr = new ContextMgr(
|
|
22
|
+
* new StaticContextReader(headers),
|
|
23
|
+
* [WebpiecesCoreHeaders.REQUEST_ID]
|
|
24
|
+
* );
|
|
25
|
+
*
|
|
26
|
+
* // Both cases
|
|
27
|
+
* const config = new ClientConfig('http://api.example.com', contextMgr);
|
|
28
|
+
* const client = createClient(SaveApiPrototype, config);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
class ContextMgr {
|
|
32
|
+
constructor(
|
|
33
|
+
/**
|
|
34
|
+
* The context reader that provides header values.
|
|
35
|
+
* Different implementations for Node.js vs browser.
|
|
36
|
+
*/
|
|
37
|
+
contextReader,
|
|
38
|
+
/**
|
|
39
|
+
* The set of platform headers to read and propagate.
|
|
40
|
+
* Only headers in this set will be added to requests.
|
|
41
|
+
*/
|
|
42
|
+
headerSet) {
|
|
43
|
+
this.contextReader = contextReader;
|
|
44
|
+
this.headerSet = headerSet;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read a single header value by header name.
|
|
48
|
+
*
|
|
49
|
+
* Returns undefined if:
|
|
50
|
+
* - Header name not found in headerSet
|
|
51
|
+
* - Header has isWantTransferred=false
|
|
52
|
+
* - contextReader returns undefined/null/empty string
|
|
53
|
+
*
|
|
54
|
+
* This method is called by ClientFactory for each header in the headerSet,
|
|
55
|
+
* allowing a single loop instead of the previous double-loop pattern.
|
|
56
|
+
*
|
|
57
|
+
* @param headerName - The HTTP header name (e.g., 'x-request-id')
|
|
58
|
+
* @returns The header value, or undefined if not available/transferable
|
|
59
|
+
*/
|
|
60
|
+
read(headerName) {
|
|
61
|
+
// Find the header definition in our set
|
|
62
|
+
const header = this.headerSet.find(h => h.headerName === headerName);
|
|
63
|
+
// Not in our header set - don't transfer
|
|
64
|
+
if (!header) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
// Header not marked for transfer - don't transfer
|
|
68
|
+
if (!header.isWantTransferred) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
// Read value from context reader
|
|
72
|
+
const value = this.contextReader.read(header);
|
|
73
|
+
// Only return non-empty values
|
|
74
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ContextMgr = ContextMgr;
|
|
81
|
+
//# sourceMappingURL=ContextMgr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextMgr.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ContextMgr.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAa,UAAU;IACnB;IACI;;;OAGG;IACa,aAA4B;IAE5C;;;OAGG;IACa,SAA2B;QAN3B,kBAAa,GAAb,aAAa,CAAe;QAM5B,cAAS,GAAT,SAAS,CAAkB;IAC5C,CAAC;IAEJ;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,UAAkB;QACnB,wCAAwC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC;QAErE,yCAAyC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,kDAAkD;QAClD,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC5B,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,iCAAiC;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE9C,+BAA+B;QAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACxD,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AArDD,gCAqDC","sourcesContent":["import { PlatformHeader, ContextReader } from '@webpieces/http-api';\n\n/**\n * ContextMgr - Manages context reader and header set for HTTP clients.\n *\n * Passed to createClient() via ClientConfig to enable automatic header propagation.\n * Combines a ContextReader (how to read header values) with a header set\n * (which headers to propagate).\n *\n * Example usage:\n * ```typescript\n * // Node.js server-side (reads from RequestContext)\n * const contextMgr = new ContextMgr(\n * new RequestContextReader(),\n * [...WebpiecesCoreHeaders.getAllHeaders(), ...CompanyHeaders.getAllHeaders()]\n * );\n *\n * // Browser client-side (reads from static map)\n * const headers = new Map([['Authorization', getToken()]]);\n * const contextMgr = new ContextMgr(\n * new StaticContextReader(headers),\n * [WebpiecesCoreHeaders.REQUEST_ID]\n * );\n *\n * // Both cases\n * const config = new ClientConfig('http://api.example.com', contextMgr);\n * const client = createClient(SaveApiPrototype, config);\n * ```\n */\nexport class ContextMgr {\n constructor(\n /**\n * The context reader that provides header values.\n * Different implementations for Node.js vs browser.\n */\n public readonly contextReader: ContextReader,\n\n /**\n * The set of platform headers to read and propagate.\n * Only headers in this set will be added to requests.\n */\n public readonly headerSet: PlatformHeader[]\n ) {}\n\n /**\n * Read a single header value by header name.\n *\n * Returns undefined if:\n * - Header name not found in headerSet\n * - Header has isWantTransferred=false\n * - contextReader returns undefined/null/empty string\n *\n * This method is called by ClientFactory for each header in the headerSet,\n * allowing a single loop instead of the previous double-loop pattern.\n *\n * @param headerName - The HTTP header name (e.g., 'x-request-id')\n * @returns The header value, or undefined if not available/transferable\n */\n read(headerName: string): string | undefined {\n // Find the header definition in our set\n const header = this.headerSet.find(h => h.headerName === headerName);\n\n // Not in our header set - don't transfer\n if (!header) {\n return undefined;\n }\n\n // Header not marked for transfer - don't transfer\n if (!header.isWantTransferred) {\n return undefined;\n }\n\n // Read value from context reader\n const value = this.contextReader.read(header);\n\n // Only return non-empty values\n if (value !== undefined && value !== null && value !== '') {\n return value;\n }\n\n return undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PlatformHeader, ContextReader } from '@webpieces/http-api';
|
|
2
|
+
/**
|
|
3
|
+
* StaticContextReader - Returns static header values from a Map.
|
|
4
|
+
*
|
|
5
|
+
* Useful for:
|
|
6
|
+
* - Browser environments where headers are manually managed
|
|
7
|
+
* - Testing with fixed header values
|
|
8
|
+
* - Angular services that read from localStorage/sessionStorage
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const headers = new Map<string, string>();
|
|
13
|
+
* headers.set('x-api-version', 'v1');
|
|
14
|
+
* headers.set('Authorization', getAuthToken());
|
|
15
|
+
* const reader = new StaticContextReader(headers);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class StaticContextReader implements ContextReader {
|
|
19
|
+
private headers;
|
|
20
|
+
constructor(headers: Map<string, string>);
|
|
21
|
+
read(header: PlatformHeader): string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* CompositeContextReader - Tries multiple readers in order.
|
|
25
|
+
*
|
|
26
|
+
* Later readers override earlier ones (last one wins).
|
|
27
|
+
* Useful for layered header sources:
|
|
28
|
+
* 1. RequestContext (base layer, from incoming request)
|
|
29
|
+
* 2. Config headers (middle layer, from ClientConfig)
|
|
30
|
+
* 3. Dynamic headers (top layer, runtime-computed values)
|
|
31
|
+
*
|
|
32
|
+
* Example:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const reader = new CompositeContextReader([
|
|
35
|
+
* new RequestContextReader(), // Try context first
|
|
36
|
+
* new StaticContextReader(config), // Fall back to config
|
|
37
|
+
* ]);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class CompositeContextReader implements ContextReader {
|
|
41
|
+
private readers;
|
|
42
|
+
constructor(readers: ContextReader[]);
|
|
43
|
+
read(header: PlatformHeader): string | undefined;
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompositeContextReader = exports.StaticContextReader = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* StaticContextReader - Returns static header values from a Map.
|
|
6
|
+
*
|
|
7
|
+
* Useful for:
|
|
8
|
+
* - Browser environments where headers are manually managed
|
|
9
|
+
* - Testing with fixed header values
|
|
10
|
+
* - Angular services that read from localStorage/sessionStorage
|
|
11
|
+
*
|
|
12
|
+
* Example:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const headers = new Map<string, string>();
|
|
15
|
+
* headers.set('x-api-version', 'v1');
|
|
16
|
+
* headers.set('Authorization', getAuthToken());
|
|
17
|
+
* const reader = new StaticContextReader(headers);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
class StaticContextReader {
|
|
21
|
+
constructor(headers) {
|
|
22
|
+
this.headers = headers;
|
|
23
|
+
}
|
|
24
|
+
read(header) {
|
|
25
|
+
return this.headers.get(header.headerName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.StaticContextReader = StaticContextReader;
|
|
29
|
+
/**
|
|
30
|
+
* CompositeContextReader - Tries multiple readers in order.
|
|
31
|
+
*
|
|
32
|
+
* Later readers override earlier ones (last one wins).
|
|
33
|
+
* Useful for layered header sources:
|
|
34
|
+
* 1. RequestContext (base layer, from incoming request)
|
|
35
|
+
* 2. Config headers (middle layer, from ClientConfig)
|
|
36
|
+
* 3. Dynamic headers (top layer, runtime-computed values)
|
|
37
|
+
*
|
|
38
|
+
* Example:
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const reader = new CompositeContextReader([
|
|
41
|
+
* new RequestContextReader(), // Try context first
|
|
42
|
+
* new StaticContextReader(config), // Fall back to config
|
|
43
|
+
* ]);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
class CompositeContextReader {
|
|
47
|
+
constructor(readers) {
|
|
48
|
+
this.readers = readers;
|
|
49
|
+
}
|
|
50
|
+
read(header) {
|
|
51
|
+
// Try readers in reverse order (last one wins/overrides)
|
|
52
|
+
for (let i = this.readers.length - 1; i >= 0; i--) {
|
|
53
|
+
const value = this.readers[i].read(header);
|
|
54
|
+
if (value !== undefined) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.CompositeContextReader = CompositeContextReader;
|
|
62
|
+
//# sourceMappingURL=ContextReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ContextReader.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAa,mBAAmB;IAC5B,YAAoB,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;IAAG,CAAC;IAEpD,IAAI,CAAC,MAAsB;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;CACJ;AAND,kDAMC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,sBAAsB;IAC/B,YAAoB,OAAwB;QAAxB,YAAO,GAAP,OAAO,CAAiB;IAAG,CAAC;IAEhD,IAAI,CAAC,MAAsB;QACvB,yDAAyD;QACzD,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAChD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtB,OAAO,KAAK,CAAC;YACjB,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AAbD,wDAaC","sourcesContent":["import { PlatformHeader, ContextReader } from '@webpieces/http-api';\n\n/**\n * StaticContextReader - Returns static header values from a Map.\n *\n * Useful for:\n * - Browser environments where headers are manually managed\n * - Testing with fixed header values\n * - Angular services that read from localStorage/sessionStorage\n *\n * Example:\n * ```typescript\n * const headers = new Map<string, string>();\n * headers.set('x-api-version', 'v1');\n * headers.set('Authorization', getAuthToken());\n * const reader = new StaticContextReader(headers);\n * ```\n */\nexport class StaticContextReader implements ContextReader {\n constructor(private headers: Map<string, string>) {}\n\n read(header: PlatformHeader): string | undefined {\n return this.headers.get(header.headerName);\n }\n}\n\n/**\n * CompositeContextReader - Tries multiple readers in order.\n *\n * Later readers override earlier ones (last one wins).\n * Useful for layered header sources:\n * 1. RequestContext (base layer, from incoming request)\n * 2. Config headers (middle layer, from ClientConfig)\n * 3. Dynamic headers (top layer, runtime-computed values)\n *\n * Example:\n * ```typescript\n * const reader = new CompositeContextReader([\n * new RequestContextReader(), // Try context first\n * new StaticContextReader(config), // Fall back to config\n * ]);\n * ```\n */\nexport class CompositeContextReader implements ContextReader {\n constructor(private readers: ContextReader[]) {}\n\n read(header: PlatformHeader): string | undefined {\n // Try readers in reverse order (last one wins/overrides)\n for (let i = this.readers.length - 1; i >= 0; i--) {\n const value = this.readers[i].read(header);\n if (value !== undefined) {\n return value;\n }\n }\n return undefined;\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -31,4 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
export { createClient, ClientConfig } from './ClientFactory';
|
|
33
33
|
export { ClientErrorTranslator } from './ClientErrorTranslator';
|
|
34
|
+
export { StaticContextReader, CompositeContextReader } from './ContextReader';
|
|
35
|
+
export { ContextMgr } from './ContextMgr';
|
|
36
|
+
export { ContextReader } from '@webpieces/http-api';
|
|
34
37
|
export { ApiInterface, Get, Post, Put, Delete, Patch, Path, ValidateImplementation, } from '@webpieces/http-api';
|
package/src/index.js
CHANGED
|
@@ -31,12 +31,18 @@
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
-
exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = exports.ClientErrorTranslator = exports.ClientConfig = exports.createClient = void 0;
|
|
34
|
+
exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = exports.ContextMgr = exports.CompositeContextReader = exports.StaticContextReader = exports.ClientErrorTranslator = exports.ClientConfig = exports.createClient = void 0;
|
|
35
35
|
var ClientFactory_1 = require("./ClientFactory");
|
|
36
36
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return ClientFactory_1.createClient; } });
|
|
37
37
|
Object.defineProperty(exports, "ClientConfig", { enumerable: true, get: function () { return ClientFactory_1.ClientConfig; } });
|
|
38
38
|
var ClientErrorTranslator_1 = require("./ClientErrorTranslator");
|
|
39
39
|
Object.defineProperty(exports, "ClientErrorTranslator", { enumerable: true, get: function () { return ClientErrorTranslator_1.ClientErrorTranslator; } });
|
|
40
|
+
// Context management for header propagation
|
|
41
|
+
var ContextReader_1 = require("./ContextReader");
|
|
42
|
+
Object.defineProperty(exports, "StaticContextReader", { enumerable: true, get: function () { return ContextReader_1.StaticContextReader; } });
|
|
43
|
+
Object.defineProperty(exports, "CompositeContextReader", { enumerable: true, get: function () { return ContextReader_1.CompositeContextReader; } });
|
|
44
|
+
var ContextMgr_1 = require("./ContextMgr");
|
|
45
|
+
Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function () { return ContextMgr_1.ContextMgr; } });
|
|
40
46
|
// Re-export API decorators for convenience (same as http-routing does)
|
|
41
47
|
var http_api_1 = require("@webpieces/http-api");
|
|
42
48
|
Object.defineProperty(exports, "ApiInterface", { enumerable: true, get: function () { return http_api_1.ApiInterface; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAEH,iDAA6D;AAApD,6GAAA,YAAY,OAAA;AAAE,6GAAA,YAAY,OAAA;AACnC,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAE9B,uEAAuE;AACvE,gDAS6B;AARzB,wGAAA,YAAY,OAAA;AACZ,+FAAA,GAAG,OAAA;AACH,gGAAA,IAAI,OAAA;AACJ,+FAAA,GAAG,OAAA;AACH,kGAAA,MAAM,OAAA;AACN,iGAAA,KAAK,OAAA;AACL,gGAAA,IAAI,OAAA","sourcesContent":["/**\n * @webpieces/http-client\n *\n * Client-side HTTP client generation package.\n * Reads API decorators and generates type-safe HTTP clients.\n *\n * This is the client-side counterpart to @webpieces/http-routing:\n * - Server (@webpieces/http-routing): API decorators → route HTTP to controllers\n * - Client (@webpieces/http-client): API decorators → generate HTTP from method calls\n *\n * Both packages depend on @webpieces/http-api for shared decorator definitions.\n *\n * Architecture:\n * ```\n * http-api (defines the contract)\n * ↑\n * ├── http-routing (server: contract → handlers)\n * └── http-client (client: contract → HTTP requests) ← YOU ARE HERE\n * ```\n *\n * Usage:\n * ```typescript\n * import { createClient, ClientConfig } from '@webpieces/http-client';\n * import { SaveApiPrototype } from './api/SaveApi';\n *\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createClient(SaveApiPrototype, config);\n *\n * const response = await client.save({ query: 'test' });\n * ```\n */\n\nexport { createClient, ClientConfig } from './ClientFactory';\nexport { ClientErrorTranslator } from './ClientErrorTranslator';\n\n// Re-export API decorators for convenience (same as http-routing does)\nexport {\n ApiInterface,\n Get,\n Post,\n Put,\n Delete,\n Patch,\n Path,\n ValidateImplementation,\n} from '@webpieces/http-api';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAEH,iDAA6D;AAApD,6GAAA,YAAY,OAAA;AAAE,6GAAA,YAAY,OAAA;AACnC,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAE9B,4CAA4C;AAC5C,iDAA8E;AAArE,oHAAA,mBAAmB,OAAA;AAAE,uHAAA,sBAAsB,OAAA;AACpD,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AAKnB,uEAAuE;AACvE,gDAS6B;AARzB,wGAAA,YAAY,OAAA;AACZ,+FAAA,GAAG,OAAA;AACH,gGAAA,IAAI,OAAA;AACJ,+FAAA,GAAG,OAAA;AACH,kGAAA,MAAM,OAAA;AACN,iGAAA,KAAK,OAAA;AACL,gGAAA,IAAI,OAAA","sourcesContent":["/**\n * @webpieces/http-client\n *\n * Client-side HTTP client generation package.\n * Reads API decorators and generates type-safe HTTP clients.\n *\n * This is the client-side counterpart to @webpieces/http-routing:\n * - Server (@webpieces/http-routing): API decorators → route HTTP to controllers\n * - Client (@webpieces/http-client): API decorators → generate HTTP from method calls\n *\n * Both packages depend on @webpieces/http-api for shared decorator definitions.\n *\n * Architecture:\n * ```\n * http-api (defines the contract)\n * ↑\n * ├── http-routing (server: contract → handlers)\n * └── http-client (client: contract → HTTP requests) ← YOU ARE HERE\n * ```\n *\n * Usage:\n * ```typescript\n * import { createClient, ClientConfig } from '@webpieces/http-client';\n * import { SaveApiPrototype } from './api/SaveApi';\n *\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createClient(SaveApiPrototype, config);\n *\n * const response = await client.save({ query: 'test' });\n * ```\n */\n\nexport { createClient, ClientConfig } from './ClientFactory';\nexport { ClientErrorTranslator } from './ClientErrorTranslator';\n\n// Context management for header propagation\nexport { StaticContextReader, CompositeContextReader } from './ContextReader';\nexport { ContextMgr } from './ContextMgr';\n\n// Re-export ContextReader interface from http-api for convenience\nexport { ContextReader } from '@webpieces/http-api';\n\n// Re-export API decorators for convenience (same as http-routing does)\nexport {\n ApiInterface,\n Get,\n Post,\n Put,\n Delete,\n Patch,\n Path,\n ValidateImplementation,\n} from '@webpieces/http-api';\n"]}
|