@webpieces/http-client 0.2.17 → 0.2.23

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.17",
3
+ "version": "0.2.23",
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.17"
24
+ "@webpieces/http-api": "0.2.23"
25
25
  }
26
26
  }
@@ -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,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 a map of method name -> route metadata for fast lookup
43
- const routeMap = new Map();
44
- for (const route of routes) {
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 = routeMap.get(prop);
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(config, route, args);
58
+ return proxyClient.makeRequest(route, args);
62
59
  };
63
60
  },
64
61
  });
65
62
  }
66
63
  /**
67
- * Make an HTTP request based on route metadata and arguments.
64
+ * ProxyClient - HTTP client implementation with logging.
68
65
  *
69
- * Uses plain JSON.stringify/parse - no serialization library needed!
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
- * Error handling:
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
- async function makeRequest(config, route, args) {
76
- const { httpMethod, path } = route;
77
- // Build the full URL
78
- const url = `${config.baseUrl}${path}`;
79
- // Build headers
80
- const headers = {
81
- 'Content-Type': 'application/json',
82
- };
83
- // Build request options
84
- const options = {
85
- method: httpMethod,
86
- headers,
87
- };
88
- // For POST/PUT/PATCH, include the body (first argument) as JSON
89
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {
90
- const requestDto = args[0];
91
- // Plain JSON stringify - works with plain objects and our DateTimeDto classes
92
- options.body = JSON.stringify(requestDto);
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
- // Make the HTTP request
95
- const response = await fetch(url, options);
96
- if (response.ok) {
97
- return response.json();
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
@@ -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"]}