@kubb/plugin-client 5.0.0-beta.30 → 5.0.0-beta.31
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/dist/clients/fetch.cjs +60 -10
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.js +60 -10
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +107 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +107 -56
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/extension.yaml +4 -4
- package/package.json +6 -6
- package/src/clients/fetch.ts +80 -7
- package/src/components/ClassClient.tsx +2 -1
- package/src/components/Client.tsx +6 -2
- package/src/components/Operations.tsx +2 -1
- package/src/components/StaticClassClient.tsx +2 -1
- package/src/components/Url.tsx +1 -0
- package/src/generators/classClientGenerator.tsx +13 -11
- package/src/generators/clientGenerator.tsx +14 -22
- package/src/generators/operationsGenerator.tsx +2 -2
- package/src/generators/staticClassClientGenerator.tsx +13 -11
- package/src/plugin.ts +4 -16
- package/src/types.ts +4 -3
- package/src/utils.ts +6 -4
package/dist/clients/fetch.cjs
CHANGED
|
@@ -37,6 +37,59 @@ function serializeHeaders(headers) {
|
|
|
37
37
|
}
|
|
38
38
|
return result;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Serializes the request body into the form `fetch` expects.
|
|
42
|
+
* `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer` and string bodies are passed through
|
|
43
|
+
* untouched. Plain objects are encoded as `URLSearchParams` for `application/x-www-form-urlencoded`
|
|
44
|
+
* and JSON-serialized otherwise.
|
|
45
|
+
*/
|
|
46
|
+
function serializeBody(data, contentType) {
|
|
47
|
+
if (data === void 0 || data === null) return void 0;
|
|
48
|
+
if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data;
|
|
49
|
+
if (typeof data === "string") return data;
|
|
50
|
+
if (contentType?.includes("application/x-www-form-urlencoded")) return new URLSearchParams(data);
|
|
51
|
+
return JSON.stringify(data);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.
|
|
55
|
+
*/
|
|
56
|
+
function detectResponseType(contentType) {
|
|
57
|
+
if (!contentType) return void 0;
|
|
58
|
+
if (contentType.includes("application/json") || contentType.includes("text/json")) return "json";
|
|
59
|
+
if (contentType.includes("text/")) return "text";
|
|
60
|
+
if (contentType.includes("image/") || contentType.includes("application/octet-stream")) return "blob";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parses a `fetch` response body.
|
|
64
|
+
*
|
|
65
|
+
* - Empty responses (204/205/304 or no body) resolve to `{}`.
|
|
66
|
+
* - An explicit `responseType` (or one detected from the `Content-Type` header) forces the matching
|
|
67
|
+
* `Response` method.
|
|
68
|
+
* - As a last resort the body is read as text and `JSON.parse`d, falling back to the raw text.
|
|
69
|
+
*/
|
|
70
|
+
async function parseResponse(response, responseType) {
|
|
71
|
+
if ([
|
|
72
|
+
204,
|
|
73
|
+
205,
|
|
74
|
+
304
|
|
75
|
+
].includes(response.status) || !response.body) return {};
|
|
76
|
+
switch (responseType ?? detectResponseType(response.headers.get("Content-Type"))) {
|
|
77
|
+
case "text":
|
|
78
|
+
case "document": return await response.text();
|
|
79
|
+
case "blob": return await response.blob();
|
|
80
|
+
case "arraybuffer": return await response.arrayBuffer();
|
|
81
|
+
case "stream": return response.body;
|
|
82
|
+
case "json": return await response.json();
|
|
83
|
+
}
|
|
84
|
+
if (responseType) return await response.json();
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
if (!text) return {};
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(text);
|
|
89
|
+
} catch {
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
40
93
|
const client = async (paramsConfig, _request) => {
|
|
41
94
|
const normalizedParams = new URLSearchParams();
|
|
42
95
|
const config = mergeConfig(getConfig(), paramsConfig);
|
|
@@ -45,22 +98,19 @@ const client = async (paramsConfig, _request) => {
|
|
|
45
98
|
});
|
|
46
99
|
let targetUrl = [config.baseURL, config.url].filter(Boolean).join("");
|
|
47
100
|
if (config.params) targetUrl += `?${normalizedParams}`;
|
|
101
|
+
const headers = {
|
|
102
|
+
...config.contentType && config.contentType !== "multipart/form-data" ? { "Content-Type": config.contentType } : {},
|
|
103
|
+
...serializeHeaders(config.headers)
|
|
104
|
+
};
|
|
48
105
|
const response = await globalThis.fetch(targetUrl, {
|
|
49
106
|
credentials: config.credentials || "same-origin",
|
|
50
107
|
method: config.method?.toUpperCase(),
|
|
51
|
-
body: config.data
|
|
108
|
+
body: serializeBody(config.data, headers["Content-Type"] ?? headers["content-type"]),
|
|
52
109
|
signal: config.signal,
|
|
53
|
-
headers
|
|
54
|
-
...config.contentType && config.contentType !== "multipart/form-data" ? { "Content-Type": config.contentType } : {},
|
|
55
|
-
...serializeHeaders(config.headers)
|
|
56
|
-
}
|
|
110
|
+
headers
|
|
57
111
|
});
|
|
58
112
|
return {
|
|
59
|
-
data:
|
|
60
|
-
204,
|
|
61
|
-
205,
|
|
62
|
-
304
|
|
63
|
-
].includes(response.status) || !response.body ? {} : await response.json(),
|
|
113
|
+
data: await parseResponse(response, config.responseType),
|
|
64
114
|
status: response.status,
|
|
65
115
|
statusText: response.statusText,
|
|
66
116
|
headers: response.headers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.cjs","names":[],"sources":["../../src/clients/fetch.ts"],"sourcesContent":["/**\n * RequestCredentials\n */\nexport type RequestCredentials = 'omit' | 'same-origin' | 'include'\n\n/**\n * Header values may be objects (e.g. JSON-encoded filter headers like `X-Filter`).\n * Non-string values are JSON-serialized before the request is sent.\n */\nexport type HeaderValue = string | number | boolean | null | undefined | object\nexport type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>\n\n/**\n * Subset of FetchRequestConfig\n */\nexport type RequestConfig<TData = unknown> = {\n baseURL?: string\n url?: string\n method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'\n params?: unknown\n data?: TData | FormData\n responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'\n signal?: AbortSignal\n headers?: HeadersInit\n credentials?: RequestCredentials\n contentType?: string\n}\n\n/**\n * Subset of FetchResponse\n */\nexport type ResponseConfig<TData = unknown> = {\n data: TData\n status: number\n statusText: string\n headers: Headers\n}\n\nlet _config: Partial<RequestConfig> = {}\n\nexport const getConfig = () => _config\n\nexport const setConfig = (config: Partial<RequestConfig>) => {\n _config = config\n return getConfig()\n}\n\nexport const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {\n return configs.reduce<Partial<T>>((merged, config) => {\n return {\n ...merged,\n ...config,\n headers: {\n ...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),\n ...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),\n },\n }\n }, {})\n}\n\n/**\n * Serializes header values into the string form `fetch` expects.\n * Objects (including arrays) are JSON-stringified so spec-defined object\n * headers like `X-Filter` are sent in their canonical JSON-string form.\n */\nfunction serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {}\n const entries = Array.isArray(headers) ? headers : Object.entries(headers)\n const result: Record<string, string> = {}\n for (const [key, value] of entries) {\n if (value === undefined || value === null) continue\n result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)\n }\n return result\n}\n\nexport type ResponseErrorConfig<TError = unknown> = TError\n\nexport type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(\n config: RequestConfig<TRequestData>,\n request?: unknown,\n) => Promise<ResponseConfig<TResponseData>>\n\nexport const client = async <TResponseData, _TError = unknown, RequestData = unknown>(\n paramsConfig: RequestConfig<RequestData>,\n _request?: unknown,\n): Promise<ResponseConfig<TResponseData>> => {\n const normalizedParams = new URLSearchParams()\n\n const config = mergeConfig(getConfig(), paramsConfig)\n\n Object.entries(config.params || {}).forEach(([key, value]) => {\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : value.toString())\n }\n })\n\n let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')\n\n if (config.params) {\n targetUrl += `?${normalizedParams}`\n }\n\n const response = await globalThis.fetch(targetUrl, {\n credentials: config.credentials || 'same-origin',\n method: config.method?.toUpperCase(),\n body:
|
|
1
|
+
{"version":3,"file":"fetch.cjs","names":[],"sources":["../../src/clients/fetch.ts"],"sourcesContent":["/**\n * RequestCredentials\n */\nexport type RequestCredentials = 'omit' | 'same-origin' | 'include'\n\n/**\n * Header values may be objects (e.g. JSON-encoded filter headers like `X-Filter`).\n * Non-string values are JSON-serialized before the request is sent.\n */\nexport type HeaderValue = string | number | boolean | null | undefined | object\nexport type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>\n\n/**\n * Subset of FetchRequestConfig\n */\nexport type RequestConfig<TData = unknown> = {\n baseURL?: string\n url?: string\n method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'\n params?: unknown\n data?: TData | FormData\n responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'\n signal?: AbortSignal\n headers?: HeadersInit\n credentials?: RequestCredentials\n contentType?: string\n}\n\n/**\n * Subset of FetchResponse\n */\nexport type ResponseConfig<TData = unknown> = {\n data: TData\n status: number\n statusText: string\n headers: Headers\n}\n\nlet _config: Partial<RequestConfig> = {}\n\nexport const getConfig = () => _config\n\nexport const setConfig = (config: Partial<RequestConfig>) => {\n _config = config\n return getConfig()\n}\n\nexport const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {\n return configs.reduce<Partial<T>>((merged, config) => {\n return {\n ...merged,\n ...config,\n headers: {\n ...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),\n ...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),\n },\n }\n }, {})\n}\n\n/**\n * Serializes header values into the string form `fetch` expects.\n * Objects (including arrays) are JSON-stringified so spec-defined object\n * headers like `X-Filter` are sent in their canonical JSON-string form.\n */\nfunction serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {}\n const entries = Array.isArray(headers) ? headers : Object.entries(headers)\n const result: Record<string, string> = {}\n for (const [key, value] of entries) {\n if (value === undefined || value === null) continue\n result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)\n }\n return result\n}\n\n/**\n * Serializes the request body into the form `fetch` expects.\n * `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer` and string bodies are passed through\n * untouched. Plain objects are encoded as `URLSearchParams` for `application/x-www-form-urlencoded`\n * and JSON-serialized otherwise.\n */\nfunction serializeBody(data: unknown, contentType?: string): BodyInit | undefined {\n if (data === undefined || data === null) return undefined\n if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n return data as BodyInit\n }\n if (typeof data === 'string') return data\n if (contentType?.includes('application/x-www-form-urlencoded')) {\n return new URLSearchParams(data as Record<string, string>)\n }\n return JSON.stringify(data)\n}\n\n/**\n * Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.\n */\nfunction detectResponseType(contentType: string | null): RequestConfig['responseType'] {\n if (!contentType) return undefined\n if (contentType.includes('application/json') || contentType.includes('text/json')) return 'json'\n if (contentType.includes('text/')) return 'text'\n if (contentType.includes('image/') || contentType.includes('application/octet-stream')) return 'blob'\n return undefined\n}\n\n/**\n * Parses a `fetch` response body.\n *\n * - Empty responses (204/205/304 or no body) resolve to `{}`.\n * - An explicit `responseType` (or one detected from the `Content-Type` header) forces the matching\n * `Response` method.\n * - As a last resort the body is read as text and `JSON.parse`d, falling back to the raw text.\n */\nasync function parseResponse<TData>(response: Response, responseType?: RequestConfig['responseType']): Promise<TData> {\n if ([204, 205, 304].includes(response.status) || !response.body) {\n return {} as TData\n }\n\n switch (responseType ?? detectResponseType(response.headers.get('Content-Type'))) {\n case 'text':\n case 'document':\n return (await response.text()) as TData\n case 'blob':\n return (await response.blob()) as TData\n case 'arraybuffer':\n return (await response.arrayBuffer()) as TData\n case 'stream':\n return response.body as TData\n case 'json':\n return (await response.json()) as TData\n }\n\n // Explicit but unrecognized responseType keeps the JSON default; otherwise read text and parse it.\n if (responseType) {\n return (await response.json()) as TData\n }\n const text = await response.text()\n if (!text) {\n return {} as TData\n }\n try {\n return JSON.parse(text) as TData\n } catch {\n return text as TData\n }\n}\n\nexport type ResponseErrorConfig<TError = unknown> = TError\n\nexport type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(\n config: RequestConfig<TRequestData>,\n request?: unknown,\n) => Promise<ResponseConfig<TResponseData>>\n\nexport const client = async <TResponseData, _TError = unknown, RequestData = unknown>(\n paramsConfig: RequestConfig<RequestData>,\n _request?: unknown,\n): Promise<ResponseConfig<TResponseData>> => {\n const normalizedParams = new URLSearchParams()\n\n const config = mergeConfig(getConfig(), paramsConfig)\n\n Object.entries(config.params || {}).forEach(([key, value]) => {\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : value.toString())\n }\n })\n\n let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')\n\n if (config.params) {\n targetUrl += `?${normalizedParams}`\n }\n\n const headers: Record<string, string> = {\n ...(config.contentType && config.contentType !== 'multipart/form-data' ? { 'Content-Type': config.contentType } : {}),\n ...serializeHeaders(config.headers),\n }\n\n const response = await globalThis.fetch(targetUrl, {\n credentials: config.credentials || 'same-origin',\n method: config.method?.toUpperCase(),\n body: serializeBody(config.data, headers['Content-Type'] ?? headers['content-type']),\n signal: config.signal,\n headers,\n })\n\n const data = await parseResponse<TResponseData>(response, config.responseType)\n\n return {\n data,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers as Headers,\n }\n}\n\nclient.getConfig = getConfig\nclient.setConfig = setConfig\n\nexport default client\n"],"mappings":"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"}
|
package/dist/clients/fetch.js
CHANGED
|
@@ -33,6 +33,59 @@ function serializeHeaders(headers) {
|
|
|
33
33
|
}
|
|
34
34
|
return result;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Serializes the request body into the form `fetch` expects.
|
|
38
|
+
* `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer` and string bodies are passed through
|
|
39
|
+
* untouched. Plain objects are encoded as `URLSearchParams` for `application/x-www-form-urlencoded`
|
|
40
|
+
* and JSON-serialized otherwise.
|
|
41
|
+
*/
|
|
42
|
+
function serializeBody(data, contentType) {
|
|
43
|
+
if (data === void 0 || data === null) return void 0;
|
|
44
|
+
if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data;
|
|
45
|
+
if (typeof data === "string") return data;
|
|
46
|
+
if (contentType?.includes("application/x-www-form-urlencoded")) return new URLSearchParams(data);
|
|
47
|
+
return JSON.stringify(data);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.
|
|
51
|
+
*/
|
|
52
|
+
function detectResponseType(contentType) {
|
|
53
|
+
if (!contentType) return void 0;
|
|
54
|
+
if (contentType.includes("application/json") || contentType.includes("text/json")) return "json";
|
|
55
|
+
if (contentType.includes("text/")) return "text";
|
|
56
|
+
if (contentType.includes("image/") || contentType.includes("application/octet-stream")) return "blob";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parses a `fetch` response body.
|
|
60
|
+
*
|
|
61
|
+
* - Empty responses (204/205/304 or no body) resolve to `{}`.
|
|
62
|
+
* - An explicit `responseType` (or one detected from the `Content-Type` header) forces the matching
|
|
63
|
+
* `Response` method.
|
|
64
|
+
* - As a last resort the body is read as text and `JSON.parse`d, falling back to the raw text.
|
|
65
|
+
*/
|
|
66
|
+
async function parseResponse(response, responseType) {
|
|
67
|
+
if ([
|
|
68
|
+
204,
|
|
69
|
+
205,
|
|
70
|
+
304
|
|
71
|
+
].includes(response.status) || !response.body) return {};
|
|
72
|
+
switch (responseType ?? detectResponseType(response.headers.get("Content-Type"))) {
|
|
73
|
+
case "text":
|
|
74
|
+
case "document": return await response.text();
|
|
75
|
+
case "blob": return await response.blob();
|
|
76
|
+
case "arraybuffer": return await response.arrayBuffer();
|
|
77
|
+
case "stream": return response.body;
|
|
78
|
+
case "json": return await response.json();
|
|
79
|
+
}
|
|
80
|
+
if (responseType) return await response.json();
|
|
81
|
+
const text = await response.text();
|
|
82
|
+
if (!text) return {};
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(text);
|
|
85
|
+
} catch {
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
36
89
|
const client = async (paramsConfig, _request) => {
|
|
37
90
|
const normalizedParams = new URLSearchParams();
|
|
38
91
|
const config = mergeConfig(getConfig(), paramsConfig);
|
|
@@ -41,22 +94,19 @@ const client = async (paramsConfig, _request) => {
|
|
|
41
94
|
});
|
|
42
95
|
let targetUrl = [config.baseURL, config.url].filter(Boolean).join("");
|
|
43
96
|
if (config.params) targetUrl += `?${normalizedParams}`;
|
|
97
|
+
const headers = {
|
|
98
|
+
...config.contentType && config.contentType !== "multipart/form-data" ? { "Content-Type": config.contentType } : {},
|
|
99
|
+
...serializeHeaders(config.headers)
|
|
100
|
+
};
|
|
44
101
|
const response = await globalThis.fetch(targetUrl, {
|
|
45
102
|
credentials: config.credentials || "same-origin",
|
|
46
103
|
method: config.method?.toUpperCase(),
|
|
47
|
-
body: config.data
|
|
104
|
+
body: serializeBody(config.data, headers["Content-Type"] ?? headers["content-type"]),
|
|
48
105
|
signal: config.signal,
|
|
49
|
-
headers
|
|
50
|
-
...config.contentType && config.contentType !== "multipart/form-data" ? { "Content-Type": config.contentType } : {},
|
|
51
|
-
...serializeHeaders(config.headers)
|
|
52
|
-
}
|
|
106
|
+
headers
|
|
53
107
|
});
|
|
54
108
|
return {
|
|
55
|
-
data:
|
|
56
|
-
204,
|
|
57
|
-
205,
|
|
58
|
-
304
|
|
59
|
-
].includes(response.status) || !response.body ? {} : await response.json(),
|
|
109
|
+
data: await parseResponse(response, config.responseType),
|
|
60
110
|
status: response.status,
|
|
61
111
|
statusText: response.statusText,
|
|
62
112
|
headers: response.headers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.js","names":[],"sources":["../../src/clients/fetch.ts"],"sourcesContent":["/**\n * RequestCredentials\n */\nexport type RequestCredentials = 'omit' | 'same-origin' | 'include'\n\n/**\n * Header values may be objects (e.g. JSON-encoded filter headers like `X-Filter`).\n * Non-string values are JSON-serialized before the request is sent.\n */\nexport type HeaderValue = string | number | boolean | null | undefined | object\nexport type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>\n\n/**\n * Subset of FetchRequestConfig\n */\nexport type RequestConfig<TData = unknown> = {\n baseURL?: string\n url?: string\n method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'\n params?: unknown\n data?: TData | FormData\n responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'\n signal?: AbortSignal\n headers?: HeadersInit\n credentials?: RequestCredentials\n contentType?: string\n}\n\n/**\n * Subset of FetchResponse\n */\nexport type ResponseConfig<TData = unknown> = {\n data: TData\n status: number\n statusText: string\n headers: Headers\n}\n\nlet _config: Partial<RequestConfig> = {}\n\nexport const getConfig = () => _config\n\nexport const setConfig = (config: Partial<RequestConfig>) => {\n _config = config\n return getConfig()\n}\n\nexport const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {\n return configs.reduce<Partial<T>>((merged, config) => {\n return {\n ...merged,\n ...config,\n headers: {\n ...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),\n ...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),\n },\n }\n }, {})\n}\n\n/**\n * Serializes header values into the string form `fetch` expects.\n * Objects (including arrays) are JSON-stringified so spec-defined object\n * headers like `X-Filter` are sent in their canonical JSON-string form.\n */\nfunction serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {}\n const entries = Array.isArray(headers) ? headers : Object.entries(headers)\n const result: Record<string, string> = {}\n for (const [key, value] of entries) {\n if (value === undefined || value === null) continue\n result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)\n }\n return result\n}\n\nexport type ResponseErrorConfig<TError = unknown> = TError\n\nexport type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(\n config: RequestConfig<TRequestData>,\n request?: unknown,\n) => Promise<ResponseConfig<TResponseData>>\n\nexport const client = async <TResponseData, _TError = unknown, RequestData = unknown>(\n paramsConfig: RequestConfig<RequestData>,\n _request?: unknown,\n): Promise<ResponseConfig<TResponseData>> => {\n const normalizedParams = new URLSearchParams()\n\n const config = mergeConfig(getConfig(), paramsConfig)\n\n Object.entries(config.params || {}).forEach(([key, value]) => {\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : value.toString())\n }\n })\n\n let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')\n\n if (config.params) {\n targetUrl += `?${normalizedParams}`\n }\n\n const response = await globalThis.fetch(targetUrl, {\n credentials: config.credentials || 'same-origin',\n method: config.method?.toUpperCase(),\n body:
|
|
1
|
+
{"version":3,"file":"fetch.js","names":[],"sources":["../../src/clients/fetch.ts"],"sourcesContent":["/**\n * RequestCredentials\n */\nexport type RequestCredentials = 'omit' | 'same-origin' | 'include'\n\n/**\n * Header values may be objects (e.g. JSON-encoded filter headers like `X-Filter`).\n * Non-string values are JSON-serialized before the request is sent.\n */\nexport type HeaderValue = string | number | boolean | null | undefined | object\nexport type HeadersInit = Array<[string, HeaderValue]> | Record<string, HeaderValue>\n\n/**\n * Subset of FetchRequestConfig\n */\nexport type RequestConfig<TData = unknown> = {\n baseURL?: string\n url?: string\n method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'\n params?: unknown\n data?: TData | FormData\n responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'\n signal?: AbortSignal\n headers?: HeadersInit\n credentials?: RequestCredentials\n contentType?: string\n}\n\n/**\n * Subset of FetchResponse\n */\nexport type ResponseConfig<TData = unknown> = {\n data: TData\n status: number\n statusText: string\n headers: Headers\n}\n\nlet _config: Partial<RequestConfig> = {}\n\nexport const getConfig = () => _config\n\nexport const setConfig = (config: Partial<RequestConfig>) => {\n _config = config\n return getConfig()\n}\n\nexport const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {\n return configs.reduce<Partial<T>>((merged, config) => {\n return {\n ...merged,\n ...config,\n headers: {\n ...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),\n ...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),\n },\n }\n }, {})\n}\n\n/**\n * Serializes header values into the string form `fetch` expects.\n * Objects (including arrays) are JSON-stringified so spec-defined object\n * headers like `X-Filter` are sent in their canonical JSON-string form.\n */\nfunction serializeHeaders(headers: HeadersInit | undefined): Record<string, string> {\n if (!headers) return {}\n const entries = Array.isArray(headers) ? headers : Object.entries(headers)\n const result: Record<string, string> = {}\n for (const [key, value] of entries) {\n if (value === undefined || value === null) continue\n result[key] = typeof value === 'string' ? value : typeof value === 'object' ? JSON.stringify(value) : String(value)\n }\n return result\n}\n\n/**\n * Serializes the request body into the form `fetch` expects.\n * `FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer` and string bodies are passed through\n * untouched. Plain objects are encoded as `URLSearchParams` for `application/x-www-form-urlencoded`\n * and JSON-serialized otherwise.\n */\nfunction serializeBody(data: unknown, contentType?: string): BodyInit | undefined {\n if (data === undefined || data === null) return undefined\n if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {\n return data as BodyInit\n }\n if (typeof data === 'string') return data\n if (contentType?.includes('application/x-www-form-urlencoded')) {\n return new URLSearchParams(data as Record<string, string>)\n }\n return JSON.stringify(data)\n}\n\n/**\n * Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.\n */\nfunction detectResponseType(contentType: string | null): RequestConfig['responseType'] {\n if (!contentType) return undefined\n if (contentType.includes('application/json') || contentType.includes('text/json')) return 'json'\n if (contentType.includes('text/')) return 'text'\n if (contentType.includes('image/') || contentType.includes('application/octet-stream')) return 'blob'\n return undefined\n}\n\n/**\n * Parses a `fetch` response body.\n *\n * - Empty responses (204/205/304 or no body) resolve to `{}`.\n * - An explicit `responseType` (or one detected from the `Content-Type` header) forces the matching\n * `Response` method.\n * - As a last resort the body is read as text and `JSON.parse`d, falling back to the raw text.\n */\nasync function parseResponse<TData>(response: Response, responseType?: RequestConfig['responseType']): Promise<TData> {\n if ([204, 205, 304].includes(response.status) || !response.body) {\n return {} as TData\n }\n\n switch (responseType ?? detectResponseType(response.headers.get('Content-Type'))) {\n case 'text':\n case 'document':\n return (await response.text()) as TData\n case 'blob':\n return (await response.blob()) as TData\n case 'arraybuffer':\n return (await response.arrayBuffer()) as TData\n case 'stream':\n return response.body as TData\n case 'json':\n return (await response.json()) as TData\n }\n\n // Explicit but unrecognized responseType keeps the JSON default; otherwise read text and parse it.\n if (responseType) {\n return (await response.json()) as TData\n }\n const text = await response.text()\n if (!text) {\n return {} as TData\n }\n try {\n return JSON.parse(text) as TData\n } catch {\n return text as TData\n }\n}\n\nexport type ResponseErrorConfig<TError = unknown> = TError\n\nexport type Client = <TResponseData, _TError = unknown, TRequestData = unknown>(\n config: RequestConfig<TRequestData>,\n request?: unknown,\n) => Promise<ResponseConfig<TResponseData>>\n\nexport const client = async <TResponseData, _TError = unknown, RequestData = unknown>(\n paramsConfig: RequestConfig<RequestData>,\n _request?: unknown,\n): Promise<ResponseConfig<TResponseData>> => {\n const normalizedParams = new URLSearchParams()\n\n const config = mergeConfig(getConfig(), paramsConfig)\n\n Object.entries(config.params || {}).forEach(([key, value]) => {\n if (value !== undefined) {\n normalizedParams.append(key, value === null ? 'null' : value.toString())\n }\n })\n\n let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')\n\n if (config.params) {\n targetUrl += `?${normalizedParams}`\n }\n\n const headers: Record<string, string> = {\n ...(config.contentType && config.contentType !== 'multipart/form-data' ? { 'Content-Type': config.contentType } : {}),\n ...serializeHeaders(config.headers),\n }\n\n const response = await globalThis.fetch(targetUrl, {\n credentials: config.credentials || 'same-origin',\n method: config.method?.toUpperCase(),\n body: serializeBody(config.data, headers['Content-Type'] ?? headers['content-type']),\n signal: config.signal,\n headers,\n })\n\n const data = await parseResponse<TResponseData>(response, config.responseType)\n\n return {\n data,\n status: response.status,\n statusText: response.statusText,\n headers: response.headers as Headers,\n }\n}\n\nclient.getConfig = getConfig\nclient.setConfig = setConfig\n\nexport default client\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -360,11 +360,30 @@ var URLPath = class {
|
|
|
360
360
|
};
|
|
361
361
|
//#endregion
|
|
362
362
|
//#region ../../internals/shared/src/operation.ts
|
|
363
|
+
/**
|
|
364
|
+
* Builds the `ResolverFileParams` every operation generator passes to
|
|
365
|
+
* `resolver.resolveFile`: a file named `name`, tagged by the operation's first
|
|
366
|
+
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
367
|
+
* that was repeated at dozens of call sites across the client and query plugins.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
function operationFileEntry(node, name, extname = ".ts") {
|
|
375
|
+
return {
|
|
376
|
+
name,
|
|
377
|
+
extname,
|
|
378
|
+
tag: node.tags[0] ?? "default",
|
|
379
|
+
path: node.path
|
|
380
|
+
};
|
|
381
|
+
}
|
|
363
382
|
function getOperationLink(node, link) {
|
|
364
383
|
if (!link) return null;
|
|
365
384
|
if (typeof link === "function") return link(node) ?? null;
|
|
366
385
|
if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
|
|
367
|
-
return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}
|
|
386
|
+
return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
|
|
368
387
|
}
|
|
369
388
|
function getContentTypeInfo(node) {
|
|
370
389
|
const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
|
|
@@ -377,6 +396,22 @@ function getContentTypeInfo(node) {
|
|
|
377
396
|
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
378
397
|
};
|
|
379
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Derives the default `responseType` for an operation from its primary success response.
|
|
401
|
+
*
|
|
402
|
+
* Returns a value only when that response declares a single non-JSON content type — a binary type
|
|
403
|
+
* (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*`) maps to `'blob'`
|
|
404
|
+
* and other `text/*` maps to `'text'`. Otherwise `undefined`, leaving the runtime client's
|
|
405
|
+
* `Content-Type` auto-detection in charge.
|
|
406
|
+
*/
|
|
407
|
+
function getResponseType(node) {
|
|
408
|
+
const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
|
|
409
|
+
if (contentTypes.length !== 1) return void 0;
|
|
410
|
+
const baseType = contentTypes[0].split(";")[0].trim().toLowerCase();
|
|
411
|
+
if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
|
|
412
|
+
if (baseType.startsWith("text/")) return "text";
|
|
413
|
+
if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
|
|
414
|
+
}
|
|
380
415
|
function buildRequestConfigType(node, resolver) {
|
|
381
416
|
const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
|
|
382
417
|
const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
|
|
@@ -420,6 +455,15 @@ function isErrorStatusCode(statusCode) {
|
|
|
420
455
|
const code = getStatusCodeNumber(statusCode);
|
|
421
456
|
return code !== null && code >= 400;
|
|
422
457
|
}
|
|
458
|
+
function getSuccessResponses(responses) {
|
|
459
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
460
|
+
}
|
|
461
|
+
function getOperationSuccessResponses(node) {
|
|
462
|
+
return getSuccessResponses(node.responses);
|
|
463
|
+
}
|
|
464
|
+
function getPrimarySuccessResponse(node) {
|
|
465
|
+
return getOperationSuccessResponses(node)[0] ?? null;
|
|
466
|
+
}
|
|
423
467
|
function resolveErrorNames(node, resolver) {
|
|
424
468
|
return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
425
469
|
}
|
|
@@ -462,6 +506,39 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
|
462
506
|
return result;
|
|
463
507
|
}
|
|
464
508
|
//#endregion
|
|
509
|
+
//#region ../../internals/shared/src/group.ts
|
|
510
|
+
/**
|
|
511
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
512
|
+
* shared default naming so every plugin groups output consistently:
|
|
513
|
+
*
|
|
514
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
515
|
+
* - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
|
|
516
|
+
*
|
|
517
|
+
* Returns `null` when grouping is disabled, matching the per-plugin convention.
|
|
518
|
+
*
|
|
519
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
520
|
+
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
521
|
+
* @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
|
|
522
|
+
*
|
|
523
|
+
* @example
|
|
524
|
+
* ```ts
|
|
525
|
+
* createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
|
|
526
|
+
* createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
|
|
527
|
+
* createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
function createGroupConfig(group, options) {
|
|
531
|
+
if (!group) return null;
|
|
532
|
+
const defaultName = (ctx) => {
|
|
533
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
534
|
+
return `${camelCase(ctx.group)}${options.suffix}`;
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
...group,
|
|
538
|
+
name: options.honorName && group.name ? group.name : defaultName
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
//#endregion
|
|
465
542
|
//#region ../../internals/shared/src/params.ts
|
|
466
543
|
function buildParamsMapping(originalParams, mappedParams) {
|
|
467
544
|
const mapping = {};
|
|
@@ -553,6 +630,7 @@ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, ts
|
|
|
553
630
|
});
|
|
554
631
|
}
|
|
555
632
|
function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
|
|
633
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
556
634
|
const path = new URLPath(node.path);
|
|
557
635
|
const paramsNode = buildUrlParamsNode({
|
|
558
636
|
paramsType,
|
|
@@ -606,9 +684,11 @@ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node,
|
|
|
606
684
|
});
|
|
607
685
|
}
|
|
608
686
|
function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
|
|
687
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
609
688
|
const path = new URLPath(node.path);
|
|
610
689
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
611
690
|
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
691
|
+
const responseType = getResponseType(node);
|
|
612
692
|
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
|
|
613
693
|
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
|
|
614
694
|
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
|
|
@@ -656,6 +736,7 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
656
736
|
params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
|
|
657
737
|
data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
|
|
658
738
|
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
739
|
+
responseType: responseType ? { value: JSON.stringify(responseType) } : null,
|
|
659
740
|
requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
|
|
660
741
|
headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
|
|
661
742
|
}
|
|
@@ -663,8 +744,8 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
663
744
|
const childrenElement = children ? children : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
|
|
664
745
|
dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
|
|
665
746
|
dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
|
|
666
|
-
dataReturnType === "full" && parser
|
|
667
|
-
dataReturnType === "data" && parser
|
|
747
|
+
dataReturnType === "full" && parser !== "zod" && "return res",
|
|
748
|
+
dataReturnType === "data" && parser !== "zod" && "return res.data"
|
|
668
749
|
] });
|
|
669
750
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
670
751
|
name,
|
|
@@ -740,16 +821,18 @@ function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, i
|
|
|
740
821
|
const { query: queryParams } = getOperationParameters(node);
|
|
741
822
|
const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
|
|
742
823
|
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
|
|
824
|
+
const responseType = getResponseType(node);
|
|
743
825
|
return createFunctionParams({ config: {
|
|
744
826
|
mode: "object",
|
|
745
827
|
children: {
|
|
746
828
|
requestConfig: { mode: "inlineSpread" },
|
|
747
|
-
method: { value: JSON.stringify(node.method.toUpperCase()) },
|
|
829
|
+
method: { value: JSON.stringify(_kubb_core.ast.isHttpOperationNode(node) ? node.method.toUpperCase() : "") },
|
|
748
830
|
url: { value: path.template },
|
|
749
831
|
baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
|
|
750
832
|
params: queryParamsName ? {} : null,
|
|
751
833
|
data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
|
|
752
834
|
contentType: isMultipleContentTypes ? {} : null,
|
|
835
|
+
responseType: responseType ? { value: JSON.stringify(responseType) } : null,
|
|
753
836
|
headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
|
|
754
837
|
}
|
|
755
838
|
} });
|
|
@@ -779,13 +862,14 @@ function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
|
|
|
779
862
|
const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
|
|
780
863
|
if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
|
|
781
864
|
if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
|
|
782
|
-
if (dataReturnType === "full" && parser
|
|
865
|
+
if (dataReturnType === "full" && parser !== "zod") return "return res";
|
|
783
866
|
return "return res.data";
|
|
784
867
|
}
|
|
785
868
|
//#endregion
|
|
786
869
|
//#region src/components/ClassClient.tsx
|
|
787
870
|
const declarationPrinter$1 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
|
|
788
871
|
function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
|
|
872
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
|
|
789
873
|
const path = new URLPath(node.path, { casing: paramsCasing });
|
|
790
874
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
791
875
|
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
@@ -913,22 +997,12 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
913
997
|
const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
914
998
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
915
999
|
function buildOperationData(node) {
|
|
916
|
-
const typeFile = tsResolver.resolveFile({
|
|
917
|
-
name: node.operationId,
|
|
918
|
-
extname: ".ts",
|
|
919
|
-
tag: node.tags[0] ?? "default",
|
|
920
|
-
path: node.path
|
|
921
|
-
}, {
|
|
1000
|
+
const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
922
1001
|
root,
|
|
923
1002
|
output: tsPluginOptions?.output ?? output,
|
|
924
1003
|
group: tsPluginOptions?.group
|
|
925
1004
|
});
|
|
926
|
-
const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
|
|
927
|
-
name: node.operationId,
|
|
928
|
-
extname: ".ts",
|
|
929
|
-
tag: node.tags[0] ?? "default",
|
|
930
|
-
path: node.path
|
|
931
|
-
}, {
|
|
1005
|
+
const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
932
1006
|
root,
|
|
933
1007
|
output: pluginZod.options?.output ?? output,
|
|
934
1008
|
group: pluginZod.options?.group ?? void 0
|
|
@@ -943,6 +1017,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
943
1017
|
};
|
|
944
1018
|
}
|
|
945
1019
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
1020
|
+
if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
|
|
946
1021
|
const tag = operationNode.tags[0];
|
|
947
1022
|
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
|
|
948
1023
|
if (!tag && !group) {
|
|
@@ -1202,6 +1277,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1202
1277
|
name: "client",
|
|
1203
1278
|
renderer: _kubb_renderer_jsx.jsxRendererSync,
|
|
1204
1279
|
operation(node, ctx) {
|
|
1280
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
1205
1281
|
const { config, driver, resolver, root } = ctx;
|
|
1206
1282
|
const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
|
|
1207
1283
|
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
|
|
@@ -1215,32 +1291,17 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1215
1291
|
const meta = {
|
|
1216
1292
|
name: resolver.resolveName(node.operationId),
|
|
1217
1293
|
urlName: resolver.resolveUrlName(node),
|
|
1218
|
-
file: resolver.resolveFile({
|
|
1219
|
-
name: node.operationId,
|
|
1220
|
-
extname: ".ts",
|
|
1221
|
-
tag: node.tags[0] ?? "default",
|
|
1222
|
-
path: node.path
|
|
1223
|
-
}, {
|
|
1294
|
+
file: resolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
1224
1295
|
root,
|
|
1225
1296
|
output,
|
|
1226
1297
|
group: group ?? void 0
|
|
1227
1298
|
}),
|
|
1228
|
-
fileTs: tsResolver.resolveFile({
|
|
1229
|
-
name: node.operationId,
|
|
1230
|
-
extname: ".ts",
|
|
1231
|
-
tag: node.tags[0] ?? "default",
|
|
1232
|
-
path: node.path
|
|
1233
|
-
}, {
|
|
1299
|
+
fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
1234
1300
|
root,
|
|
1235
1301
|
output: pluginTs.options?.output ?? output,
|
|
1236
1302
|
group: pluginTs.options?.group ?? void 0
|
|
1237
1303
|
}),
|
|
1238
|
-
fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
|
|
1239
|
-
name: node.operationId,
|
|
1240
|
-
extname: ".ts",
|
|
1241
|
-
tag: node.tags[0] ?? "default",
|
|
1242
|
-
path: node.path
|
|
1243
|
-
}, {
|
|
1304
|
+
fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
1244
1305
|
root,
|
|
1245
1306
|
output: pluginZod.options.output ?? output,
|
|
1246
1307
|
group: pluginZod.options?.group ?? void 0
|
|
@@ -1434,6 +1495,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1434
1495
|
function Operations({ name, nodes }) {
|
|
1435
1496
|
const operationsObject = {};
|
|
1436
1497
|
nodes.forEach((node) => {
|
|
1498
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return;
|
|
1437
1499
|
operationsObject[node.operationId] = {
|
|
1438
1500
|
path: new URLPath(node.path).URL,
|
|
1439
1501
|
method: node.method.toLowerCase()
|
|
@@ -1495,7 +1557,7 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1495
1557
|
}),
|
|
1496
1558
|
children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Operations, {
|
|
1497
1559
|
name,
|
|
1498
|
-
nodes
|
|
1560
|
+
nodes: nodes.filter(_kubb_core.ast.isHttpOperationNode)
|
|
1499
1561
|
})
|
|
1500
1562
|
});
|
|
1501
1563
|
}
|
|
@@ -1504,6 +1566,7 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1504
1566
|
//#region src/components/StaticClassClient.tsx
|
|
1505
1567
|
const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
|
|
1506
1568
|
function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
|
|
1569
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
|
|
1507
1570
|
const path = new URLPath(node.path, { casing: paramsCasing });
|
|
1508
1571
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
1509
1572
|
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
@@ -1604,22 +1667,12 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1604
1667
|
const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
1605
1668
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
1606
1669
|
function buildOperationData(node) {
|
|
1607
|
-
const typeFile = tsResolver.resolveFile({
|
|
1608
|
-
name: node.operationId,
|
|
1609
|
-
extname: ".ts",
|
|
1610
|
-
tag: node.tags[0] ?? "default",
|
|
1611
|
-
path: node.path
|
|
1612
|
-
}, {
|
|
1670
|
+
const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
1613
1671
|
root,
|
|
1614
1672
|
output: tsPluginOptions?.output ?? output,
|
|
1615
1673
|
group: tsPluginOptions?.group
|
|
1616
1674
|
});
|
|
1617
|
-
const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
|
|
1618
|
-
name: node.operationId,
|
|
1619
|
-
extname: ".ts",
|
|
1620
|
-
tag: node.tags[0] ?? "default",
|
|
1621
|
-
path: node.path
|
|
1622
|
-
}, {
|
|
1675
|
+
const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
1623
1676
|
root,
|
|
1624
1677
|
output: pluginZod.options?.output ?? output,
|
|
1625
1678
|
group: pluginZod.options?.group ?? void 0
|
|
@@ -1634,6 +1687,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1634
1687
|
};
|
|
1635
1688
|
}
|
|
1636
1689
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
1690
|
+
if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
|
|
1637
1691
|
const tag = operationNode.tags[0];
|
|
1638
1692
|
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
|
|
1639
1693
|
if (!tag && !group) {
|
|
@@ -1903,20 +1957,17 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1903
1957
|
const { output = {
|
|
1904
1958
|
path: "clients",
|
|
1905
1959
|
barrelType: "named"
|
|
1906
|
-
}, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser =
|
|
1960
|
+
}, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = false, client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, transformer: userTransformer } = options;
|
|
1907
1961
|
const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
|
|
1908
1962
|
const selectedGenerators = options.generators ?? [
|
|
1909
1963
|
clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
|
|
1910
1964
|
group && clientType === "function" ? groupedClientGenerator : null,
|
|
1911
1965
|
operations ? operationsGenerator : null
|
|
1912
1966
|
].filter((x) => Boolean(x));
|
|
1913
|
-
const groupConfig = group
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
1918
|
-
}
|
|
1919
|
-
} : null;
|
|
1967
|
+
const groupConfig = createGroupConfig(group, {
|
|
1968
|
+
suffix: "Controller",
|
|
1969
|
+
honorName: true
|
|
1970
|
+
});
|
|
1920
1971
|
return {
|
|
1921
1972
|
name: pluginClientName,
|
|
1922
1973
|
options,
|