@rexeus/typeweaver-clients 0.0.2 → 0.0.4
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/README.md +86 -361
- package/dist/LICENSE +202 -0
- package/dist/NOTICE +4 -0
- package/dist/index.d.ts +1 -1611
- package/dist/index.js +88 -925
- package/dist/lib/ApiClient.ts +71 -28
- package/dist/lib/RequestCommand.ts +30 -10
- package/dist/lib/index.ts +7 -0
- package/dist/templates/Client.ejs +13 -12
- package/dist/templates/RequestCommand.ejs +90 -0
- package/package.json +19 -9
package/dist/lib/ApiClient.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by typeweaver.
|
|
3
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
4
|
+
*
|
|
5
|
+
* @generated by @rexeus/typeweaver
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import axios, { AxiosError } from "axios";
|
|
1
9
|
import type {
|
|
2
|
-
IHttpQuery,
|
|
3
|
-
IHttpParam,
|
|
4
10
|
IHttpHeader,
|
|
11
|
+
IHttpParam,
|
|
12
|
+
IHttpQuery,
|
|
5
13
|
IHttpResponse,
|
|
6
14
|
} from "@rexeus/typeweaver-core";
|
|
7
15
|
import { RequestCommand } from "./RequestCommand";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
import type { ProcessResponseOptions } from "./RequestCommand";
|
|
17
|
+
import type { AxiosHeaderValue, AxiosInstance, AxiosResponse } from "axios";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for handling unknown responses.
|
|
21
|
+
*/
|
|
22
|
+
export type UnknownResponseHandling = "throw" | "passthrough";
|
|
14
23
|
|
|
15
24
|
/**
|
|
16
25
|
* Configuration options for ApiClient initialization.
|
|
@@ -20,6 +29,10 @@ export type ApiClientProps = {
|
|
|
20
29
|
axiosInstance?: AxiosInstance;
|
|
21
30
|
/** Base URL for API requests. If not provided, must be set in axiosInstance */
|
|
22
31
|
baseUrl?: string;
|
|
32
|
+
/** How to handle unknown responses. Defaults to "throw" */
|
|
33
|
+
unknownResponseHandling?: UnknownResponseHandling;
|
|
34
|
+
/** Predicate to determine if a status code represents success. Defaults to 2xx status codes */
|
|
35
|
+
isSuccessStatusCode?: (statusCode: number) => boolean;
|
|
23
36
|
};
|
|
24
37
|
|
|
25
38
|
/**
|
|
@@ -37,6 +50,10 @@ export abstract class ApiClient {
|
|
|
37
50
|
public readonly axiosInstance: AxiosInstance;
|
|
38
51
|
/** The base URL for all API requests */
|
|
39
52
|
public readonly baseUrl: string;
|
|
53
|
+
/** How to handle unknown responses */
|
|
54
|
+
public readonly unknownResponseHandling: UnknownResponseHandling;
|
|
55
|
+
/** Predicate to determine if a status code represents success */
|
|
56
|
+
public readonly isSuccessStatusCode: (statusCode: number) => boolean;
|
|
40
57
|
|
|
41
58
|
/**
|
|
42
59
|
* Creates a new ApiClient instance.
|
|
@@ -53,6 +70,24 @@ export abstract class ApiClient {
|
|
|
53
70
|
"Base URL must be provided either in axios instance or in constructor"
|
|
54
71
|
);
|
|
55
72
|
}
|
|
73
|
+
|
|
74
|
+
this.unknownResponseHandling = props.unknownResponseHandling ?? "throw";
|
|
75
|
+
this.isSuccessStatusCode =
|
|
76
|
+
props.isSuccessStatusCode ??
|
|
77
|
+
((statusCode: number) => statusCode >= 200 && statusCode < 300);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the process response options for this client instance.
|
|
82
|
+
*
|
|
83
|
+
* @returns The configuration options for processing responses
|
|
84
|
+
* @protected
|
|
85
|
+
*/
|
|
86
|
+
protected get processResponseOptions(): ProcessResponseOptions {
|
|
87
|
+
return {
|
|
88
|
+
unknownResponseHandling: this.unknownResponseHandling,
|
|
89
|
+
isSuccessStatusCode: this.isSuccessStatusCode,
|
|
90
|
+
};
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
/**
|
|
@@ -109,28 +144,17 @@ export abstract class ApiClient {
|
|
|
109
144
|
throw new Error("Network error: Unknown error");
|
|
110
145
|
}
|
|
111
146
|
|
|
112
|
-
throw new Error(
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Network error: ${error instanceof Error ? error.message : String(error)}`
|
|
149
|
+
);
|
|
113
150
|
}
|
|
114
151
|
}
|
|
115
152
|
|
|
116
153
|
private createResponse(response: AxiosResponse): IHttpResponse {
|
|
117
|
-
const header: IHttpHeader =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const lowerCaseKey = key.toLowerCase();
|
|
124
|
-
const headerCaseKey = Case.header(lowerCaseKey);
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
...acc,
|
|
128
|
-
[headerCaseKey]: value,
|
|
129
|
-
[lowerCaseKey]: value,
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
{}
|
|
133
|
-
);
|
|
154
|
+
const header: IHttpHeader = {};
|
|
155
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
156
|
+
this.addMultiValue(header, key, String(value));
|
|
157
|
+
});
|
|
134
158
|
|
|
135
159
|
return {
|
|
136
160
|
body: response.data,
|
|
@@ -177,13 +201,32 @@ export abstract class ApiClient {
|
|
|
177
201
|
|
|
178
202
|
private createUrl(path: string, query?: IHttpQuery): string {
|
|
179
203
|
const url = new URL(path, this.baseUrl);
|
|
180
|
-
|
|
181
204
|
this.addQuery(url, query);
|
|
182
|
-
|
|
183
205
|
return url.toString();
|
|
184
206
|
}
|
|
185
207
|
|
|
186
208
|
private createHeader(header: any): any {
|
|
187
209
|
return header;
|
|
188
210
|
}
|
|
211
|
+
|
|
212
|
+
private addMultiValue(
|
|
213
|
+
record: Record<string, string | string[]>,
|
|
214
|
+
key: string,
|
|
215
|
+
value: AxiosHeaderValue
|
|
216
|
+
): void {
|
|
217
|
+
const existing = record[key];
|
|
218
|
+
const preparedValue = Array.isArray(value)
|
|
219
|
+
? value.map(String)
|
|
220
|
+
: [String(value)];
|
|
221
|
+
if (existing) {
|
|
222
|
+
if (Array.isArray(existing)) {
|
|
223
|
+
existing.push(...preparedValue);
|
|
224
|
+
} else {
|
|
225
|
+
record[key] = [existing, ...preparedValue];
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
record[key] =
|
|
229
|
+
preparedValue.length > 1 ? preparedValue : (preparedValue[0] as string);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
189
232
|
}
|
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
/**
|
|
2
|
+
* This file was automatically generated by typeweaver.
|
|
3
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
4
|
+
*
|
|
5
|
+
* @generated by @rexeus/typeweaver
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { HttpResponse } from "@rexeus/typeweaver-core";
|
|
9
|
+
import type {
|
|
10
|
+
HttpMethod,
|
|
11
|
+
IHttpBody,
|
|
12
|
+
IHttpHeader,
|
|
13
|
+
IHttpParam,
|
|
14
|
+
IHttpQuery,
|
|
15
|
+
IHttpRequest,
|
|
16
|
+
IHttpResponse,
|
|
10
17
|
} from "@rexeus/typeweaver-core";
|
|
18
|
+
import type { UnknownResponseHandling } from "./ApiClient";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for processing HTTP responses.
|
|
22
|
+
*/
|
|
23
|
+
export type ProcessResponseOptions = {
|
|
24
|
+
readonly unknownResponseHandling: UnknownResponseHandling;
|
|
25
|
+
readonly isSuccessStatusCode: (statusCode: number) => boolean;
|
|
26
|
+
};
|
|
11
27
|
|
|
12
28
|
/**
|
|
13
29
|
* Abstract base class for type-safe API request commands.
|
|
@@ -55,7 +71,11 @@ export abstract class RequestCommand<
|
|
|
55
71
|
* - Error response handling
|
|
56
72
|
*
|
|
57
73
|
* @param response - The raw HTTP response from the server
|
|
74
|
+
* @param options - Configuration options for response processing
|
|
58
75
|
* @returns The processed, type-safe response object
|
|
59
76
|
*/
|
|
60
|
-
public abstract processResponse(
|
|
77
|
+
public abstract processResponse(
|
|
78
|
+
response: IHttpResponse,
|
|
79
|
+
options: ProcessResponseOptions
|
|
80
|
+
): HttpResponse;
|
|
61
81
|
}
|
package/dist/lib/index.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* This file was automatically generated by typeweaver.
|
|
4
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
5
|
+
*
|
|
6
|
+
* @generated by @rexeus/typeweaver
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import { ApiClient, type ApiClientProps } from "../lib/clients";
|
|
2
10
|
<% for (const operation of operations) { %>
|
|
3
|
-
import { <%= operation.operationId %>RequestCommand
|
|
11
|
+
import { <%= operation.operationId %>RequestCommand } from "./<%= operation.operationId %>RequestCommand";
|
|
12
|
+
import type {
|
|
13
|
+
Successful<%= operation.operationId %>Response
|
|
14
|
+
} from "<%= operation.requestFile %>";
|
|
4
15
|
<% } %>
|
|
5
16
|
|
|
6
17
|
export type <%= pascalCaseEntityName %>RequestCommands =
|
|
@@ -24,16 +35,6 @@ export class <%= pascalCaseEntityName %>Client extends ApiClient {
|
|
|
24
35
|
<% } %>
|
|
25
36
|
public async send(command: <%= pascalCaseEntityName %>RequestCommands): Promise<Successful<%= pascalCaseEntityName %>Responses> {
|
|
26
37
|
const response = await this.execute(command);
|
|
27
|
-
|
|
28
|
-
switch (true) {
|
|
29
|
-
<% for (const operation of operations) { %>
|
|
30
|
-
case command instanceof <%= operation.operationId %>RequestCommand: {
|
|
31
|
-
return command.processResponse(response);
|
|
32
|
-
}
|
|
33
|
-
<% } %>
|
|
34
|
-
default: {
|
|
35
|
-
throw new Error("Command is not supported");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
+
return command.processResponse(response, this.processResponseOptions);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* This file was automatically generated by typeweaver.
|
|
4
|
+
* DO NOT EDIT. Instead, modify the source definition file and generate again.
|
|
5
|
+
*
|
|
6
|
+
* @generated by @rexeus/typeweaver
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import definition from "<%= sourcePath %>";
|
|
10
|
+
import { HttpMethod, type IHttpResponse, ResponseValidationError, UnknownResponse } from "@rexeus/typeweaver-core";
|
|
11
|
+
import { RequestCommand, type ProcessResponseOptions } from "../lib/clients";
|
|
12
|
+
import { <%= pascalCaseOperationId %>ResponseValidator } from "<%= responseValidatorFile %>";
|
|
13
|
+
import type {
|
|
14
|
+
I<%= pascalCaseOperationId %>Request,
|
|
15
|
+
<%= headerTsType ? `I${pascalCaseOperationId}RequestHeader,` : "" %>
|
|
16
|
+
<%= paramTsType ? `I${pascalCaseOperationId}RequestParam,` : "" %>
|
|
17
|
+
<%= queryTsType ? `I${pascalCaseOperationId}RequestQuery,` : "" %>
|
|
18
|
+
<%= bodyTsType ? `I${pascalCaseOperationId}RequestBody,` : "" %>
|
|
19
|
+
Successful<%= pascalCaseOperationId %>Response,
|
|
20
|
+
} from "<%= requestFile %>";
|
|
21
|
+
<% for (const successResponse of [...ownSuccessResponses, ...sharedSuccessResponses]) { %>
|
|
22
|
+
import { <%= successResponse.name %>Response } from "<%= successResponseImportPath(successResponse) %>";
|
|
23
|
+
<% } %>
|
|
24
|
+
|
|
25
|
+
export class <%= pascalCaseOperationId %>RequestCommand extends RequestCommand implements I<%= pascalCaseOperationId %>Request {
|
|
26
|
+
public override readonly method = definition.method as HttpMethod.<%= method %>;
|
|
27
|
+
public override readonly path = definition.path;
|
|
28
|
+
|
|
29
|
+
public <%= headerTsType ? `override` : `declare` %> readonly header: <%= headerTsType ? `I${pascalCaseOperationId}RequestHeader` : `undefined` %>;
|
|
30
|
+
public <%= paramTsType ? `override` : `declare` %> readonly param: <%= paramTsType ? `I${pascalCaseOperationId}RequestParam` : `undefined` %>;
|
|
31
|
+
public <%= queryTsType ? `override` : `declare` %> readonly query: <%= queryTsType ? `I${pascalCaseOperationId}RequestQuery` : `undefined` %>;
|
|
32
|
+
public <%= bodyTsType ? `override` : `declare` %> readonly body: <%= bodyTsType ? `I${pascalCaseOperationId}RequestBody` : `undefined` %>;
|
|
33
|
+
|
|
34
|
+
private readonly responseValidator: <%= pascalCaseOperationId %>ResponseValidator;
|
|
35
|
+
|
|
36
|
+
public constructor(input: Omit<I<%= pascalCaseOperationId %>Request, "method" | "path">) {
|
|
37
|
+
super();
|
|
38
|
+
|
|
39
|
+
<% if (headerTsType) { %>
|
|
40
|
+
this.header = input.header;
|
|
41
|
+
<% } %>
|
|
42
|
+
|
|
43
|
+
<% if (paramTsType) { %>
|
|
44
|
+
this.param = input.param;
|
|
45
|
+
<% } %>
|
|
46
|
+
|
|
47
|
+
<% if (queryTsType) { %>
|
|
48
|
+
this.query = input.query;
|
|
49
|
+
<% } %>
|
|
50
|
+
|
|
51
|
+
<% if (bodyTsType) { %>
|
|
52
|
+
this.body = input.body;
|
|
53
|
+
<% } %>
|
|
54
|
+
|
|
55
|
+
this.responseValidator = new <%= pascalCaseOperationId %>ResponseValidator();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public processResponse(
|
|
59
|
+
response: IHttpResponse,
|
|
60
|
+
options: ProcessResponseOptions
|
|
61
|
+
): Successful<%= pascalCaseOperationId %>Response {
|
|
62
|
+
try {
|
|
63
|
+
const result = this.responseValidator.validate(response);
|
|
64
|
+
|
|
65
|
+
<% for (const successResponse of [...ownSuccessResponses, ...sharedSuccessResponses]) { %>
|
|
66
|
+
if (result instanceof <%= successResponse.name %>Response) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
<% } %>
|
|
70
|
+
|
|
71
|
+
throw result;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof ResponseValidationError) {
|
|
74
|
+
const unknownResponse = new UnknownResponse(
|
|
75
|
+
response.statusCode,
|
|
76
|
+
response.header,
|
|
77
|
+
response.body,
|
|
78
|
+
error
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (options.unknownResponseHandling === "passthrough" && options.isSuccessStatusCode(response.statusCode)) {
|
|
82
|
+
return unknownResponse as any;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw unknownResponse;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexeus/typeweaver-clients",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "HTTP client generator for
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "HTTP client generator for typeweaver API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"package.json",
|
|
17
|
-
"README.md"
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"NOTICE"
|
|
18
20
|
],
|
|
19
21
|
"keywords": [
|
|
20
22
|
"api",
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
"typeweaver"
|
|
27
29
|
],
|
|
28
30
|
"author": "Dennis Wentzien <dw@rexeus.com>",
|
|
29
|
-
"license": "
|
|
31
|
+
"license": "Apache-2.0",
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
32
34
|
"url": "git+https://github.com/rexeus/typeweaver.git",
|
|
@@ -37,18 +39,26 @@
|
|
|
37
39
|
},
|
|
38
40
|
"homepage": "https://github.com/rexeus/typeweaver#readme",
|
|
39
41
|
"peerDependencies": {
|
|
40
|
-
"axios": "^1.7.0"
|
|
42
|
+
"axios": "^1.7.0",
|
|
43
|
+
"@rexeus/typeweaver-core": "^0.0.4",
|
|
44
|
+
"@rexeus/typeweaver-gen": "^0.0.4"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
47
|
+
"@hono/node-server": "^1.16.0",
|
|
48
|
+
"axios": "^1.11.0",
|
|
49
|
+
"test-utils": "file:../test-utils",
|
|
50
|
+
"@rexeus/typeweaver-gen": "^0.0.4",
|
|
51
|
+
"@rexeus/typeweaver-core": "^0.0.4"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
43
54
|
"case": "^1.6.3",
|
|
44
|
-
"
|
|
45
|
-
"@rexeus/typeweaver-core": "^0.0.2",
|
|
46
|
-
"@rexeus/typeweaver-gen": "^0.0.2"
|
|
55
|
+
"@rexeus/typeweaver-zod-to-ts": "^0.0.4"
|
|
47
56
|
},
|
|
48
57
|
"scripts": {
|
|
49
58
|
"typecheck": "tsc --noEmit",
|
|
50
59
|
"format": "prettier --write .",
|
|
51
|
-
"build": "pkgroll --clean-dist && cp -r ./src/templates ./dist/templates && cp -r ./src/lib ./dist/lib",
|
|
60
|
+
"build": "pkgroll --clean-dist && cp -r ./src/templates ./dist/templates && cp -r ./src/lib ./dist/lib && cp ../../LICENSE ../../NOTICE ./dist/",
|
|
61
|
+
"test": "vitest --run",
|
|
52
62
|
"preversion": "npm run build"
|
|
53
63
|
}
|
|
54
64
|
}
|