@webpieces/http-client 0.2.91 → 0.2.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ClientFactory.d.ts +29 -17
- package/src/ClientFactory.js +37 -54
- package/src/ClientFactory.js.map +1 -1
- package/src/index.d.ts +9 -9
- package/src/index.js +13 -16
- 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.93",
|
|
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.93"
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/ClientFactory.d.ts
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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(SaveApiPrototype, 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
|
-
*
|
|
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 {};
|
package/src/ClientFactory.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(SaveApiPrototype, 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 @
|
|
71
|
+
throw new Error(`Class ${className} must be decorated with @ApiPath()`);
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
85
|
-
//
|
|
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 @
|
|
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
|
-
*
|
|
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
|
-
//
|
|
182
|
+
// POST body is the first argument as JSON
|
|
199
183
|
let requestDto;
|
|
200
|
-
if (
|
|
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
|
package/src/ClientFactory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientFactory.js","sourceRoot":"","sources":["../../../../../packages/http/http-client/src/ClientFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\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(SaveApiPrototype, 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"]}
|
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
|
|
9
|
-
* - Client (@webpieces/http-client): API decorators
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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 {
|
|
23
|
+
* import { createApiClient, ClientConfig } from '@webpieces/http-client';
|
|
24
24
|
* import { SaveApiPrototype } from './api/SaveApi';
|
|
25
25
|
*
|
|
26
26
|
* const config = new ClientConfig('http://localhost:3000');
|
|
27
|
-
* const client =
|
|
27
|
+
* const client = createApiClient(SaveApiPrototype, config);
|
|
28
28
|
*
|
|
29
29
|
* const response = await client.save({ query: 'test' });
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
export {
|
|
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 {
|
|
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
|
|
10
|
-
* - Client (@webpieces/http-client): API decorators
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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 {
|
|
24
|
+
* import { createApiClient, ClientConfig } from '@webpieces/http-client';
|
|
25
25
|
* import { SaveApiPrototype } from './api/SaveApi';
|
|
26
26
|
*
|
|
27
27
|
* const config = new ClientConfig('http://localhost:3000');
|
|
28
|
-
* const client =
|
|
28
|
+
* const client = createApiClient(SaveApiPrototype, 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.
|
|
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, "
|
|
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, "
|
|
49
|
-
Object.defineProperty(exports, "
|
|
50
|
-
Object.defineProperty(exports, "
|
|
51
|
-
Object.defineProperty(exports, "
|
|
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,
|
|
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 { SaveApiPrototype } from './api/SaveApi';\n *\n * const config = new ClientConfig('http://localhost:3000');\n * const client = createApiClient(SaveApiPrototype, 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"]}
|