@webpieces/http-client 0.2.16 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-client",
3
- "version": "0.2.16",
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.16"
24
+ "@webpieces/http-api": "0.2.21"
25
25
  }
26
26
  }
@@ -0,0 +1,37 @@
1
+ import { ProtocolError } from '@webpieces/http-api';
2
+ /**
3
+ * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.
4
+ *
5
+ * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.
6
+ * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.
7
+ *
8
+ * Architecture:
9
+ * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON
10
+ * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError
11
+ *
12
+ * This achieves symmetric error handling - server throws typed exceptions,
13
+ * client receives typed exceptions.
14
+ */
15
+ export declare class ClientErrorTranslator {
16
+ /**
17
+ * Parse error response and reconstruct appropriate HttpError subclass.
18
+ *
19
+ * Maps HTTP status codes to error types (symmetric with server):
20
+ * - 400 → HttpBadRequestError (with field, guiAlertMessage)
21
+ * - 266 → HttpUserError (with errorCode) - 2xx code for user validation
22
+ * - 401 → HttpUnauthorizedError
23
+ * - 403 → HttpForbiddenError
24
+ * - 404 → HttpNotFoundError
25
+ * - 408 → HttpTimeoutError
26
+ * - 500 → HttpInternalServerError
27
+ * - 502 → HttpBadGatewayError
28
+ * - 504 → HttpGatewayTimeoutError
29
+ * - 598 → HttpVendorError (with waitSeconds) - custom status code
30
+ * - other → generic HttpError
31
+ *
32
+ * @param response - Fetch Response object
33
+ * @param protocolError - Parsed ProtocolError from response body
34
+ * @returns HttpError subclass instance
35
+ */
36
+ static translateError(response: Response, protocolError: ProtocolError): Error;
37
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ClientErrorTranslator = void 0;
4
+ const http_api_1 = require("@webpieces/http-api");
5
+ /**
6
+ * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.
7
+ *
8
+ * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.
9
+ * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.
10
+ *
11
+ * Architecture:
12
+ * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON
13
+ * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError
14
+ *
15
+ * This achieves symmetric error handling - server throws typed exceptions,
16
+ * client receives typed exceptions.
17
+ */
18
+ class ClientErrorTranslator {
19
+ /**
20
+ * Parse error response and reconstruct appropriate HttpError subclass.
21
+ *
22
+ * Maps HTTP status codes to error types (symmetric with server):
23
+ * - 400 → HttpBadRequestError (with field, guiAlertMessage)
24
+ * - 266 → HttpUserError (with errorCode) - 2xx code for user validation
25
+ * - 401 → HttpUnauthorizedError
26
+ * - 403 → HttpForbiddenError
27
+ * - 404 → HttpNotFoundError
28
+ * - 408 → HttpTimeoutError
29
+ * - 500 → HttpInternalServerError
30
+ * - 502 → HttpBadGatewayError
31
+ * - 504 → HttpGatewayTimeoutError
32
+ * - 598 → HttpVendorError (with waitSeconds) - custom status code
33
+ * - other → generic HttpError
34
+ *
35
+ * @param response - Fetch Response object
36
+ * @param protocolError - Parsed ProtocolError from response body
37
+ * @returns HttpError subclass instance
38
+ */
39
+ static translateError(response, protocolError) {
40
+ const statusCode = response.status;
41
+ const message = protocolError.message || response.statusText || 'Unknown error';
42
+ const subType = protocolError.subType;
43
+ // Map status codes to error types (symmetric with server's ExpressWrapper.handleError())
44
+ switch (statusCode) {
45
+ case 400:
46
+ return new http_api_1.HttpBadRequestError(message, protocolError.field, protocolError.guiAlertMessage);
47
+ case 266: // HttpUserError - 2xx code for user validation errors
48
+ return new http_api_1.HttpUserError(message, protocolError.errorCode);
49
+ case 401:
50
+ return new http_api_1.HttpUnauthorizedError(message, subType);
51
+ case 403:
52
+ return new http_api_1.HttpForbiddenError(message);
53
+ case 404:
54
+ return new http_api_1.HttpNotFoundError(message);
55
+ case 408:
56
+ return new http_api_1.HttpTimeoutError(message);
57
+ case 500:
58
+ return new http_api_1.HttpInternalServerError(message);
59
+ case 502:
60
+ return new http_api_1.HttpBadGatewayError(message);
61
+ case 504:
62
+ return new http_api_1.HttpGatewayTimeoutError(message);
63
+ case 598: // HttpVendorError - custom status code for vendor/external service errors
64
+ return new http_api_1.HttpVendorError(message, protocolError.waitSeconds);
65
+ default:
66
+ // Unknown status code - return generic HttpError
67
+ return new Error(` could not translate statusCode=${statusCode}`);
68
+ }
69
+ }
70
+ }
71
+ exports.ClientErrorTranslator = ClientErrorTranslator;
72
+ //# sourceMappingURL=ClientErrorTranslator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,kDAa6B;AAE7B;;;;;;;;;;;;GAYG;AACH,MAAa,qBAAqB;IAC9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,cAAc,CAAC,QAAkB,EAAE,aAA4B;QAClE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,IAAI,eAAe,CAAC;QAChF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC;QAEtC,yFAAyF;QACzF,QAAQ,UAAU,EAAE,CAAC;YACjB,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAmB,CAC1B,OAAO,EACP,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,eAAe,CAChC,CAAC;YAEN,KAAK,GAAG,EAAE,sDAAsD;gBAC5D,OAAO,IAAI,wBAAa,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;YAE/D,KAAK,GAAG;gBACJ,OAAO,IAAI,gCAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEvD,KAAK,GAAG;gBACJ,OAAO,IAAI,6BAAkB,CAAC,OAAO,CAAC,CAAC;YAE3C,KAAK,GAAG;gBACJ,OAAO,IAAI,4BAAiB,CAAC,OAAO,CAAC,CAAC;YAE1C,KAAK,GAAG;gBACJ,OAAO,IAAI,2BAAgB,CAAC,OAAO,CAAC,CAAC;YAEzC,KAAK,GAAG;gBACJ,OAAO,IAAI,kCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,KAAK,GAAG;gBACJ,OAAO,IAAI,kCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG,EAAE,0EAA0E;gBAChF,OAAO,IAAI,0BAAe,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;YAEnE;gBACI,iDAAiD;gBACjD,OAAO,IAAI,KAAK,CAAC,mCAAmC,UAAU,EAAE,CAAC,CAAC;QAC1E,CAAC;IACL,CAAC;CACJ;AAnED,sDAmEC","sourcesContent":["import {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpUserError,\n HttpVendorError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n} from '@webpieces/http-api';\n\n/**\n * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.\n *\n * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.\n * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.\n *\n * Architecture:\n * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON\n * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError\n *\n * This achieves symmetric error handling - server throws typed exceptions,\n * client receives typed exceptions.\n */\nexport class ClientErrorTranslator {\n /**\n * Parse error response and reconstruct appropriate HttpError subclass.\n *\n * Maps HTTP status codes to error types (symmetric with server):\n * - 400 → HttpBadRequestError (with field, guiAlertMessage)\n * - 266 → HttpUserError (with errorCode) - 2xx code for user validation\n * - 401 → HttpUnauthorizedError\n * - 403 → HttpForbiddenError\n * - 404 → HttpNotFoundError\n * - 408 → HttpTimeoutError\n * - 500 → HttpInternalServerError\n * - 502 → HttpBadGatewayError\n * - 504 → HttpGatewayTimeoutError\n * - 598 → HttpVendorError (with waitSeconds) - custom status code\n * - other → generic HttpError\n *\n * @param response - Fetch Response object\n * @param protocolError - Parsed ProtocolError from response body\n * @returns HttpError subclass instance\n */\n static translateError(response: Response, protocolError: ProtocolError): Error {\n const statusCode = response.status;\n const message = protocolError.message || response.statusText || 'Unknown error';\n const subType = protocolError.subType;\n\n // Map status codes to error types (symmetric with server's ExpressWrapper.handleError())\n switch (statusCode) {\n case 400:\n return new HttpBadRequestError(\n message,\n protocolError.field,\n protocolError.guiAlertMessage,\n );\n\n case 266: // HttpUserError - 2xx code for user validation errors\n return new HttpUserError(message, protocolError.errorCode);\n\n case 401:\n return new HttpUnauthorizedError(message, subType);\n\n case 403:\n return new HttpForbiddenError(message);\n\n case 404:\n return new HttpNotFoundError(message);\n\n case 408:\n return new HttpTimeoutError(message);\n\n case 500:\n return new HttpInternalServerError(message);\n\n case 502:\n return new HttpBadGatewayError(message);\n\n case 504:\n return new HttpGatewayTimeoutError(message);\n\n case 598: // HttpVendorError - custom status code for vendor/external service errors\n return new HttpVendorError(message, protocolError.waitSeconds);\n\n default:\n // Unknown status code - return generic HttpError\n return new Error(` could not translate statusCode=${statusCode}`);\n }\n }\n}\n"]}
@@ -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
- constructor(baseUrl: string);
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
+ }
@@ -1,14 +1,16 @@
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
+ const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
6
7
  /**
7
8
  * Configuration options for HTTP client.
8
9
  */
9
10
  class ClientConfig {
10
- constructor(baseUrl) {
11
+ constructor(baseUrl, contextMgr) {
11
12
  this.baseUrl = baseUrl;
13
+ this.contextMgr = contextMgr;
12
14
  }
13
15
  }
14
16
  exports.ClientConfig = ClientConfig;
@@ -28,9 +30,10 @@ exports.ClientConfig = ClientConfig;
28
30
  *
29
31
  * @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)
30
32
  * @param config - Client configuration with baseUrl
33
+ * @param logApiCall - Optional LogApiCall instance (creates new one if not provided)
31
34
  * @returns A proxy object that implements the API interface
32
35
  */
33
- function createClient(apiPrototype, config) {
36
+ function createClient(apiPrototype, config, contextMgr) {
34
37
  // Validate that the API prototype is marked with @ApiInterface
35
38
  if (!(0, http_api_1.isApiInterface)(apiPrototype)) {
36
39
  const className = apiPrototype.name || 'Unknown';
@@ -38,11 +41,9 @@ function createClient(apiPrototype, config) {
38
41
  }
39
42
  // Get all routes from the API prototype
40
43
  const routes = (0, http_api_1.getRoutes)(apiPrototype);
41
- // Create a map of method name -> route metadata for fast lookup
42
- const routeMap = new Map();
43
- for (const route of routes) {
44
- routeMap.set(route.methodName, route);
45
- }
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);
46
47
  // Create a proxy that intercepts method calls and makes HTTP requests
47
48
  return new Proxy({}, {
48
49
  get(target, prop) {
@@ -51,45 +52,119 @@ function createClient(apiPrototype, config) {
51
52
  return undefined;
52
53
  }
53
54
  // Get the route metadata for this method
54
- const route = routeMap.get(prop);
55
- if (!route) {
56
- throw new Error(`No route found for method ${prop}`);
57
- }
55
+ const route = proxyClient.getRoute(prop);
58
56
  // Return a function that makes the HTTP request
59
57
  return async (...args) => {
60
- return makeRequest(config, route, args);
58
+ return proxyClient.makeRequest(route, args);
61
59
  };
62
60
  },
63
61
  });
64
62
  }
65
63
  /**
66
- * Make an HTTP request based on route metadata and arguments.
64
+ * ProxyClient - HTTP client implementation with logging.
65
+ *
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
71
+ *
72
+ * LogApiCall is injected for consistent logging across the framework.
67
73
  */
68
- async function makeRequest(config, route, args) {
69
- const { httpMethod, path } = route;
70
- // Build the full URL
71
- const url = `${config.baseUrl}${path}`;
72
- // Build headers
73
- const headers = {
74
- 'Content-Type': 'application/json',
75
- };
76
- // Build request options
77
- const options = {
78
- method: httpMethod,
79
- headers,
80
- };
81
- // For POST/PUT/PATCH, include the body (first argument)
82
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {
83
- options.body = JSON.stringify(args[0]);
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);
84
153
  }
85
- // Make the HTTP request
86
- const response = await fetch(url, options);
87
- // Check for HTTP errors
88
- if (!response.ok) {
89
- const errorText = await response.text();
90
- throw new Error(`HTTP ${response.status}: ${response.statusText}. ${errorText}`);
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);
91
167
  }
92
- // Parse and return the JSON response
93
- return response.json();
94
168
  }
169
+ exports.ProxyClient = ProxyClient;
95
170
  //# sourceMappingURL=ClientFactory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":";;;AAgCA,oCAuCC;AAvED,kDAA+E;AAE/E;;GAEG;AACH,MAAa,YAAY;IAIrB,YAAY,OAAe;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAPD,oCAOC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,YAAY,CACxB,YAAyC,EACzC,MAAoB;IAEpB,+DAA+D;IAC/D,IAAI,CAAC,IAAA,yBAAc,EAAC,YAAY,CAAC,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,IAAI,SAAS,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,SAAS,SAAS,yCAAyC,CAAC,CAAC;IACjF,CAAC;IAED,wCAAwC;IACxC,MAAM,MAAM,GAAG,IAAA,oBAAS,EAAC,YAAY,CAAC,CAAC;IAEvC,gEAAgE;IAChE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACzB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,sEAAsE;IACtE,OAAO,IAAI,KAAK,CAAC,EAAO,EAAE;QACtB,GAAG,CAAC,MAAM,EAAE,IAAqB;YAC7B,+CAA+C;YAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC3B,OAAO,SAAS,CAAC;YACrB,CAAC;YAED,yCAAyC;YACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;YAED,gDAAgD;YAChD,OAAO,KAAK,EAAE,GAAG,IAAW,EAAE,EAAE;gBAC5B,OAAO,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC5C,CAAC,CAAC;QACN,CAAC;KACJ,CAAC,CAAC;AACP,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CAAC,MAAoB,EAAE,KAAoB,EAAE,IAAW;IAC9E,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAEnC,qBAAqB;IACrB,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;IAEvC,gBAAgB;IAChB,MAAM,OAAO,GAA2B;QACpC,cAAc,EAAE,kBAAkB;KACrC,CAAC;IAEF,wBAAwB;IACxB,MAAM,OAAO,GAAgB;QACzB,MAAM,EAAE,UAAU;QAClB,OAAO;KACV,CAAC;IAEF,wDAAwD;IACxD,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,wBAAwB;IACxB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAE3C,wBAAwB;IACxB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,qCAAqC;IACrC,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import { getRoutes, isApiInterface, RouteMetadata } from '@webpieces/http-api';\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 */\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)\n if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {\n options.body = JSON.stringify(args[0]);\n }\n\n // Make the HTTP request\n const response = await fetch(url, options);\n\n // Check for HTTP errors\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`HTTP ${response.status}: ${response.statusText}. ${errorText}`);\n }\n\n // Parse and return the JSON response\n return response.json();\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
@@ -30,4 +30,8 @@
30
30
  * ```
31
31
  */
32
32
  export { createClient, ClientConfig } from './ClientFactory';
33
+ export { ClientErrorTranslator } from './ClientErrorTranslator';
34
+ export { StaticContextReader, CompositeContextReader } from './ContextReader';
35
+ export { ContextMgr } from './ContextMgr';
36
+ export { ContextReader } from '@webpieces/http-api';
33
37
  export { ApiInterface, Get, Post, Put, Delete, Patch, Path, ValidateImplementation, } from '@webpieces/http-api';
package/src/index.js CHANGED
@@ -31,10 +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.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
+ var ClientErrorTranslator_1 = require("./ClientErrorTranslator");
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; } });
38
46
  // Re-export API decorators for convenience (same as http-routing does)
39
47
  var http_api_1 = require("@webpieces/http-api");
40
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;AAEnC,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';\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"]}