zod-codegen 1.5.0 → 1.6.1
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/.github/workflows/ci.yml +6 -0
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +37 -0
- package/CONTRIBUTING.md +1 -1
- package/EXAMPLES.md +91 -12
- package/README.md +11 -4
- package/dist/scripts/add-js-extensions.d.ts +2 -0
- package/dist/scripts/add-js-extensions.d.ts.map +1 -0
- package/dist/scripts/add-js-extensions.js +66 -0
- package/dist/scripts/update-manifest.d.ts +14 -0
- package/dist/scripts/update-manifest.d.ts.map +1 -0
- package/dist/scripts/update-manifest.js +33 -0
- package/dist/src/assets/manifest.json +1 -1
- package/dist/src/cli.js +3 -3
- package/dist/src/generator.d.ts +46 -4
- package/dist/src/generator.d.ts.map +1 -1
- package/dist/src/generator.js +43 -1
- package/dist/src/interfaces/code-generator.d.ts +1 -1
- package/dist/src/interfaces/code-generator.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.d.ts +5 -3
- package/dist/src/services/code-generator.service.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.js +69 -1
- package/dist/src/services/file-reader.service.d.ts +2 -2
- package/dist/src/services/file-reader.service.d.ts.map +1 -1
- package/dist/src/services/file-writer.service.d.ts +1 -1
- package/dist/src/services/file-writer.service.d.ts.map +1 -1
- package/dist/src/services/import-builder.service.d.ts +1 -1
- package/dist/src/services/import-builder.service.d.ts.map +1 -1
- package/dist/src/services/import-builder.service.js +1 -1
- package/dist/src/services/type-builder.service.d.ts +1 -1
- package/dist/src/services/type-builder.service.d.ts.map +1 -1
- package/dist/src/types/generator-options.d.ts +1 -1
- package/dist/src/types/generator-options.d.ts.map +1 -1
- package/dist/src/utils/error-handler.d.ts +3 -2
- package/dist/src/utils/error-handler.d.ts.map +1 -1
- package/dist/src/utils/error-handler.js +4 -4
- package/dist/src/utils/reporter.d.ts +3 -2
- package/dist/src/utils/reporter.d.ts.map +1 -1
- package/dist/src/utils/reporter.js +7 -5
- package/dist/src/utils/signal-handler.d.ts +3 -2
- package/dist/src/utils/signal-handler.d.ts.map +1 -1
- package/dist/src/utils/signal-handler.js +4 -4
- package/examples/README.md +10 -1
- package/examples/petstore/README.md +6 -6
- package/examples/petstore/authenticated-usage.ts +1 -1
- package/examples/petstore/basic-usage.ts +1 -1
- package/examples/petstore/retry-handler-usage.ts +173 -0
- package/examples/petstore/server-variables-usage.ts +1 -1
- package/examples/petstore/type.ts +68 -47
- package/examples/pokeapi/README.md +3 -3
- package/examples/pokeapi/basic-usage.ts +1 -1
- package/examples/pokeapi/custom-client.ts +1 -1
- package/generated/type.ts +323 -0
- package/package.json +10 -13
- package/scripts/add-js-extensions.ts +79 -0
- package/scripts/update-manifest.ts +4 -2
- package/src/assets/manifest.json +1 -1
- package/src/cli.ts +7 -7
- package/src/generator.ts +51 -9
- package/src/interfaces/code-generator.ts +1 -1
- package/src/services/code-generator.service.ts +114 -8
- package/src/services/file-reader.service.ts +3 -3
- package/src/services/file-writer.service.ts +1 -1
- package/src/services/import-builder.service.ts +2 -2
- package/src/services/type-builder.service.ts +1 -1
- package/src/types/generator-options.ts +1 -1
- package/src/utils/error-handler.ts +6 -5
- package/src/utils/reporter.ts +6 -3
- package/src/utils/signal-handler.ts +10 -8
- package/tests/integration/cli-comprehensive.test.ts +123 -0
- package/tests/integration/cli.test.ts +2 -2
- package/tests/integration/error-scenarios.test.ts +240 -0
- package/tests/integration/snapshots.test.ts +131 -0
- package/tests/unit/code-generator-edge-cases.test.ts +551 -0
- package/tests/unit/code-generator.test.ts +385 -2
- package/tests/unit/file-reader.test.ts +16 -1
- package/tests/unit/generator.test.ts +19 -2
- package/tests/unit/naming-convention.test.ts +30 -1
- package/tests/unit/reporter.test.ts +63 -0
- package/tests/unit/type-builder.test.ts +131 -0
- package/tsconfig.json +3 -3
- package/dist/src/http/fetch-client.d.ts +0 -15
- package/dist/src/http/fetch-client.d.ts.map +0 -1
- package/dist/src/http/fetch-client.js +0 -140
- package/dist/src/polyfills/fetch.d.ts +0 -5
- package/dist/src/polyfills/fetch.d.ts.map +0 -1
- package/dist/src/polyfills/fetch.js +0 -18
- package/dist/src/types/http.d.ts +0 -25
- package/dist/src/types/http.d.ts.map +0 -1
- package/dist/src/types/http.js +0 -10
- package/dist/src/utils/manifest.d.ts +0 -8
- package/dist/src/utils/manifest.d.ts.map +0 -1
- package/dist/src/utils/manifest.js +0 -9
- package/dist/src/utils/tty.d.ts +0 -2
- package/dist/src/utils/tty.d.ts.map +0 -1
- package/dist/src/utils/tty.js +0 -3
- package/src/http/fetch-client.ts +0 -181
- package/src/polyfills/fetch.ts +0 -26
- package/src/types/http.ts +0 -35
- package/src/utils/manifest.ts +0 -17
- package/src/utils/tty.ts +0 -3
package/tsconfig.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"isolatedModules": true,
|
|
13
13
|
"lib": ["ES2022", "DOM"],
|
|
14
14
|
"module": "ESNext",
|
|
15
|
-
"moduleResolution": "
|
|
15
|
+
"moduleResolution": "bundler",
|
|
16
16
|
"declaration": true,
|
|
17
17
|
"declarationMap": true,
|
|
18
18
|
"noEmit": false,
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"target": "ES2022",
|
|
40
40
|
"typeRoots": ["./node_modules/@types"],
|
|
41
41
|
"useUnknownInCatchVariables": true,
|
|
42
|
-
"verbatimModuleSyntax":
|
|
42
|
+
"verbatimModuleSyntax": false
|
|
43
43
|
},
|
|
44
|
-
"exclude": ["node_modules", "dist", "build", "examples/**/*.ts", "tests/**/*.ts"
|
|
44
|
+
"exclude": ["node_modules", "dist", "build", "examples/**/*.ts", "tests/**/*.ts"],
|
|
45
45
|
"include": ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts", "vitest.config.ts"]
|
|
46
46
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { HttpClient, HttpRequestConfig, HttpResponse } from '../types/http.js';
|
|
2
|
-
export declare class FetchHttpClient implements HttpClient {
|
|
3
|
-
private readonly baseUrl;
|
|
4
|
-
private readonly defaultHeaders;
|
|
5
|
-
private readonly fetch;
|
|
6
|
-
private readonly Headers;
|
|
7
|
-
constructor(baseUrl?: string, defaultHeaders?: Record<string, string>);
|
|
8
|
-
request<TResponse = unknown, TRequest = unknown>(config: HttpRequestConfig<TRequest>): Promise<HttpResponse<TResponse>>;
|
|
9
|
-
private buildUrl;
|
|
10
|
-
private buildHeaders;
|
|
11
|
-
private buildBody;
|
|
12
|
-
private extractHeaders;
|
|
13
|
-
private parseResponse;
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=fetch-client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-client.d.ts","sourceRoot":"","sources":["../../../src/http/fetch-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAE,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAclF,qBAAa,eAAgB,YAAW,UAAU;IAK9C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc;IALjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAG1B,OAAO,SAAK,EACZ,cAAc,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAoBxD,OAAO,CAAC,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO,EACnD,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAClC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IA4DnC,OAAO,CAAC,QAAQ;IAehB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,SAAS;IAqBjB,OAAO,CAAC,cAAc;YAQR,aAAa;CAkB5B"}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { HttpError } from '../types/http.js';
|
|
2
|
-
export class FetchHttpClient {
|
|
3
|
-
baseUrl;
|
|
4
|
-
defaultHeaders;
|
|
5
|
-
fetch;
|
|
6
|
-
Headers;
|
|
7
|
-
constructor(baseUrl = '', defaultHeaders = {}) {
|
|
8
|
-
this.baseUrl = baseUrl;
|
|
9
|
-
this.defaultHeaders = defaultHeaders;
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
11
|
-
if (typeof globalThis.fetch === 'function' && globalThis.Headers) {
|
|
12
|
-
this.fetch = globalThis.fetch.bind(globalThis);
|
|
13
|
-
this.Headers = globalThis.Headers;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
17
|
-
this.fetch = window.fetch.bind(window);
|
|
18
|
-
this.Headers = window.Headers;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
throw new Error("Fetch API is not available. Please ensure you're running in a compatible environment or polyfill fetch.");
|
|
22
|
-
}
|
|
23
|
-
async request(config) {
|
|
24
|
-
const url = this.buildUrl(config.url, config.params);
|
|
25
|
-
const headers = this.buildHeaders(config.headers);
|
|
26
|
-
const body = this.buildBody(config.data, headers);
|
|
27
|
-
const controller = new AbortController();
|
|
28
|
-
const timeoutId = config.timeout
|
|
29
|
-
? setTimeout(() => {
|
|
30
|
-
controller.abort();
|
|
31
|
-
}, config.timeout)
|
|
32
|
-
: undefined;
|
|
33
|
-
try {
|
|
34
|
-
const response = await this.fetch(url, {
|
|
35
|
-
method: config.method,
|
|
36
|
-
headers,
|
|
37
|
-
body,
|
|
38
|
-
signal: controller.signal,
|
|
39
|
-
});
|
|
40
|
-
const responseHeaders = this.extractHeaders(response.headers);
|
|
41
|
-
const data = await this.parseResponse(response);
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new HttpError(`HTTP ${String(response.status)}: ${response.statusText}`, response.status, {
|
|
44
|
-
data,
|
|
45
|
-
status: response.status,
|
|
46
|
-
statusText: response.statusText,
|
|
47
|
-
headers: responseHeaders,
|
|
48
|
-
url: response.url,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
data,
|
|
53
|
-
status: response.status,
|
|
54
|
-
statusText: response.statusText,
|
|
55
|
-
headers: responseHeaders,
|
|
56
|
-
url: response.url,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
if (error instanceof HttpError) {
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
if (error instanceof Error) {
|
|
64
|
-
if (error.name === 'AbortError') {
|
|
65
|
-
throw new HttpError('Request timeout', 408);
|
|
66
|
-
}
|
|
67
|
-
throw new HttpError(`Network error: ${error.message}`, 0);
|
|
68
|
-
}
|
|
69
|
-
throw new HttpError('Unknown network error', 0);
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
if (timeoutId) {
|
|
73
|
-
clearTimeout(timeoutId);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
buildUrl(path, params) {
|
|
78
|
-
const url = new URL(path, this.baseUrl || 'http://localhost');
|
|
79
|
-
if (params) {
|
|
80
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
82
|
-
if (value !== undefined && value !== null) {
|
|
83
|
-
url.searchParams.set(key, String(value));
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return url.toString();
|
|
88
|
-
}
|
|
89
|
-
buildHeaders(customHeaders) {
|
|
90
|
-
const headers = new this.Headers();
|
|
91
|
-
Object.entries(this.defaultHeaders).forEach(([key, value]) => {
|
|
92
|
-
headers.set(key, value);
|
|
93
|
-
});
|
|
94
|
-
if (customHeaders) {
|
|
95
|
-
Object.entries(customHeaders).forEach(([key, value]) => {
|
|
96
|
-
headers.set(key, value);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return headers;
|
|
100
|
-
}
|
|
101
|
-
buildBody(data, headers) {
|
|
102
|
-
if (!data) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
const contentType = headers?.get('content-type') ?? 'application/json';
|
|
106
|
-
if (contentType.includes('application/json')) {
|
|
107
|
-
if (!headers?.has('content-type')) {
|
|
108
|
-
headers?.set('content-type', 'application/json');
|
|
109
|
-
}
|
|
110
|
-
return JSON.stringify(data);
|
|
111
|
-
}
|
|
112
|
-
if (typeof data === 'string') {
|
|
113
|
-
return data;
|
|
114
|
-
}
|
|
115
|
-
return JSON.stringify(data);
|
|
116
|
-
}
|
|
117
|
-
extractHeaders(headers) {
|
|
118
|
-
const result = {};
|
|
119
|
-
headers.forEach((value, key) => {
|
|
120
|
-
result[key.toLowerCase()] = value;
|
|
121
|
-
});
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
async parseResponse(response) {
|
|
125
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
126
|
-
if (contentType.includes('application/json')) {
|
|
127
|
-
return (await response.json());
|
|
128
|
-
}
|
|
129
|
-
if (contentType.includes('text/')) {
|
|
130
|
-
return (await response.text());
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
const text = await response.text();
|
|
134
|
-
return text ? JSON.parse(text) : {};
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return {};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/polyfills/fetch.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1F"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export async function setupFetchPolyfill(options = {}) {
|
|
2
|
-
if (typeof fetch === 'function') {
|
|
3
|
-
return;
|
|
4
|
-
}
|
|
5
|
-
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
if (options.enableNodejsPolyfill && typeof process !== 'undefined' && process.versions.node) {
|
|
9
|
-
try {
|
|
10
|
-
await import('undici');
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
// Fall through to error
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
throw new Error('Fetch API is not available. ' + 'For Node.js environments, please install undici: npm install undici');
|
|
18
|
-
}
|
package/dist/src/types/http.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export interface HttpRequestConfig<TData = unknown> {
|
|
2
|
-
readonly url: string;
|
|
3
|
-
readonly method: HttpMethod;
|
|
4
|
-
readonly headers?: Record<string, string>;
|
|
5
|
-
readonly params?: Record<string, string | number | boolean>;
|
|
6
|
-
readonly data?: TData;
|
|
7
|
-
readonly timeout?: number;
|
|
8
|
-
}
|
|
9
|
-
export interface HttpResponse<TData = unknown> {
|
|
10
|
-
readonly data: TData;
|
|
11
|
-
readonly status: number;
|
|
12
|
-
readonly statusText: string;
|
|
13
|
-
readonly headers: Record<string, string>;
|
|
14
|
-
readonly url: string;
|
|
15
|
-
}
|
|
16
|
-
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
17
|
-
export interface HttpClient {
|
|
18
|
-
request<TResponse = unknown, TRequest = unknown>(config: HttpRequestConfig<TRequest>): Promise<HttpResponse<TResponse>>;
|
|
19
|
-
}
|
|
20
|
-
export declare class HttpError extends Error {
|
|
21
|
-
readonly status: number;
|
|
22
|
-
readonly response?: HttpResponse | undefined;
|
|
23
|
-
constructor(message: string, status: number, response?: HttpResponse | undefined);
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=http.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/types/http.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,OAAO;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY,CAAC,KAAK,GAAG,OAAO;IAC3C,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,SAAS,GAAG,OAAO,EAAE,QAAQ,GAAG,OAAO,EAC7C,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAClC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;CACrC;AAED,qBAAa,SAAU,SAAQ,KAAK;aAGhB,MAAM,EAAE,MAAM;aACd,QAAQ,CAAC,EAAE,YAAY;gBAFvC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,YAAY,YAAA;CAK1C"}
|
package/dist/src/types/http.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/utils/manifest.ts"],"names":[],"mappings":"AAEA,UAAU,QAAQ;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAQD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
package/dist/src/utils/tty.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tty.d.ts","sourceRoot":"","sources":["../../../src/utils/tty.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAEtD"}
|
package/dist/src/utils/tty.js
DELETED
package/src/http/fetch-client.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type {HttpClient, HttpRequestConfig, HttpResponse} from '../types/http.js';
|
|
2
|
-
import {HttpError} from '../types/http.js';
|
|
3
|
-
|
|
4
|
-
declare const globalThis: typeof global & {
|
|
5
|
-
fetch?: typeof fetch;
|
|
6
|
-
Headers?: typeof Headers;
|
|
7
|
-
Request?: typeof Request;
|
|
8
|
-
Response?: typeof Response;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type FetchFunction = (input: string | Request, init?: RequestInit) => Promise<Response>;
|
|
12
|
-
|
|
13
|
-
type HeadersConstructor = new (init?: HeadersInit) => Headers;
|
|
14
|
-
|
|
15
|
-
export class FetchHttpClient implements HttpClient {
|
|
16
|
-
private readonly fetch: FetchFunction;
|
|
17
|
-
private readonly Headers: HeadersConstructor;
|
|
18
|
-
|
|
19
|
-
constructor(
|
|
20
|
-
private readonly baseUrl = '',
|
|
21
|
-
private readonly defaultHeaders: Record<string, string> = {},
|
|
22
|
-
) {
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
24
|
-
if (typeof globalThis.fetch === 'function' && globalThis.Headers) {
|
|
25
|
-
this.fetch = globalThis.fetch.bind(globalThis);
|
|
26
|
-
this.Headers = globalThis.Headers;
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
31
|
-
this.fetch = window.fetch.bind(window);
|
|
32
|
-
this.Headers = window.Headers;
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
throw new Error(
|
|
37
|
-
"Fetch API is not available. Please ensure you're running in a compatible environment or polyfill fetch.",
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async request<TResponse = unknown, TRequest = unknown>(
|
|
42
|
-
config: HttpRequestConfig<TRequest>,
|
|
43
|
-
): Promise<HttpResponse<TResponse>> {
|
|
44
|
-
const url = this.buildUrl(config.url, config.params);
|
|
45
|
-
const headers = this.buildHeaders(config.headers);
|
|
46
|
-
const body = this.buildBody(config.data, headers);
|
|
47
|
-
|
|
48
|
-
const controller = new AbortController();
|
|
49
|
-
const timeoutId = config.timeout
|
|
50
|
-
? setTimeout(() => {
|
|
51
|
-
controller.abort();
|
|
52
|
-
}, config.timeout)
|
|
53
|
-
: undefined;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const response = await this.fetch(url, {
|
|
57
|
-
method: config.method,
|
|
58
|
-
headers,
|
|
59
|
-
body,
|
|
60
|
-
signal: controller.signal,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const responseHeaders = this.extractHeaders(response.headers);
|
|
64
|
-
const data = await this.parseResponse<TResponse>(response);
|
|
65
|
-
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
throw new HttpError(`HTTP ${String(response.status)}: ${response.statusText}`, response.status, {
|
|
68
|
-
data,
|
|
69
|
-
status: response.status,
|
|
70
|
-
statusText: response.statusText,
|
|
71
|
-
headers: responseHeaders,
|
|
72
|
-
url: response.url,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
data,
|
|
78
|
-
status: response.status,
|
|
79
|
-
statusText: response.statusText,
|
|
80
|
-
headers: responseHeaders,
|
|
81
|
-
url: response.url,
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
if (error instanceof HttpError) {
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (error instanceof Error) {
|
|
89
|
-
if (error.name === 'AbortError') {
|
|
90
|
-
throw new HttpError('Request timeout', 408);
|
|
91
|
-
}
|
|
92
|
-
throw new HttpError(`Network error: ${error.message}`, 0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new HttpError('Unknown network error', 0);
|
|
96
|
-
} finally {
|
|
97
|
-
if (timeoutId) {
|
|
98
|
-
clearTimeout(timeoutId);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private buildUrl(path: string, params?: Record<string, string | number | boolean>): string {
|
|
104
|
-
const url = new URL(path, this.baseUrl || 'http://localhost');
|
|
105
|
-
|
|
106
|
-
if (params) {
|
|
107
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
109
|
-
if (value !== undefined && value !== null) {
|
|
110
|
-
url.searchParams.set(key, String(value));
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return url.toString();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private buildHeaders(customHeaders?: Record<string, string>): Headers {
|
|
119
|
-
const headers = new this.Headers();
|
|
120
|
-
|
|
121
|
-
Object.entries(this.defaultHeaders).forEach(([key, value]) => {
|
|
122
|
-
headers.set(key, value);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
if (customHeaders) {
|
|
126
|
-
Object.entries(customHeaders).forEach(([key, value]) => {
|
|
127
|
-
headers.set(key, value);
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return headers;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private buildBody(data?: unknown, headers?: Headers): string | null {
|
|
135
|
-
if (!data) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const contentType = headers?.get('content-type') ?? 'application/json';
|
|
140
|
-
|
|
141
|
-
if (contentType.includes('application/json')) {
|
|
142
|
-
if (!headers?.has('content-type')) {
|
|
143
|
-
headers?.set('content-type', 'application/json');
|
|
144
|
-
}
|
|
145
|
-
return JSON.stringify(data);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (typeof data === 'string') {
|
|
149
|
-
return data;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return JSON.stringify(data);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private extractHeaders(headers: Headers): Record<string, string> {
|
|
156
|
-
const result: Record<string, string> = {};
|
|
157
|
-
headers.forEach((value, key) => {
|
|
158
|
-
result[key.toLowerCase()] = value;
|
|
159
|
-
});
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private async parseResponse<TResponse>(response: Response): Promise<TResponse> {
|
|
164
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
165
|
-
|
|
166
|
-
if (contentType.includes('application/json')) {
|
|
167
|
-
return (await response.json()) as TResponse;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (contentType.includes('text/')) {
|
|
171
|
-
return (await response.text()) as TResponse;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const text = await response.text();
|
|
176
|
-
return text ? (JSON.parse(text) as TResponse) : ({} as TResponse);
|
|
177
|
-
} catch {
|
|
178
|
-
return {} as TResponse;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
package/src/polyfills/fetch.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export interface FetchPolyfillOptions {
|
|
2
|
-
enableNodejsPolyfill?: boolean;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export async function setupFetchPolyfill(options: FetchPolyfillOptions = {}): Promise<void> {
|
|
6
|
-
if (typeof fetch === 'function') {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (options.enableNodejsPolyfill && typeof process !== 'undefined' && process.versions.node) {
|
|
15
|
-
try {
|
|
16
|
-
await import('undici');
|
|
17
|
-
return;
|
|
18
|
-
} catch {
|
|
19
|
-
// Fall through to error
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
throw new Error(
|
|
24
|
-
'Fetch API is not available. ' + 'For Node.js environments, please install undici: npm install undici',
|
|
25
|
-
);
|
|
26
|
-
}
|
package/src/types/http.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface HttpRequestConfig<TData = unknown> {
|
|
2
|
-
readonly url: string;
|
|
3
|
-
readonly method: HttpMethod;
|
|
4
|
-
readonly headers?: Record<string, string>;
|
|
5
|
-
readonly params?: Record<string, string | number | boolean>;
|
|
6
|
-
readonly data?: TData;
|
|
7
|
-
readonly timeout?: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface HttpResponse<TData = unknown> {
|
|
11
|
-
readonly data: TData;
|
|
12
|
-
readonly status: number;
|
|
13
|
-
readonly statusText: string;
|
|
14
|
-
readonly headers: Record<string, string>;
|
|
15
|
-
readonly url: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
19
|
-
|
|
20
|
-
export interface HttpClient {
|
|
21
|
-
request<TResponse = unknown, TRequest = unknown>(
|
|
22
|
-
config: HttpRequestConfig<TRequest>,
|
|
23
|
-
): Promise<HttpResponse<TResponse>>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class HttpError extends Error {
|
|
27
|
-
constructor(
|
|
28
|
-
message: string,
|
|
29
|
-
public readonly status: number,
|
|
30
|
-
public readonly response?: HttpResponse,
|
|
31
|
-
) {
|
|
32
|
-
super(message);
|
|
33
|
-
this.name = 'HttpError';
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/utils/manifest.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {z} from 'zod';
|
|
2
|
-
|
|
3
|
-
interface Manifest {
|
|
4
|
-
readonly name: string;
|
|
5
|
-
readonly version: string;
|
|
6
|
-
readonly description: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const ManifestSchema = z.object({
|
|
10
|
-
name: z.string().min(1),
|
|
11
|
-
version: z.string().min(1),
|
|
12
|
-
description: z.string().min(1),
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
export function isManifest(input: unknown): input is Manifest {
|
|
16
|
-
return ManifestSchema.safeParse(input).success;
|
|
17
|
-
}
|
package/src/utils/tty.ts
DELETED