@webpieces/http-client 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ClientErrorTranslator.d.ts +37 -0
- package/src/ClientErrorTranslator.js +72 -0
- package/src/ClientErrorTranslator.js.map +1 -0
- package/src/ClientFactory.js +18 -8
- package/src/ClientFactory.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-client",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "HTTP client for WebPieces framework",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -21,6 +21,6 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@webpieces/http-api": "0.2.
|
|
24
|
+
"@webpieces/http-api": "0.2.17"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ProtocolError } from '@webpieces/http-api';
|
|
2
|
+
/**
|
|
3
|
+
* ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.
|
|
4
|
+
*
|
|
5
|
+
* This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.
|
|
6
|
+
* It reconstructs typed HttpError exceptions from ProtocolError JSON responses.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON
|
|
10
|
+
* - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError
|
|
11
|
+
*
|
|
12
|
+
* This achieves symmetric error handling - server throws typed exceptions,
|
|
13
|
+
* client receives typed exceptions.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ClientErrorTranslator {
|
|
16
|
+
/**
|
|
17
|
+
* Parse error response and reconstruct appropriate HttpError subclass.
|
|
18
|
+
*
|
|
19
|
+
* Maps HTTP status codes to error types (symmetric with server):
|
|
20
|
+
* - 400 → HttpBadRequestError (with field, guiAlertMessage)
|
|
21
|
+
* - 266 → HttpUserError (with errorCode) - 2xx code for user validation
|
|
22
|
+
* - 401 → HttpUnauthorizedError
|
|
23
|
+
* - 403 → HttpForbiddenError
|
|
24
|
+
* - 404 → HttpNotFoundError
|
|
25
|
+
* - 408 → HttpTimeoutError
|
|
26
|
+
* - 500 → HttpInternalServerError
|
|
27
|
+
* - 502 → HttpBadGatewayError
|
|
28
|
+
* - 504 → HttpGatewayTimeoutError
|
|
29
|
+
* - 598 → HttpVendorError (with waitSeconds) - custom status code
|
|
30
|
+
* - other → generic HttpError
|
|
31
|
+
*
|
|
32
|
+
* @param response - Fetch Response object
|
|
33
|
+
* @param protocolError - Parsed ProtocolError from response body
|
|
34
|
+
* @returns HttpError subclass instance
|
|
35
|
+
*/
|
|
36
|
+
static translateError(response: Response, protocolError: ProtocolError): Error;
|
|
37
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClientErrorTranslator = void 0;
|
|
4
|
+
const http_api_1 = require("@webpieces/http-api");
|
|
5
|
+
/**
|
|
6
|
+
* ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.
|
|
7
|
+
*
|
|
8
|
+
* This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.
|
|
9
|
+
* It reconstructs typed HttpError exceptions from ProtocolError JSON responses.
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON
|
|
13
|
+
* - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError
|
|
14
|
+
*
|
|
15
|
+
* This achieves symmetric error handling - server throws typed exceptions,
|
|
16
|
+
* client receives typed exceptions.
|
|
17
|
+
*/
|
|
18
|
+
class ClientErrorTranslator {
|
|
19
|
+
/**
|
|
20
|
+
* Parse error response and reconstruct appropriate HttpError subclass.
|
|
21
|
+
*
|
|
22
|
+
* Maps HTTP status codes to error types (symmetric with server):
|
|
23
|
+
* - 400 → HttpBadRequestError (with field, guiAlertMessage)
|
|
24
|
+
* - 266 → HttpUserError (with errorCode) - 2xx code for user validation
|
|
25
|
+
* - 401 → HttpUnauthorizedError
|
|
26
|
+
* - 403 → HttpForbiddenError
|
|
27
|
+
* - 404 → HttpNotFoundError
|
|
28
|
+
* - 408 → HttpTimeoutError
|
|
29
|
+
* - 500 → HttpInternalServerError
|
|
30
|
+
* - 502 → HttpBadGatewayError
|
|
31
|
+
* - 504 → HttpGatewayTimeoutError
|
|
32
|
+
* - 598 → HttpVendorError (with waitSeconds) - custom status code
|
|
33
|
+
* - other → generic HttpError
|
|
34
|
+
*
|
|
35
|
+
* @param response - Fetch Response object
|
|
36
|
+
* @param protocolError - Parsed ProtocolError from response body
|
|
37
|
+
* @returns HttpError subclass instance
|
|
38
|
+
*/
|
|
39
|
+
static translateError(response, protocolError) {
|
|
40
|
+
const statusCode = response.status;
|
|
41
|
+
const message = protocolError.message || response.statusText || 'Unknown error';
|
|
42
|
+
const subType = protocolError.subType;
|
|
43
|
+
// Map status codes to error types (symmetric with server's ExpressWrapper.handleError())
|
|
44
|
+
switch (statusCode) {
|
|
45
|
+
case 400:
|
|
46
|
+
return new http_api_1.HttpBadRequestError(message, protocolError.field, protocolError.guiAlertMessage);
|
|
47
|
+
case 266: // HttpUserError - 2xx code for user validation errors
|
|
48
|
+
return new http_api_1.HttpUserError(message, protocolError.errorCode);
|
|
49
|
+
case 401:
|
|
50
|
+
return new http_api_1.HttpUnauthorizedError(message, subType);
|
|
51
|
+
case 403:
|
|
52
|
+
return new http_api_1.HttpForbiddenError(message);
|
|
53
|
+
case 404:
|
|
54
|
+
return new http_api_1.HttpNotFoundError(message);
|
|
55
|
+
case 408:
|
|
56
|
+
return new http_api_1.HttpTimeoutError(message);
|
|
57
|
+
case 500:
|
|
58
|
+
return new http_api_1.HttpInternalServerError(message);
|
|
59
|
+
case 502:
|
|
60
|
+
return new http_api_1.HttpBadGatewayError(message);
|
|
61
|
+
case 504:
|
|
62
|
+
return new http_api_1.HttpGatewayTimeoutError(message);
|
|
63
|
+
case 598: // HttpVendorError - custom status code for vendor/external service errors
|
|
64
|
+
return new http_api_1.HttpVendorError(message, protocolError.waitSeconds);
|
|
65
|
+
default:
|
|
66
|
+
// Unknown status code - return generic HttpError
|
|
67
|
+
return new Error(` could not translate statusCode=${statusCode}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.ClientErrorTranslator = ClientErrorTranslator;
|
|
72
|
+
//# sourceMappingURL=ClientErrorTranslator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientErrorTranslator.ts"],"names":[],"mappings":";;;AAAA,kDAa6B;AAE7B;;;;;;;;;;;;GAYG;AACH,MAAa,qBAAqB;IAC9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,cAAc,CAAC,QAAkB,EAAE,aAA4B;QAClE,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,QAAQ,CAAC,UAAU,IAAI,eAAe,CAAC;QAChF,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC;QAEtC,yFAAyF;QACzF,QAAQ,UAAU,EAAE,CAAC;YACjB,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAmB,CAC1B,OAAO,EACP,aAAa,CAAC,KAAK,EACnB,aAAa,CAAC,eAAe,CAChC,CAAC;YAEN,KAAK,GAAG,EAAE,sDAAsD;gBAC5D,OAAO,IAAI,wBAAa,CAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;YAE/D,KAAK,GAAG;gBACJ,OAAO,IAAI,gCAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEvD,KAAK,GAAG;gBACJ,OAAO,IAAI,6BAAkB,CAAC,OAAO,CAAC,CAAC;YAE3C,KAAK,GAAG;gBACJ,OAAO,IAAI,4BAAiB,CAAC,OAAO,CAAC,CAAC;YAE1C,KAAK,GAAG;gBACJ,OAAO,IAAI,2BAAgB,CAAC,OAAO,CAAC,CAAC;YAEzC,KAAK,GAAG;gBACJ,OAAO,IAAI,kCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG;gBACJ,OAAO,IAAI,8BAAmB,CAAC,OAAO,CAAC,CAAC;YAE5C,KAAK,GAAG;gBACJ,OAAO,IAAI,kCAAuB,CAAC,OAAO,CAAC,CAAC;YAEhD,KAAK,GAAG,EAAE,0EAA0E;gBAChF,OAAO,IAAI,0BAAe,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;YAEnE;gBACI,iDAAiD;gBACjD,OAAO,IAAI,KAAK,CAAC,mCAAmC,UAAU,EAAE,CAAC,CAAC;QAC1E,CAAC;IACL,CAAC;CACJ;AAnED,sDAmEC","sourcesContent":["import {\n ProtocolError,\n HttpError,\n HttpBadRequestError,\n HttpUserError,\n HttpVendorError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpTimeoutError,\n HttpInternalServerError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n} from '@webpieces/http-api';\n\n/**\n * ClientErrorTranslator - Translates HTTP error responses to HttpError exceptions.\n *\n * This is the CLIENT-SIDE reverse of ExpressWrapper.handleError() on the server.\n * It reconstructs typed HttpError exceptions from ProtocolError JSON responses.\n *\n * Architecture:\n * - Server: HttpError → ExpressWrapper.handleError() → ProtocolError JSON\n * - Client: ProtocolError JSON → ClientErrorTranslator.translateError() → HttpError\n *\n * This achieves symmetric error handling - server throws typed exceptions,\n * client receives typed exceptions.\n */\nexport class ClientErrorTranslator {\n /**\n * Parse error response and reconstruct appropriate HttpError subclass.\n *\n * Maps HTTP status codes to error types (symmetric with server):\n * - 400 → HttpBadRequestError (with field, guiAlertMessage)\n * - 266 → HttpUserError (with errorCode) - 2xx code for user validation\n * - 401 → HttpUnauthorizedError\n * - 403 → HttpForbiddenError\n * - 404 → HttpNotFoundError\n * - 408 → HttpTimeoutError\n * - 500 → HttpInternalServerError\n * - 502 → HttpBadGatewayError\n * - 504 → HttpGatewayTimeoutError\n * - 598 → HttpVendorError (with waitSeconds) - custom status code\n * - other → generic HttpError\n *\n * @param response - Fetch Response object\n * @param protocolError - Parsed ProtocolError from response body\n * @returns HttpError subclass instance\n */\n static translateError(response: Response, protocolError: ProtocolError): Error {\n const statusCode = response.status;\n const message = protocolError.message || response.statusText || 'Unknown error';\n const subType = protocolError.subType;\n\n // Map status codes to error types (symmetric with server's ExpressWrapper.handleError())\n switch (statusCode) {\n case 400:\n return new HttpBadRequestError(\n message,\n protocolError.field,\n protocolError.guiAlertMessage,\n );\n\n case 266: // HttpUserError - 2xx code for user validation errors\n return new HttpUserError(message, protocolError.errorCode);\n\n case 401:\n return new HttpUnauthorizedError(message, subType);\n\n case 403:\n return new HttpForbiddenError(message);\n\n case 404:\n return new HttpNotFoundError(message);\n\n case 408:\n return new HttpTimeoutError(message);\n\n case 500:\n return new HttpInternalServerError(message);\n\n case 502:\n return new HttpBadGatewayError(message);\n\n case 504:\n return new HttpGatewayTimeoutError(message);\n\n case 598: // HttpVendorError - custom status code for vendor/external service errors\n return new HttpVendorError(message, protocolError.waitSeconds);\n\n default:\n // Unknown status code - return generic HttpError\n return new Error(` could not translate statusCode=${statusCode}`);\n }\n }\n}\n"]}
|
package/src/ClientFactory.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ClientConfig = void 0;
|
|
4
4
|
exports.createClient = createClient;
|
|
5
5
|
const http_api_1 = require("@webpieces/http-api");
|
|
6
|
+
const ClientErrorTranslator_1 = require("./ClientErrorTranslator");
|
|
6
7
|
/**
|
|
7
8
|
* Configuration options for HTTP client.
|
|
8
9
|
*/
|
|
@@ -64,6 +65,12 @@ function createClient(apiPrototype, config) {
|
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
67
|
* Make an HTTP request based on route metadata and arguments.
|
|
68
|
+
*
|
|
69
|
+
* Uses plain JSON.stringify/parse - no serialization library needed!
|
|
70
|
+
*
|
|
71
|
+
* Error handling:
|
|
72
|
+
* - Server: Throws HttpError → translates to ProtocolError JSON
|
|
73
|
+
* - Client: Receives ProtocolError JSON → reconstructs HttpError
|
|
67
74
|
*/
|
|
68
75
|
async function makeRequest(config, route, args) {
|
|
69
76
|
const { httpMethod, path } = route;
|
|
@@ -78,18 +85,21 @@ async function makeRequest(config, route, args) {
|
|
|
78
85
|
method: httpMethod,
|
|
79
86
|
headers,
|
|
80
87
|
};
|
|
81
|
-
// For POST/PUT/PATCH, include the body (first argument)
|
|
88
|
+
// For POST/PUT/PATCH, include the body (first argument) as JSON
|
|
82
89
|
if (['POST', 'PUT', 'PATCH'].includes(httpMethod) && args.length > 0) {
|
|
83
|
-
|
|
90
|
+
const requestDto = args[0];
|
|
91
|
+
// Plain JSON stringify - works with plain objects and our DateTimeDto classes
|
|
92
|
+
options.body = JSON.stringify(requestDto);
|
|
84
93
|
}
|
|
85
94
|
// Make the HTTP request
|
|
86
95
|
const response = await fetch(url, options);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const errorText = await response.text();
|
|
90
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}. ${errorText}`);
|
|
96
|
+
if (response.ok) {
|
|
97
|
+
return response.json();
|
|
91
98
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
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);
|
|
94
104
|
}
|
|
95
105
|
//# sourceMappingURL=ClientFactory.js.map
|
package/src/ClientFactory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":";;;
|
|
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"]}
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
-
exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = exports.ClientConfig = exports.createClient = void 0;
|
|
34
|
+
exports.Path = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = exports.ApiInterface = exports.ClientErrorTranslator = exports.ClientConfig = exports.createClient = void 0;
|
|
35
35
|
var ClientFactory_1 = require("./ClientFactory");
|
|
36
36
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return ClientFactory_1.createClient; } });
|
|
37
37
|
Object.defineProperty(exports, "ClientConfig", { enumerable: true, get: function () { return ClientFactory_1.ClientConfig; } });
|
|
38
|
+
var ClientErrorTranslator_1 = require("./ClientErrorTranslator");
|
|
39
|
+
Object.defineProperty(exports, "ClientErrorTranslator", { enumerable: true, get: function () { return ClientErrorTranslator_1.ClientErrorTranslator; } });
|
|
38
40
|
// Re-export API decorators for convenience (same as http-routing does)
|
|
39
41
|
var http_api_1 = require("@webpieces/http-api");
|
|
40
42
|
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;
|
|
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"]}
|