@ps-aux/api-client-axios 0.7.0-rc.1 → 0.7.0-rc.3

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 CHANGED
@@ -3,6 +3,7 @@
3
3
  Axios-based HTTP client for OpenAPI-generated APIs with Node and browser builds.
4
4
 
5
5
  ## Installation
6
+
6
7
  ```bash
7
8
  npm install @ps-aux/api-client-axios axios
8
9
  ```
@@ -13,34 +14,37 @@ npm install @ps-aux/api-client-axios axios
13
14
  import axios from 'axios'
14
15
  import { createHttpClient } from '@ps-aux/api-client-axios'
15
16
 
16
- const http = createHttpClient(axios.create({ baseURL: 'https://api.example.com' }), {
17
- querySerializer: null
18
- })
17
+ const http = createHttpClient(
18
+ axios.create({ baseURL: 'https://api.example.com' }),
19
+ {
20
+ querySerializer: null
21
+ }
22
+ )
19
23
 
20
24
  const users = await http.request({
21
- path: '/users',
22
- method: 'GET'
25
+ path: '/users',
26
+ method: 'GET'
23
27
  })
24
28
  ```
25
29
 
26
30
  ## Custom query serialization
27
31
 
28
32
  Use the `QuerySerializer` type to control how query params are serialized. Pass a serializer into
29
- `createHttpClient` and return a serialized `queryString`.
33
+ `createHttpClient`; the serializer returns a serialized query string.
30
34
 
31
35
  ```ts
32
36
  import axios from 'axios'
33
37
  import {
34
- createHttpClient,
35
- type QuerySerializer
38
+ createHttpClient,
39
+ type QuerySerializer
36
40
  } from '@ps-aux/api-client-axios'
37
41
  import { qsQuerySerializer } from '@ps-aux/api-client-query-serializer-qs'
38
42
 
39
43
  const querySerializer: QuerySerializer = qsQuerySerializer()
40
44
 
41
45
  const http = createHttpClient(
42
- axios.create({ baseURL: 'https://api.example.com' }),
43
- { querySerializer }
46
+ axios.create({ baseURL: 'https://api.example.com' }),
47
+ { querySerializer }
44
48
  )
45
49
  ```
46
50
 
@@ -49,7 +53,9 @@ Query serializers live in their own packages (for example
49
53
  the one that matches your backend conventions.
50
54
 
51
55
  ## Notes
56
+
52
57
  - Bundlers will pick the browser build via the package browser field.
53
58
 
54
59
  ## License
60
+
55
61
  MIT
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ const fileResFilenameRegex = /filename="([^"]+)"/;
4
+ const getFileNameFromContentDispositionHeader = (contentDispositionHeader) => {
5
+ if (!contentDispositionHeader) return "download-file";
6
+ const match = contentDispositionHeader.match(fileResFilenameRegex);
7
+ const fileName = match ? match[1] : "downloaded-file";
8
+ return fileName;
9
+ };
10
+
11
+ const convertToFormData = (payload) => {
12
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
13
+ throw new Error("Multipart payload must be an object");
14
+ }
15
+ const formData = new FormData();
16
+ const source = payload;
17
+ const addProp = (key, value) => {
18
+ if (value === void 0) {
19
+ return;
20
+ }
21
+ if (Array.isArray(value)) {
22
+ value.forEach((item) => addProp(key, item));
23
+ return;
24
+ }
25
+ if (isFileList(value)) {
26
+ for (let i = 0; i < value.length; i += 1) {
27
+ addProp(key, value.item(i));
28
+ }
29
+ return;
30
+ }
31
+ if (isBlob(value)) {
32
+ formData.append(key, value);
33
+ return;
34
+ }
35
+ if (isNodeFileDescriptor(value)) {
36
+ const file = fileDescriptorToBlob(value);
37
+ formData.append(key, file, value.name);
38
+ return;
39
+ }
40
+ if (typeof value === "object" && value != null) {
41
+ throw new Error(
42
+ `Object serialization into FormData not supported: ${safeStringify(value)}`
43
+ );
44
+ }
45
+ formData.append(key, String(value));
46
+ };
47
+ Object.entries(source).forEach(([key, value]) => addProp(key, value));
48
+ return formData;
49
+ };
50
+ const withoutContentTypeHeader = (headers) => {
51
+ return Object.fromEntries(
52
+ Object.entries(headers).filter(
53
+ ([key]) => key.toLowerCase() !== "content-type"
54
+ )
55
+ );
56
+ };
57
+ const isBlob = (value) => {
58
+ return typeof Blob !== "undefined" && value instanceof Blob;
59
+ };
60
+ const isFileList = (value) => {
61
+ return typeof FileList !== "undefined" && value instanceof FileList;
62
+ };
63
+ const isNodeFileDescriptor = (value) => {
64
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
65
+ return false;
66
+ }
67
+ const typed = value;
68
+ return typeof typed.name === "string" && "file" in typed;
69
+ };
70
+ const fileDescriptorToBlob = (descriptor) => {
71
+ const { file, type } = descriptor;
72
+ if (isBlob(file)) {
73
+ if (type && type !== file.type) {
74
+ return new Blob([file], { type });
75
+ }
76
+ return file;
77
+ }
78
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
79
+ const bytes = new Uint8Array(file.byteLength);
80
+ bytes.set(file);
81
+ return new Blob([bytes.buffer], { type });
82
+ }
83
+ if (file instanceof ArrayBuffer) {
84
+ return new Blob([file], { type });
85
+ }
86
+ if (ArrayBuffer.isView(file)) {
87
+ const bytes = new Uint8Array(file.byteLength);
88
+ bytes.set(new Uint8Array(file.buffer, file.byteOffset, file.byteLength));
89
+ return new Blob([bytes.buffer], { type });
90
+ }
91
+ throw new Error(
92
+ "Object serialization into FormData not supported: Readable stream file descriptors are not supported"
93
+ );
94
+ };
95
+ const safeStringify = (value) => {
96
+ try {
97
+ return JSON.stringify(value);
98
+ } catch {
99
+ return String(value);
100
+ }
101
+ };
102
+
103
+ class HttpClientHelper {
104
+ validateRequest(req) {
105
+ this.assertRelativePathWithoutQuery(req.path);
106
+ }
107
+ assertRelativePathWithoutQuery(path) {
108
+ if (!path.startsWith("/")) {
109
+ throw new Error('Request path must start with "/".');
110
+ }
111
+ if (path.includes("?")) {
112
+ throw new Error(
113
+ "Request path must not contain a query string. Use req.query instead."
114
+ );
115
+ }
116
+ }
117
+ withQueryString(path, query, querySerializer) {
118
+ if (!query) {
119
+ return path;
120
+ }
121
+ const queryString = this.serializeQuery(query, querySerializer);
122
+ if (!queryString) {
123
+ return path;
124
+ }
125
+ return `${path}?${queryString}`;
126
+ }
127
+ serializeQuery(query, querySerializer) {
128
+ if (querySerializer) {
129
+ return querySerializer(query);
130
+ }
131
+ return this.toDefaultQueryString(query);
132
+ }
133
+ resolveUrl(path, baseUrl) {
134
+ if (!baseUrl) {
135
+ return path;
136
+ }
137
+ return new URL(path, baseUrl).toString();
138
+ }
139
+ convertToFormData(payload) {
140
+ return convertToFormData(payload);
141
+ }
142
+ withoutContentTypeHeader(headers) {
143
+ return withoutContentTypeHeader(headers);
144
+ }
145
+ getFileNameFromContentDispositionHeader(contentDispositionHeader) {
146
+ return getFileNameFromContentDispositionHeader(contentDispositionHeader);
147
+ }
148
+ toDefaultQueryString(query) {
149
+ const params = new URLSearchParams();
150
+ const addValue = (key, value) => {
151
+ if (value === void 0) return;
152
+ if (Array.isArray(value)) {
153
+ value.forEach((item) => addValue(key, item));
154
+ return;
155
+ }
156
+ params.append(key, String(value));
157
+ };
158
+ Object.entries(query).forEach(([key, value]) => addValue(key, value));
159
+ return params.toString();
160
+ }
161
+ }
162
+
163
+ const createHttpClient$1 = (axios, opts) => {
164
+ const { querySerializer } = opts;
165
+ const helper = new HttpClientHelper();
166
+ return {
167
+ request: (req) => {
168
+ helper.validateRequest(req);
169
+ const { query, requestContentType, body, headers: reqHeaders } = req;
170
+ const paramsSerializer = querySerializer ? (r) => helper.serializeQuery(r, querySerializer) : void 0;
171
+ const isBlobResponse = req.format === "document";
172
+ const headers = { ...reqHeaders || {} };
173
+ let data = body;
174
+ if (requestContentType === "multipart/form-data") {
175
+ data = helper.convertToFormData(body);
176
+ } else if (requestContentType) {
177
+ headers["Content-Type"] = requestContentType;
178
+ }
179
+ return axios.request({
180
+ method: req.method,
181
+ url: req.path,
182
+ params: query,
183
+ paramsSerializer,
184
+ data,
185
+ headers,
186
+ responseType: isBlobResponse ? "arraybuffer" : "json"
187
+ }).then((r) => {
188
+ const headers2 = normalizeHeaders(
189
+ r.headers
190
+ );
191
+ const response = {
192
+ body: r.data,
193
+ headers: headers2,
194
+ status: r.status
195
+ };
196
+ if (isBlobResponse) {
197
+ const contDist = getHeaderValue(
198
+ headers2["content-disposition"]
199
+ );
200
+ const fileName = helper.getFileNameFromContentDispositionHeader(
201
+ contDist
202
+ );
203
+ const type = getHeaderValue(headers2["content-type"]);
204
+ return {
205
+ ...response,
206
+ body: new File([r.data], fileName, {
207
+ type
208
+ })
209
+ };
210
+ }
211
+ return response;
212
+ });
213
+ }
214
+ };
215
+ };
216
+ const normalizeHeaders = (headers) => {
217
+ const normalized = {};
218
+ for (const [key, value] of Object.entries(headers)) {
219
+ if (value == null) {
220
+ continue;
221
+ }
222
+ normalized[key.toLowerCase()] = Array.isArray(value) ? value.map(String) : String(value);
223
+ }
224
+ return normalized;
225
+ };
226
+ const getHeaderValue = (header) => {
227
+ if (Array.isArray(header)) {
228
+ return header[0];
229
+ }
230
+ return header;
231
+ };
232
+
233
+ const createHttpClient = (axios, opts) => {
234
+ return createHttpClient$1(axios, {
235
+ querySerializer: opts.querySerializer
236
+ });
237
+ };
238
+
239
+ exports.createHttpClient = createHttpClient;
@@ -0,0 +1,44 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
4
+ type RequestContentType = KnownRequestContentType | string;
5
+ type QueryValue = string | number | boolean | null | undefined | QueryValue[] | Record<string, any> | unknown;
6
+ type QueryParams = Record<string, QueryValue>;
7
+ type Request = {
8
+ path: string;
9
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
10
+ format?: 'json' | 'document';
11
+ headers?: Record<string, string>;
12
+ query?: QueryParams;
13
+ body?: any;
14
+ requestContentType?: RequestContentType;
15
+ };
16
+ type HttpResponse<Data> = {
17
+ body: Data;
18
+ headers: Record<string, string | string[]>;
19
+ status: number;
20
+ };
21
+ type QuerySerializer = (params: QueryParams) => string;
22
+
23
+ type HttpClient<RequestParams = never> = {
24
+ request: <Data>(req: Request, params?: RequestParams) => Promise<HttpResponse<Data>>;
25
+ };
26
+
27
+ /// <reference lib="esnext.asynciterable" />
28
+
29
+ /**
30
+ * Note: This will add Symbol.observable globally for all TypeScript users,
31
+ * however, we are no longer polyfilling Symbol.observable
32
+ */
33
+ declare global {
34
+ interface SymbolConstructor {
35
+ readonly observable: symbol;
36
+ }
37
+ }
38
+
39
+ declare const createHttpClient: (axios: AxiosInstance, opts: {
40
+ querySerializer: QuerySerializer | null;
41
+ }) => HttpClient;
42
+
43
+ export { createHttpClient };
44
+ export type { HttpResponse, HttpClient as PromiseHttpClient, QuerySerializer };
@@ -0,0 +1,44 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
4
+ type RequestContentType = KnownRequestContentType | string;
5
+ type QueryValue = string | number | boolean | null | undefined | QueryValue[] | Record<string, any> | unknown;
6
+ type QueryParams = Record<string, QueryValue>;
7
+ type Request = {
8
+ path: string;
9
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
10
+ format?: 'json' | 'document';
11
+ headers?: Record<string, string>;
12
+ query?: QueryParams;
13
+ body?: any;
14
+ requestContentType?: RequestContentType;
15
+ };
16
+ type HttpResponse<Data> = {
17
+ body: Data;
18
+ headers: Record<string, string | string[]>;
19
+ status: number;
20
+ };
21
+ type QuerySerializer = (params: QueryParams) => string;
22
+
23
+ type HttpClient<RequestParams = never> = {
24
+ request: <Data>(req: Request, params?: RequestParams) => Promise<HttpResponse<Data>>;
25
+ };
26
+
27
+ /// <reference lib="esnext.asynciterable" />
28
+
29
+ /**
30
+ * Note: This will add Symbol.observable globally for all TypeScript users,
31
+ * however, we are no longer polyfilling Symbol.observable
32
+ */
33
+ declare global {
34
+ interface SymbolConstructor {
35
+ readonly observable: symbol;
36
+ }
37
+ }
38
+
39
+ declare const createHttpClient: (axios: AxiosInstance, opts: {
40
+ querySerializer: QuerySerializer | null;
41
+ }) => HttpClient;
42
+
43
+ export { createHttpClient };
44
+ export type { HttpResponse, HttpClient as PromiseHttpClient, QuerySerializer };
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,29 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
 
3
+ type KnownRequestContentType = 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded';
4
+ type RequestContentType = KnownRequestContentType | string;
5
+ type QueryValue = string | number | boolean | null | undefined | QueryValue[] | Record<string, any> | unknown;
6
+ type QueryParams = Record<string, QueryValue>;
7
+ type Request = {
8
+ path: string;
9
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
10
+ format?: 'json' | 'document';
11
+ headers?: Record<string, string>;
12
+ query?: QueryParams;
13
+ body?: any;
14
+ requestContentType?: RequestContentType;
15
+ };
16
+ type HttpResponse<Data> = {
17
+ body: Data;
18
+ headers: Record<string, string | string[]>;
19
+ status: number;
20
+ };
21
+ type QuerySerializer = (params: QueryParams) => string;
22
+
23
+ type HttpClient<RequestParams = never> = {
24
+ request: <Data>(req: Request, params?: RequestParams) => Promise<HttpResponse<Data>>;
25
+ };
26
+
3
27
  /// <reference lib="esnext.asynciterable" />
4
28
 
5
29
  /**
@@ -12,28 +36,9 @@ declare global {
12
36
  }
13
37
  }
14
38
 
15
- type RequestContentType = 'application/json' | 'multipart/form-data';
16
- type Request = {
17
- path: string;
18
- method: 'GET' | 'POST' | 'PUT' | 'DELETE';
19
- format?: 'json' | 'document';
20
- headers?: Record<string, string>;
21
- query?: Record<string, unknown>;
22
- body?: any;
23
- requestContentType?: RequestContentType;
24
- };
25
- type QuerySerializer = (req: {
26
- queryParams: Record<string, unknown>;
27
- }) => {
28
- queryString: string;
29
- };
30
- type PromiseHttpClient<RequestParams = never> = {
31
- request: <Data>(req: Request, params?: RequestParams) => Promise<Data>;
32
- };
33
-
34
39
  declare const createHttpClient: (axios: AxiosInstance, opts: {
35
40
  querySerializer: QuerySerializer | null;
36
- }) => PromiseHttpClient<never>;
41
+ }) => HttpClient;
37
42
 
38
43
  export { createHttpClient };
39
- export type { PromiseHttpClient, QuerySerializer };
44
+ export type { HttpResponse, HttpClient as PromiseHttpClient, QuerySerializer };
@@ -0,0 +1,237 @@
1
+ const fileResFilenameRegex = /filename="([^"]+)"/;
2
+ const getFileNameFromContentDispositionHeader = (contentDispositionHeader) => {
3
+ if (!contentDispositionHeader) return "download-file";
4
+ const match = contentDispositionHeader.match(fileResFilenameRegex);
5
+ const fileName = match ? match[1] : "downloaded-file";
6
+ return fileName;
7
+ };
8
+
9
+ const convertToFormData = (payload) => {
10
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
11
+ throw new Error("Multipart payload must be an object");
12
+ }
13
+ const formData = new FormData();
14
+ const source = payload;
15
+ const addProp = (key, value) => {
16
+ if (value === void 0) {
17
+ return;
18
+ }
19
+ if (Array.isArray(value)) {
20
+ value.forEach((item) => addProp(key, item));
21
+ return;
22
+ }
23
+ if (isFileList(value)) {
24
+ for (let i = 0; i < value.length; i += 1) {
25
+ addProp(key, value.item(i));
26
+ }
27
+ return;
28
+ }
29
+ if (isBlob(value)) {
30
+ formData.append(key, value);
31
+ return;
32
+ }
33
+ if (isNodeFileDescriptor(value)) {
34
+ const file = fileDescriptorToBlob(value);
35
+ formData.append(key, file, value.name);
36
+ return;
37
+ }
38
+ if (typeof value === "object" && value != null) {
39
+ throw new Error(
40
+ `Object serialization into FormData not supported: ${safeStringify(value)}`
41
+ );
42
+ }
43
+ formData.append(key, String(value));
44
+ };
45
+ Object.entries(source).forEach(([key, value]) => addProp(key, value));
46
+ return formData;
47
+ };
48
+ const withoutContentTypeHeader = (headers) => {
49
+ return Object.fromEntries(
50
+ Object.entries(headers).filter(
51
+ ([key]) => key.toLowerCase() !== "content-type"
52
+ )
53
+ );
54
+ };
55
+ const isBlob = (value) => {
56
+ return typeof Blob !== "undefined" && value instanceof Blob;
57
+ };
58
+ const isFileList = (value) => {
59
+ return typeof FileList !== "undefined" && value instanceof FileList;
60
+ };
61
+ const isNodeFileDescriptor = (value) => {
62
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
63
+ return false;
64
+ }
65
+ const typed = value;
66
+ return typeof typed.name === "string" && "file" in typed;
67
+ };
68
+ const fileDescriptorToBlob = (descriptor) => {
69
+ const { file, type } = descriptor;
70
+ if (isBlob(file)) {
71
+ if (type && type !== file.type) {
72
+ return new Blob([file], { type });
73
+ }
74
+ return file;
75
+ }
76
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
77
+ const bytes = new Uint8Array(file.byteLength);
78
+ bytes.set(file);
79
+ return new Blob([bytes.buffer], { type });
80
+ }
81
+ if (file instanceof ArrayBuffer) {
82
+ return new Blob([file], { type });
83
+ }
84
+ if (ArrayBuffer.isView(file)) {
85
+ const bytes = new Uint8Array(file.byteLength);
86
+ bytes.set(new Uint8Array(file.buffer, file.byteOffset, file.byteLength));
87
+ return new Blob([bytes.buffer], { type });
88
+ }
89
+ throw new Error(
90
+ "Object serialization into FormData not supported: Readable stream file descriptors are not supported"
91
+ );
92
+ };
93
+ const safeStringify = (value) => {
94
+ try {
95
+ return JSON.stringify(value);
96
+ } catch {
97
+ return String(value);
98
+ }
99
+ };
100
+
101
+ class HttpClientHelper {
102
+ validateRequest(req) {
103
+ this.assertRelativePathWithoutQuery(req.path);
104
+ }
105
+ assertRelativePathWithoutQuery(path) {
106
+ if (!path.startsWith("/")) {
107
+ throw new Error('Request path must start with "/".');
108
+ }
109
+ if (path.includes("?")) {
110
+ throw new Error(
111
+ "Request path must not contain a query string. Use req.query instead."
112
+ );
113
+ }
114
+ }
115
+ withQueryString(path, query, querySerializer) {
116
+ if (!query) {
117
+ return path;
118
+ }
119
+ const queryString = this.serializeQuery(query, querySerializer);
120
+ if (!queryString) {
121
+ return path;
122
+ }
123
+ return `${path}?${queryString}`;
124
+ }
125
+ serializeQuery(query, querySerializer) {
126
+ if (querySerializer) {
127
+ return querySerializer(query);
128
+ }
129
+ return this.toDefaultQueryString(query);
130
+ }
131
+ resolveUrl(path, baseUrl) {
132
+ if (!baseUrl) {
133
+ return path;
134
+ }
135
+ return new URL(path, baseUrl).toString();
136
+ }
137
+ convertToFormData(payload) {
138
+ return convertToFormData(payload);
139
+ }
140
+ withoutContentTypeHeader(headers) {
141
+ return withoutContentTypeHeader(headers);
142
+ }
143
+ getFileNameFromContentDispositionHeader(contentDispositionHeader) {
144
+ return getFileNameFromContentDispositionHeader(contentDispositionHeader);
145
+ }
146
+ toDefaultQueryString(query) {
147
+ const params = new URLSearchParams();
148
+ const addValue = (key, value) => {
149
+ if (value === void 0) return;
150
+ if (Array.isArray(value)) {
151
+ value.forEach((item) => addValue(key, item));
152
+ return;
153
+ }
154
+ params.append(key, String(value));
155
+ };
156
+ Object.entries(query).forEach(([key, value]) => addValue(key, value));
157
+ return params.toString();
158
+ }
159
+ }
160
+
161
+ const createHttpClient$1 = (axios, opts) => {
162
+ const { querySerializer } = opts;
163
+ const helper = new HttpClientHelper();
164
+ return {
165
+ request: (req) => {
166
+ helper.validateRequest(req);
167
+ const { query, requestContentType, body, headers: reqHeaders } = req;
168
+ const paramsSerializer = querySerializer ? (r) => helper.serializeQuery(r, querySerializer) : void 0;
169
+ const isBlobResponse = req.format === "document";
170
+ const headers = { ...reqHeaders || {} };
171
+ let data = body;
172
+ if (requestContentType === "multipart/form-data") {
173
+ data = helper.convertToFormData(body);
174
+ } else if (requestContentType) {
175
+ headers["Content-Type"] = requestContentType;
176
+ }
177
+ return axios.request({
178
+ method: req.method,
179
+ url: req.path,
180
+ params: query,
181
+ paramsSerializer,
182
+ data,
183
+ headers,
184
+ responseType: isBlobResponse ? "arraybuffer" : "json"
185
+ }).then((r) => {
186
+ const headers2 = normalizeHeaders(
187
+ r.headers
188
+ );
189
+ const response = {
190
+ body: r.data,
191
+ headers: headers2,
192
+ status: r.status
193
+ };
194
+ if (isBlobResponse) {
195
+ const contDist = getHeaderValue(
196
+ headers2["content-disposition"]
197
+ );
198
+ const fileName = helper.getFileNameFromContentDispositionHeader(
199
+ contDist
200
+ );
201
+ const type = getHeaderValue(headers2["content-type"]);
202
+ return {
203
+ ...response,
204
+ body: new File([r.data], fileName, {
205
+ type
206
+ })
207
+ };
208
+ }
209
+ return response;
210
+ });
211
+ }
212
+ };
213
+ };
214
+ const normalizeHeaders = (headers) => {
215
+ const normalized = {};
216
+ for (const [key, value] of Object.entries(headers)) {
217
+ if (value == null) {
218
+ continue;
219
+ }
220
+ normalized[key.toLowerCase()] = Array.isArray(value) ? value.map(String) : String(value);
221
+ }
222
+ return normalized;
223
+ };
224
+ const getHeaderValue = (header) => {
225
+ if (Array.isArray(header)) {
226
+ return header[0];
227
+ }
228
+ return header;
229
+ };
230
+
231
+ const createHttpClient = (axios, opts) => {
232
+ return createHttpClient$1(axios, {
233
+ querySerializer: opts.querySerializer
234
+ });
235
+ };
236
+
237
+ export { createHttpClient };