@webpieces/http-client 0.2.92 → 0.2.94

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.92",
3
+ "version": "0.2.94",
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.92"
24
+ "@webpieces/http-api": "0.2.94"
25
25
  }
26
26
  }
@@ -1,5 +1,12 @@
1
1
  import { RouteMetadata, HeaderMethods, LogApiCall } from '@webpieces/http-api';
2
2
  import { ContextMgr } from './ContextMgr';
3
+ /**
4
+ * Type representing a class constructor whose prototype is T.
5
+ * Used as the apiPrototype parameter for createApiClient.
6
+ */
7
+ type ApiPrototype<T> = Function & {
8
+ prototype: T;
9
+ };
3
10
  /**
4
11
  * Configuration options for HTTP client.
5
12
  */
@@ -13,9 +20,26 @@ export declare class ClientConfig {
13
20
  contextMgr?: ContextMgr;
14
21
  constructor(baseUrl: string, contextMgr?: ContextMgr);
15
22
  }
16
- export declare function createClient<T extends object>(apiPrototype: Function & {
17
- prototype: T;
18
- }, config: ClientConfig, contextMgr?: ContextMgr): T;
23
+ /**
24
+ * Creates a type-safe HTTP client from an API prototype with @ApiPath/@Endpoint decorators.
25
+ *
26
+ * This is the client-side equivalent of ApiRoutingFactory.
27
+ * - Server: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers
28
+ * - Client: createApiClient reads decorators -> generates HTTP requests from method calls
29
+ *
30
+ * Usage:
31
+ * ```typescript
32
+ * const config = new ClientConfig('http://localhost:3000');
33
+ * const client = createApiClient(SaveApi, config);
34
+ * const response = await client.save({ query: 'test' }); // Type-safe!
35
+ * ```
36
+ *
37
+ * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators
38
+ * @param config - Client configuration with baseUrl
39
+ * @param contextMgr - Optional context manager for header propagation
40
+ * @returns A proxy object that implements the API interface
41
+ */
42
+ export declare function createApiClient<T extends object>(apiPrototype: ApiPrototype<T>, config: ClientConfig, contextMgr?: ContextMgr): T;
19
43
  /**
20
44
  * ProxyClient - HTTP client implementation with logging.
21
45
  *
@@ -46,20 +70,7 @@ export declare class ProxyClient {
46
70
  /**
47
71
  * Make an HTTP request based on route metadata and arguments.
48
72
  *
49
- * Uses plain JSON.stringify/parse - no serialization library needed!
50
- *
51
- * Error handling:
52
- * - Server: Throws HttpError → translates to ProtocolError JSON
53
- * - Client: Receives ProtocolError JSON → reconstructs HttpError
54
- *
55
- * Automatic header propagation via ContextMgr:
56
- * - If config.contextMgr is provided, reads headers from ContextReader
57
- * - Adds headers to request before fetch()
58
- *
59
- * Logging via LogApiCall.execute():
60
- * - [API-CLIENT-req] logs outgoing requests with headers (secure ones masked)
61
- * - [API-CLIENT-resp-SUCCESS] logs successful responses
62
- * - [API-CLIENT-resp-FAIL] logs failed responses
73
+ * All endpoints are POST-only. The request body is the first argument.
63
74
  */
64
75
  makeRequest(route: RouteMetadata, args: any[]): Promise<any>;
65
76
  /**
@@ -67,3 +78,4 @@ export declare class ProxyClient {
67
78
  */
68
79
  private executeFetch;
69
80
  }
81
+ export {};
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProxyClient = exports.ClientConfig = void 0;
4
- exports.createClient = createClient;
4
+ exports.createApiClient = createApiClient;
5
5
  const http_api_1 = require("@webpieces/http-api");
6
6
  const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
7
7
  /**
@@ -14,25 +14,6 @@ class ClientConfig {
14
14
  }
15
15
  }
16
16
  exports.ClientConfig = ClientConfig;
17
- /**
18
- * Creates a type-safe HTTP client from an API interface prototype.
19
- *
20
- * This is the client-side equivalent of RESTApiRoutes.
21
- * - Server: RESTApiRoutes reads decorators → routes HTTP requests to controllers
22
- * - Client: createClient reads decorators → generates HTTP requests from method calls
23
- *
24
- * Usage:
25
- * ```typescript
26
- * const config = new ClientConfig('http://localhost:3000');
27
- * const client = createClient(SaveApiPrototype, config);
28
- * const response = await client.save({ query: 'test' }); // Type-safe!
29
- * ```
30
- *
31
- * @param apiPrototype - The API prototype class with decorators (e.g., SaveApiPrototype)
32
- * @param config - Client configuration with baseUrl
33
- * @param logApiCall - Optional LogApiCall instance (creates new one if not provided)
34
- * @returns A proxy object that implements the API interface
35
- */
36
17
  /**
37
18
  * Properties accessed by DI frameworks (Angular, Vue), debuggers, Promise checks, and serializers.
38
19
  * These should return undefined instead of throwing, allowing frameworks to inspect the proxy.
@@ -64,25 +45,41 @@ const FRAMEWORK_INSPECTION_PROPERTIES = new Set([
64
45
  'ngOnChanges', // Angular lifecycle hook check
65
46
  'asymmetricMatch', // Jest matcher protocol
66
47
  ]);
67
- function createClient(apiPrototype, config, contextMgr) {
68
- // Validate that the API prototype is marked with @ApiInterface
69
- if (!(0, http_api_1.isApiInterface)(apiPrototype)) {
48
+ /**
49
+ * Creates a type-safe HTTP client from an API prototype with @ApiPath/@Endpoint decorators.
50
+ *
51
+ * This is the client-side equivalent of ApiRoutingFactory.
52
+ * - Server: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers
53
+ * - Client: createApiClient reads decorators -> generates HTTP requests from method calls
54
+ *
55
+ * Usage:
56
+ * ```typescript
57
+ * const config = new ClientConfig('http://localhost:3000');
58
+ * const client = createApiClient(SaveApi, config);
59
+ * const response = await client.save({ query: 'test' }); // Type-safe!
60
+ * ```
61
+ *
62
+ * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators
63
+ * @param config - Client configuration with baseUrl
64
+ * @param contextMgr - Optional context manager for header propagation
65
+ * @returns A proxy object that implements the API interface
66
+ */
67
+ function createApiClient(apiPrototype, config, contextMgr) {
68
+ // Validate that the API prototype is marked with @ApiPath
69
+ if (!(0, http_api_1.isApiPath)(apiPrototype)) {
70
70
  const className = apiPrototype.name || 'Unknown';
71
- throw new Error(`Class ${className} must be decorated with @ApiInterface()`);
71
+ throw new Error(`Class ${className} must be decorated with @ApiPath()`);
72
72
  }
73
- // Get all routes from the API prototype
74
- const routes = (0, http_api_1.getRoutes)(apiPrototype);
75
- // Validate that all methods use @Post() - we only support POST for now
76
- for (const route of routes) {
77
- if (route.httpMethod !== 'POST') {
78
- throw new Error(`Method '${route.methodName}' uses @${route.httpMethod.charAt(0) + route.httpMethod.slice(1).toLowerCase()}() but we only support @Post() on methods right now. ` +
79
- `This is how gRPC, thrift, etc. all work - @Get is not needed but we may add later. ` +
80
- `Currently, no app has 'truly' needed it and only wanted to conform to ideals when in practice, ` +
81
- `there are no issues with @Post() and in fact @Post is more flexible as it can evolve to returning stuff later which happens frequently.`);
82
- }
73
+ const basePath = (0, http_api_1.getApiPath)(apiPrototype);
74
+ const endpoints = (0, http_api_1.getEndpoints)(apiPrototype) || {};
75
+ // Build RouteMetadata array from @ApiPath + @Endpoint metadata
76
+ const routes = [];
77
+ for (const [methodName, endpointPath] of Object.entries(endpoints)) {
78
+ const fullPath = basePath + endpointPath;
79
+ routes.push(new http_api_1.RouteMetadata('POST', fullPath, methodName));
83
80
  }
84
- // Create ProxyClient with injected LogApiCall (or create new one)
85
- //CRAP our own little DI going on here as angular and nodejs are using 2 different DI systems!!! fuck!!
81
+ // Create ProxyClient with injected LogApiCall
82
+ // Our own little DI going on here as angular and nodejs are using 2 different DI systems
86
83
  const proxyClient = new ProxyClient(config, new http_api_1.LogApiCall(), new http_api_1.HeaderMethods(), routes, contextMgr);
87
84
  // Create a proxy that intercepts method calls and makes HTTP requests
88
85
  return new Proxy({}, {
@@ -101,7 +98,7 @@ function createClient(apiPrototype, config, contextMgr) {
101
98
  if (!proxyClient.hasRoute(prop)) {
102
99
  // For unknown properties (likely typos), throw a helpful error
103
100
  throw new Error(`No route found for method '${prop}'. ` +
104
- `Check for typos or ensure the method has @Post() decorator.`);
101
+ `Check for typos or ensure the method has @Endpoint() decorator.`);
105
102
  }
106
103
  const route = proxyClient.getRoute(prop);
107
104
  // Return a function that makes the HTTP request
@@ -154,20 +151,7 @@ class ProxyClient {
154
151
  /**
155
152
  * Make an HTTP request based on route metadata and arguments.
156
153
  *
157
- * Uses plain JSON.stringify/parse - no serialization library needed!
158
- *
159
- * Error handling:
160
- * - Server: Throws HttpError → translates to ProtocolError JSON
161
- * - Client: Receives ProtocolError JSON → reconstructs HttpError
162
- *
163
- * Automatic header propagation via ContextMgr:
164
- * - If config.contextMgr is provided, reads headers from ContextReader
165
- * - Adds headers to request before fetch()
166
- *
167
- * Logging via LogApiCall.execute():
168
- * - [API-CLIENT-req] logs outgoing requests with headers (secure ones masked)
169
- * - [API-CLIENT-resp-SUCCESS] logs successful responses
170
- * - [API-CLIENT-resp-FAIL] logs failed responses
154
+ * All endpoints are POST-only. The request body is the first argument.
171
155
  */
172
156
  async makeRequest(route, args) {
173
157
  const { httpMethod, path } = route;
@@ -195,11 +179,10 @@ class ProxyClient {
195
179
  method: httpMethod,
196
180
  headers: httpHeaders,
197
181
  };
198
- // For POST/PUT/PATCH, include the body (first argument) as JSON
182
+ // POST body is the first argument as JSON
199
183
  let requestDto;
200
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {
184
+ if (args.length > 0) {
201
185
  requestDto = args[0];
202
- // Plain JSON stringify - works with plain objects and our DateTimeDto classes
203
186
  options.body = JSON.stringify(requestDto);
204
187
  }
205
188
  // Wrap fetch in a method for LogApiCall.execute
@@ -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 {\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 */\n/**\n * Properties accessed by DI frameworks (Angular, Vue), debuggers, Promise checks, and serializers.\n * These should return undefined instead of throwing, allowing frameworks to inspect the proxy.\n *\n * Why this exists:\n * - Angular's injector profiler accesses `constructor` after useFactory returns\n * - Promise.resolve() checks for `then` to detect thenables\n * - JSON.stringify checks for `toJSON`\n * - Debuggers access `prototype`, `__proto__`, etc.\n */\nconst FRAMEWORK_INSPECTION_PROPERTIES = new Set([\n 'constructor', // Angular DI profiler, class inspection\n 'prototype', // Prototype chain inspection\n '__proto__', // Legacy prototype access\n 'name', // Angular isNotFound() check, function/class name inspection\n 'then', // Promise/thenable detection\n 'catch', // Promise check\n 'finally', // Promise check\n 'toJSON', // JSON.stringify\n 'valueOf', // Type coercion\n 'toString', // String coercion\n 'nodeType', // DOM element check\n 'tagName', // DOM element check\n '$$typeof', // React element/component check\n '$typeof', // React internal\n '_isVue', // Vue internal\n 'ngOnInit', // Angular lifecycle hook check\n 'ngOnDestroy', // Angular lifecycle hook check\n 'ngOnChanges', // Angular lifecycle hook check\n 'asymmetricMatch', // Jest matcher protocol\n]);\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 // Validate that all methods use @Post() - we only support POST for now\n for (const route of routes) {\n if (route.httpMethod !== 'POST') {\n throw new Error(\n `Method '${route.methodName}' uses @${route.httpMethod.charAt(0) + route.httpMethod.slice(1).toLowerCase()}() but we only support @Post() on methods right now. ` +\n `This is how gRPC, thrift, etc. all work - @Get is not needed but we may add later. ` +\n `Currently, no app has 'truly' needed it and only wanted to conform to ideals when in practice, ` +\n `there are no issues with @Post() and in fact @Post is more flexible as it can evolve to returning stuff later which happens frequently.`\n );\n }\n }\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 // Symbols (Symbol.toStringTag, Symbol.iterator, etc.) - throw for now to learn if this happens\n if (typeof prop !== 'string') {\n throw new Error(\n `Proxy accessed with non-string property: ${String(prop)} (type: ${typeof prop}). ` +\n `Please report this so we can add it to the whitelist.`\n );\n }\n\n // Framework inspection properties - return undefined to allow inspection\n // WITHOUT throwing. This is critical for Angular DI, Promise checks, etc.\n if (FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {\n return undefined;\n }\n\n // Check if this property is actually a route method\n if (!proxyClient.hasRoute(prop)) {\n // For unknown properties (likely typos), throw a helpful error\n throw new Error(\n `No route found for method '${prop}'. ` +\n `Check for typos or ensure the method has @Post() decorator.`\n );\n }\n\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 * Check if a route exists for the given method name.\n */\n hasRoute(methodName: string): boolean {\n return this.routeMap.has(methodName);\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"]}
1
+ {"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n isApiPath,\n getApiPath,\n getEndpoints,\n RouteMetadata,\n ProtocolError,\n HeaderMethods,\n LogApiCall,\n} from '@webpieces/http-api';\nimport { ClientErrorTranslator } from './ClientErrorTranslator';\nimport { ContextMgr } from './ContextMgr';\n\n/**\n * Type representing a class constructor whose prototype is T.\n * Used as the apiPrototype parameter for createApiClient.\n */\ntype ApiPrototype<T> = Function & { prototype: T };\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 * Return type for the Proxy get trap — either an async method or undefined for framework inspection.\n */\n// webpieces-disable no-any-unknown -- Proxy get trap returns generic response promises\ntype ProxyGetResult = (...args: never[]) => Promise<unknown>;\n\n/**\n * Properties accessed by DI frameworks (Angular, Vue), debuggers, Promise checks, and serializers.\n * These should return undefined instead of throwing, allowing frameworks to inspect the proxy.\n *\n * Why this exists:\n * - Angular's injector profiler accesses `constructor` after useFactory returns\n * - Promise.resolve() checks for `then` to detect thenables\n * - JSON.stringify checks for `toJSON`\n * - Debuggers access `prototype`, `__proto__`, etc.\n */\nconst FRAMEWORK_INSPECTION_PROPERTIES = new Set([\n 'constructor', // Angular DI profiler, class inspection\n 'prototype', // Prototype chain inspection\n '__proto__', // Legacy prototype access\n 'name', // Angular isNotFound() check, function/class name inspection\n 'then', // Promise/thenable detection\n 'catch', // Promise check\n 'finally', // Promise check\n 'toJSON', // JSON.stringify\n 'valueOf', // Type coercion\n 'toString', // String coercion\n 'nodeType', // DOM element check\n 'tagName', // DOM element check\n '$$typeof', // React element/component check\n '$typeof', // React internal\n '_isVue', // Vue internal\n 'ngOnInit', // Angular lifecycle hook check\n 'ngOnDestroy', // Angular lifecycle hook check\n 'ngOnChanges', // Angular lifecycle hook check\n 'asymmetricMatch', // Jest matcher protocol\n]);\n\n/**\n * Creates a type-safe HTTP client from an API prototype with @ApiPath/@Endpoint decorators.\n *\n * This is the client-side equivalent of ApiRoutingFactory.\n * - Server: ApiRoutingFactory reads decorators -> routes HTTP requests to controllers\n * - Client: createApiClient reads decorators -> generates HTTP requests from method calls\n *\n * Usage:\n * ```typescript\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createApiClient(SaveApi, config);\n * const response = await client.save({ query: 'test' }); // Type-safe!\n * ```\n *\n * @param apiPrototype - The API prototype class with @ApiPath/@Endpoint decorators\n * @param config - Client configuration with baseUrl\n * @param contextMgr - Optional context manager for header propagation\n * @returns A proxy object that implements the API interface\n */\nexport function createApiClient<T extends object>(\n apiPrototype: ApiPrototype<T>,\n config: ClientConfig,\n contextMgr?: ContextMgr\n): T {\n // Validate that the API prototype is marked with @ApiPath\n if (!isApiPath(apiPrototype)) {\n const className = apiPrototype.name || 'Unknown';\n throw new Error(`Class ${className} must be decorated with @ApiPath()`);\n }\n\n const basePath = getApiPath(apiPrototype)!;\n const endpoints = getEndpoints(apiPrototype) || {};\n\n // Build RouteMetadata array from @ApiPath + @Endpoint metadata\n const routes: RouteMetadata[] = [];\n for (const [methodName, endpointPath] of Object.entries(endpoints)) {\n const fullPath = basePath + endpointPath;\n routes.push(new RouteMetadata('POST', fullPath, methodName));\n }\n\n // Create ProxyClient with injected LogApiCall\n // Our own little DI going on here as angular and nodejs are using 2 different DI systems\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): ProxyGetResult | undefined {\n // Symbols (Symbol.toStringTag, Symbol.iterator, etc.) - throw for now to learn if this happens\n if (typeof prop !== 'string') {\n throw new Error(\n `Proxy accessed with non-string property: ${String(prop)} (type: ${typeof prop}). ` +\n `Please report this so we can add it to the whitelist.`\n );\n }\n\n // Framework inspection properties - return undefined to allow inspection\n // WITHOUT throwing. This is critical for Angular DI, Promise checks, etc.\n if (FRAMEWORK_INSPECTION_PROPERTIES.has(prop)) {\n return undefined;\n }\n\n // Check if this property is actually a route method\n if (!proxyClient.hasRoute(prop)) {\n // For unknown properties (likely typos), throw a helpful error\n throw new Error(\n `No route found for method '${prop}'. ` +\n `Check for typos or ensure the method has @Endpoint() decorator.`\n );\n }\n\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 * Check if a route exists for the given method name.\n */\n hasRoute(methodName: string): boolean {\n return this.routeMap.has(methodName);\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 * All endpoints are POST-only. The request body is the first argument.\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 // 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 // POST body is the first argument as JSON\n let requestDto: unknown;\n if (args.length > 0) {\n requestDto = args[0];\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"]}
@@ -23,7 +23,7 @@ import { PlatformHeader, ContextReader } from '@webpieces/http-api';
23
23
  *
24
24
  * // Both cases
25
25
  * const config = new ClientConfig('http://api.example.com', contextMgr);
26
- * const client = createClient(SaveApiPrototype, config);
26
+ * const client = createClient(SaveApi, config);
27
27
  * ```
28
28
  */
29
29
  export declare class ContextMgr {
package/src/ContextMgr.js CHANGED
@@ -25,7 +25,7 @@ exports.ContextMgr = void 0;
25
25
  *
26
26
  * // Both cases
27
27
  * const config = new ClientConfig('http://api.example.com', contextMgr);
28
- * const client = createClient(SaveApiPrototype, config);
28
+ * const client = createClient(SaveApi, config);
29
29
  * ```
30
30
  */
31
31
  class ContextMgr {
@@ -1 +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"]}
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(SaveApi, 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"]}
package/src/index.d.ts CHANGED
@@ -5,33 +5,33 @@
5
5
  * Reads API decorators and generates type-safe HTTP clients.
6
6
  *
7
7
  * This is the client-side counterpart to @webpieces/http-routing:
8
- * - Server (@webpieces/http-routing): API decorators route HTTP to controllers
9
- * - Client (@webpieces/http-client): API decorators generate HTTP from method calls
8
+ * - Server (@webpieces/http-routing): API decorators -> route HTTP to controllers
9
+ * - Client (@webpieces/http-client): API decorators -> generate HTTP from method calls
10
10
  *
11
11
  * Both packages depend on @webpieces/http-api for shared decorator definitions.
12
12
  *
13
13
  * Architecture:
14
14
  * ```
15
15
  * http-api (defines the contract)
16
- *
17
- * ├── http-routing (server: contract handlers)
18
- * └── http-client (client: contract HTTP requests) YOU ARE HERE
16
+ * ^
17
+ * +-- http-routing (server: contract -> handlers)
18
+ * +-- http-client (client: contract -> HTTP requests) <- YOU ARE HERE
19
19
  * ```
20
20
  *
21
21
  * Usage:
22
22
  * ```typescript
23
- * import { createClient, ClientConfig } from '@webpieces/http-client';
24
- * import { SaveApiPrototype } from './api/SaveApi';
23
+ * import { createApiClient, ClientConfig } from '@webpieces/http-client';
24
+ * import { SaveApi } from './api/SaveApi';
25
25
  *
26
26
  * const config = new ClientConfig('http://localhost:3000');
27
- * const client = createClient(SaveApiPrototype, config);
27
+ * const client = createApiClient(SaveApi, config);
28
28
  *
29
29
  * const response = await client.save({ query: 'test' });
30
30
  * ```
31
31
  */
32
- export { createClient, ClientConfig } from './ClientFactory';
32
+ export { createApiClient, ClientConfig } from './ClientFactory';
33
33
  export { ClientErrorTranslator } from './ClientErrorTranslator';
34
34
  export { StaticContextReader, CompositeContextReader } from './ContextReader';
35
35
  export { ContextMgr } from './ContextMgr';
36
36
  export { ContextReader } from '@webpieces/http-api';
37
- export { ApiInterface, Get, Post, Put, Delete, Patch, Path, ValidateImplementation, } from '@webpieces/http-api';
37
+ export { ApiPath, Endpoint, Authentication, AuthenticationConfig, ValidateImplementation, } from '@webpieces/http-api';
package/src/index.js CHANGED
@@ -6,34 +6,34 @@
6
6
  * Reads API decorators and generates type-safe HTTP clients.
7
7
  *
8
8
  * This is the client-side counterpart to @webpieces/http-routing:
9
- * - Server (@webpieces/http-routing): API decorators route HTTP to controllers
10
- * - Client (@webpieces/http-client): API decorators generate HTTP from method calls
9
+ * - Server (@webpieces/http-routing): API decorators -> route HTTP to controllers
10
+ * - Client (@webpieces/http-client): API decorators -> generate HTTP from method calls
11
11
  *
12
12
  * Both packages depend on @webpieces/http-api for shared decorator definitions.
13
13
  *
14
14
  * Architecture:
15
15
  * ```
16
16
  * http-api (defines the contract)
17
- *
18
- * ├── http-routing (server: contract handlers)
19
- * └── http-client (client: contract HTTP requests) YOU ARE HERE
17
+ * ^
18
+ * +-- http-routing (server: contract -> handlers)
19
+ * +-- http-client (client: contract -> HTTP requests) <- YOU ARE HERE
20
20
  * ```
21
21
  *
22
22
  * Usage:
23
23
  * ```typescript
24
- * import { createClient, ClientConfig } from '@webpieces/http-client';
25
- * import { SaveApiPrototype } from './api/SaveApi';
24
+ * import { createApiClient, ClientConfig } from '@webpieces/http-client';
25
+ * import { SaveApi } from './api/SaveApi';
26
26
  *
27
27
  * const config = new ClientConfig('http://localhost:3000');
28
- * const client = createClient(SaveApiPrototype, config);
28
+ * const client = createApiClient(SaveApi, config);
29
29
  *
30
30
  * const response = await client.save({ query: 'test' });
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.ContextMgr = exports.CompositeContextReader = exports.StaticContextReader = exports.ClientErrorTranslator = exports.ClientConfig = exports.createClient = void 0;
34
+ exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = exports.ContextMgr = exports.CompositeContextReader = exports.StaticContextReader = exports.ClientErrorTranslator = exports.ClientConfig = exports.createApiClient = void 0;
35
35
  var ClientFactory_1 = require("./ClientFactory");
36
- Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return ClientFactory_1.createClient; } });
36
+ Object.defineProperty(exports, "createApiClient", { enumerable: true, get: function () { return ClientFactory_1.createApiClient; } });
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; } });
@@ -45,11 +45,8 @@ var ContextMgr_1 = require("./ContextMgr");
45
45
  Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function () { return ContextMgr_1.ContextMgr; } });
46
46
  // Re-export API decorators for convenience (same as http-routing does)
47
47
  var http_api_1 = require("@webpieces/http-api");
48
- Object.defineProperty(exports, "ApiInterface", { enumerable: true, get: function () { return http_api_1.ApiInterface; } });
49
- Object.defineProperty(exports, "Get", { enumerable: true, get: function () { return http_api_1.Get; } });
50
- Object.defineProperty(exports, "Post", { enumerable: true, get: function () { return http_api_1.Post; } });
51
- Object.defineProperty(exports, "Put", { enumerable: true, get: function () { return http_api_1.Put; } });
52
- Object.defineProperty(exports, "Delete", { enumerable: true, get: function () { return http_api_1.Delete; } });
53
- Object.defineProperty(exports, "Patch", { enumerable: true, get: function () { return http_api_1.Patch; } });
54
- Object.defineProperty(exports, "Path", { enumerable: true, get: function () { return http_api_1.Path; } });
48
+ Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return http_api_1.ApiPath; } });
49
+ Object.defineProperty(exports, "Endpoint", { enumerable: true, get: function () { return http_api_1.Endpoint; } });
50
+ Object.defineProperty(exports, "Authentication", { enumerable: true, get: function () { return http_api_1.Authentication; } });
51
+ Object.defineProperty(exports, "AuthenticationConfig", { enumerable: true, get: function () { return http_api_1.AuthenticationConfig; } });
55
52
  //# sourceMappingURL=index.js.map
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,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"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAEH,iDAAgE;AAAvD,gHAAA,eAAe,OAAA;AAAE,6GAAA,YAAY,OAAA;AACtC,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,gDAM6B;AALzB,mGAAA,OAAO,OAAA;AACP,oGAAA,QAAQ,OAAA;AACR,0GAAA,cAAc,OAAA;AACd,gHAAA,oBAAoB,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 { createApiClient, ClientConfig } from '@webpieces/http-client';\n * import { SaveApi } from './api/SaveApi';\n *\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createApiClient(SaveApi, config);\n *\n * const response = await client.save({ query: 'test' });\n * ```\n */\n\nexport { createApiClient, 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 ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n ValidateImplementation,\n} from '@webpieces/http-api';\n"]}