@byndyusoft-ui/http-client 0.1.0-alpha.0

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.
Files changed (91) hide show
  1. package/README.md +479 -0
  2. package/dist/cjs/adapters/FetchAdapter.cjs +218 -0
  3. package/dist/cjs/adapters/XhrAdapter.cjs +357 -0
  4. package/dist/cjs/asserts/assertNonBlankString.cjs +11 -0
  5. package/dist/cjs/asserts/assertValidAdapter.cjs +15 -0
  6. package/dist/cjs/asserts/assertValidBaseUrl.cjs +10 -0
  7. package/dist/cjs/asserts/assertValidBody.cjs +17 -0
  8. package/dist/cjs/asserts/assertValidFetchAdapterOptions.cjs +70 -0
  9. package/dist/cjs/asserts/assertValidHeader.cjs +18 -0
  10. package/dist/cjs/asserts/assertValidHeaders.cjs +17 -0
  11. package/dist/cjs/asserts/assertValidHttpClientOptions.cjs +60 -0
  12. package/dist/cjs/asserts/assertValidMethod.cjs +13 -0
  13. package/dist/cjs/asserts/assertValidParam.cjs +23 -0
  14. package/dist/cjs/asserts/assertValidParams.cjs +17 -0
  15. package/dist/cjs/asserts/assertValidRequestConfig.cjs +55 -0
  16. package/dist/cjs/asserts/assertValidResponseType.cjs +13 -0
  17. package/dist/cjs/asserts/assertValidSignal.cjs +16 -0
  18. package/dist/cjs/asserts/assertValidTimeout.cjs +12 -0
  19. package/dist/cjs/asserts/assertValidUrl.cjs +10 -0
  20. package/dist/cjs/asserts/assertValidValidateStatus.cjs +13 -0
  21. package/dist/cjs/asserts/assertValidWithCredentials.cjs +12 -0
  22. package/dist/cjs/asserts/assertValidXhrAdapterOptions.cjs +39 -0
  23. package/dist/cjs/asserts/headerValueLineBreakPattern.cjs +5 -0
  24. package/dist/cjs/asserts/isRecord.cjs +11 -0
  25. package/dist/cjs/constants/httpMethods.cjs +13 -0
  26. package/dist/cjs/constants/httpResponseTypes.cjs +11 -0
  27. package/dist/cjs/constants/httpStatusCodes.cjs +68 -0
  28. package/dist/cjs/constants/requestBuilderErrorCodes.cjs +26 -0
  29. package/dist/cjs/core/HttpClient.cjs +141 -0
  30. package/dist/cjs/core/HttpRequestBuilder.cjs +129 -0
  31. package/dist/cjs/errors/AbortError.cjs +12 -0
  32. package/dist/cjs/errors/HttpClientError.cjs +18 -0
  33. package/dist/cjs/errors/HttpResponseError.cjs +26 -0
  34. package/dist/cjs/errors/NetworkError.cjs +12 -0
  35. package/dist/cjs/errors/ParseError.cjs +20 -0
  36. package/dist/cjs/errors/RequestBuilderError.cjs +18 -0
  37. package/dist/cjs/errors/RequestPreparationError.cjs +13 -0
  38. package/dist/cjs/errors/TimeoutError.cjs +18 -0
  39. package/dist/cjs/index.cjs +45 -0
  40. package/dist/cjs/utilities/buildUrl.cjs +94 -0
  41. package/dist/cjs/utilities/getErrorMessage.cjs +8 -0
  42. package/dist/cjs/utilities/isStatusAccepted.cjs +8 -0
  43. package/dist/cjs/utilities/mergeHeaders.cjs +30 -0
  44. package/dist/cjs/utilities/mergeParams.cjs +37 -0
  45. package/dist/cjs/utilities/prepareRequestBody.cjs +43 -0
  46. package/dist/esm/adapters/FetchAdapter.js +216 -0
  47. package/dist/esm/adapters/XhrAdapter.js +355 -0
  48. package/dist/esm/asserts/assertNonBlankString.js +9 -0
  49. package/dist/esm/asserts/assertValidAdapter.js +13 -0
  50. package/dist/esm/asserts/assertValidBaseUrl.js +8 -0
  51. package/dist/esm/asserts/assertValidBody.js +15 -0
  52. package/dist/esm/asserts/assertValidFetchAdapterOptions.js +68 -0
  53. package/dist/esm/asserts/assertValidHeader.js +16 -0
  54. package/dist/esm/asserts/assertValidHeaders.js +15 -0
  55. package/dist/esm/asserts/assertValidHttpClientOptions.js +58 -0
  56. package/dist/esm/asserts/assertValidMethod.js +11 -0
  57. package/dist/esm/asserts/assertValidParam.js +21 -0
  58. package/dist/esm/asserts/assertValidParams.js +15 -0
  59. package/dist/esm/asserts/assertValidRequestConfig.js +53 -0
  60. package/dist/esm/asserts/assertValidResponseType.js +11 -0
  61. package/dist/esm/asserts/assertValidSignal.js +14 -0
  62. package/dist/esm/asserts/assertValidTimeout.js +10 -0
  63. package/dist/esm/asserts/assertValidUrl.js +8 -0
  64. package/dist/esm/asserts/assertValidValidateStatus.js +11 -0
  65. package/dist/esm/asserts/assertValidWithCredentials.js +10 -0
  66. package/dist/esm/asserts/assertValidXhrAdapterOptions.js +37 -0
  67. package/dist/esm/asserts/headerValueLineBreakPattern.js +3 -0
  68. package/dist/esm/asserts/isRecord.js +9 -0
  69. package/dist/esm/constants/httpMethods.js +11 -0
  70. package/dist/esm/constants/httpResponseTypes.js +9 -0
  71. package/dist/esm/constants/httpStatusCodes.js +66 -0
  72. package/dist/esm/constants/requestBuilderErrorCodes.js +24 -0
  73. package/dist/esm/core/HttpClient.js +139 -0
  74. package/dist/esm/core/HttpRequestBuilder.js +127 -0
  75. package/dist/esm/errors/AbortError.js +9 -0
  76. package/dist/esm/errors/HttpClientError.js +15 -0
  77. package/dist/esm/errors/HttpResponseError.js +23 -0
  78. package/dist/esm/errors/NetworkError.js +9 -0
  79. package/dist/esm/errors/ParseError.js +17 -0
  80. package/dist/esm/errors/RequestBuilderError.js +15 -0
  81. package/dist/esm/errors/RequestPreparationError.js +10 -0
  82. package/dist/esm/errors/TimeoutError.js +15 -0
  83. package/dist/esm/index.js +16 -0
  84. package/dist/esm/utilities/buildUrl.js +92 -0
  85. package/dist/esm/utilities/getErrorMessage.js +6 -0
  86. package/dist/esm/utilities/isStatusAccepted.js +6 -0
  87. package/dist/esm/utilities/mergeHeaders.js +27 -0
  88. package/dist/esm/utilities/mergeParams.js +35 -0
  89. package/dist/esm/utilities/prepareRequestBody.js +41 -0
  90. package/dist/index.d.ts +354 -0
  91. package/package.json +53 -0
@@ -0,0 +1,127 @@
1
+ import { HTTP_RESPONSE_TYPES } from '../constants/httpResponseTypes.js';
2
+ import { REQUEST_BUILDER_ERROR_CODES } from '../constants/requestBuilderErrorCodes.js';
3
+ import { RequestBuilderError } from '../errors/RequestBuilderError.js';
4
+ import { assertValidBody } from '../asserts/assertValidBody.js';
5
+ import { assertNonBlankString } from '../asserts/assertNonBlankString.js';
6
+ import { assertValidBaseUrl } from '../asserts/assertValidBaseUrl.js';
7
+ import { assertValidHeader } from '../asserts/assertValidHeader.js';
8
+ import { assertValidHeaders } from '../asserts/assertValidHeaders.js';
9
+ import { assertValidParams } from '../asserts/assertValidParams.js';
10
+ import { assertValidTimeout } from '../asserts/assertValidTimeout.js';
11
+ import { assertValidWithCredentials } from '../asserts/assertValidWithCredentials.js';
12
+ import { assertValidValidateStatus } from '../asserts/assertValidValidateStatus.js';
13
+ import { assertValidMethod } from '../asserts/assertValidMethod.js';
14
+ import { assertValidParam } from '../asserts/assertValidParam.js';
15
+ import { assertValidRequestConfig } from '../asserts/assertValidRequestConfig.js';
16
+ import { assertValidSignal } from '../asserts/assertValidSignal.js';
17
+ import { assertValidUrl } from '../asserts/assertValidUrl.js';
18
+ import { HEADER_VALUE_LINE_BREAK_PATTERN } from '../asserts/headerValueLineBreakPattern.js';
19
+ import { mergeHeaders } from '../utilities/mergeHeaders.js';
20
+ import { mergeParams } from '../utilities/mergeParams.js';
21
+
22
+ function cloneConfig(config) {
23
+ return {
24
+ ...config,
25
+ ...(config.headers === undefined ? {} : { headers: { ...config.headers } }),
26
+ ...(config.params === undefined ? {} : { params: mergeParams(config.params) })
27
+ };
28
+ }
29
+ /**
30
+ * Builds request configs immutably. Every configuration method returns a new builder instance.
31
+ */
32
+ class HttpRequestBuilder {
33
+ executor;
34
+ config;
35
+ constructor(executor, method, url) {
36
+ if (typeof executor !== 'function') {
37
+ throw new RequestBuilderError('Executor must be a function', REQUEST_BUILDER_ERROR_CODES.INVALID_EXECUTOR);
38
+ }
39
+ assertValidMethod(method);
40
+ assertValidUrl(url);
41
+ this.executor = executor;
42
+ this.config = { method, url };
43
+ }
44
+ withConfig(partial) {
45
+ const builder = new HttpRequestBuilder(this.executor, this.config.method, this.config.url);
46
+ builder.config = cloneConfig({ ...this.config, ...partial });
47
+ return builder;
48
+ }
49
+ build() {
50
+ assertValidRequestConfig(this.config);
51
+ return cloneConfig(this.config);
52
+ }
53
+ baseUrl(value) {
54
+ assertValidBaseUrl(value);
55
+ return this.withConfig({ baseUrl: value });
56
+ }
57
+ header(key, value) {
58
+ assertValidHeader(key, value);
59
+ return this.withConfig({ headers: mergeHeaders(this.config.headers, { [key]: value }) });
60
+ }
61
+ headers(headers) {
62
+ assertValidHeaders(headers);
63
+ return this.withConfig({ headers: mergeHeaders(this.config.headers, headers) });
64
+ }
65
+ param(key, value) {
66
+ assertValidParam(key, value);
67
+ return this.withConfig({
68
+ params: mergeParams(this.config.params, { [key]: value })
69
+ });
70
+ }
71
+ params(params) {
72
+ assertValidParams(params);
73
+ return this.withConfig({
74
+ params: mergeParams(this.config.params, params)
75
+ });
76
+ }
77
+ body(data) {
78
+ assertValidBody(this.config.method, data);
79
+ return this.withConfig({ data });
80
+ }
81
+ signal(signal) {
82
+ assertValidSignal(signal);
83
+ return this.withConfig({ signal });
84
+ }
85
+ /** A zero timeout disables the request timeout, including a timeout inherited from the client config. */
86
+ timeout(timeout) {
87
+ assertValidTimeout(timeout);
88
+ return this.withConfig({ timeout });
89
+ }
90
+ /** Determines whether a response status should be treated as successful. */
91
+ validateStatus(validateStatus) {
92
+ assertValidValidateStatus(validateStatus);
93
+ return this.withConfig({ validateStatus });
94
+ }
95
+ /** Includes credentials in cross-origin requests. */
96
+ withCredentials(withCredentials) {
97
+ assertValidWithCredentials(withCredentials);
98
+ return this.withConfig({ withCredentials });
99
+ }
100
+ bearer(token) {
101
+ assertNonBlankString(token, 'Bearer token must be a non-empty string', REQUEST_BUILDER_ERROR_CODES.INVALID_BEARER_TOKEN);
102
+ if (HEADER_VALUE_LINE_BREAK_PATTERN.test(token)) {
103
+ throw new RequestBuilderError('Bearer token must not contain line breaks', REQUEST_BUILDER_ERROR_CODES.INVALID_BEARER_TOKEN);
104
+ }
105
+ return this.header('Authorization', `Bearer ${token}`);
106
+ }
107
+ asJson() {
108
+ return this.withConfig({ responseType: HTTP_RESPONSE_TYPES.JSON });
109
+ }
110
+ asText() {
111
+ return this.withConfig({ responseType: HTTP_RESPONSE_TYPES.TEXT });
112
+ }
113
+ asBlob() {
114
+ return this.withConfig({ responseType: HTTP_RESPONSE_TYPES.BLOB });
115
+ }
116
+ asArrayBuffer() {
117
+ return this.withConfig({ responseType: HTTP_RESPONSE_TYPES.ARRAY_BUFFER });
118
+ }
119
+ asStream() {
120
+ return this.withConfig({ responseType: HTTP_RESPONSE_TYPES.STREAM });
121
+ }
122
+ execute() {
123
+ return this.executor(this.build());
124
+ }
125
+ }
126
+
127
+ export { HttpRequestBuilder };
@@ -0,0 +1,9 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ class AbortError extends HttpClientError {
4
+ }
5
+ function isAbortError(error) {
6
+ return error instanceof AbortError;
7
+ }
8
+
9
+ export { AbortError, isAbortError };
@@ -0,0 +1,15 @@
1
+ /** Base class for all HTTP client errors. Supports catching any client error with a single instanceof check. */
2
+ class HttpClientError extends Error {
3
+ config;
4
+ constructor(message, options) {
5
+ super(message, options);
6
+ this.name = this.constructor.name;
7
+ this.config = options?.config;
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ }
11
+ function isHttpClientError(error) {
12
+ return error instanceof HttpClientError;
13
+ }
14
+
15
+ export { HttpClientError, isHttpClientError };
@@ -0,0 +1,23 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ /** Indicates that the server completed the request with a non-2xx status. */
4
+ class HttpResponseError extends HttpClientError {
5
+ status;
6
+ statusText;
7
+ headers;
8
+ config;
9
+ data;
10
+ constructor(message, status, statusText, headers, config, data, options) {
11
+ super(message, { ...options, config });
12
+ this.status = status;
13
+ this.statusText = statusText;
14
+ this.headers = headers;
15
+ this.config = config;
16
+ this.data = data;
17
+ }
18
+ }
19
+ function isHttpResponseError(error) {
20
+ return error instanceof HttpResponseError;
21
+ }
22
+
23
+ export { HttpResponseError, isHttpResponseError };
@@ -0,0 +1,9 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ class NetworkError extends HttpClientError {
4
+ }
5
+ function isNetworkError(error) {
6
+ return error instanceof NetworkError;
7
+ }
8
+
9
+ export { NetworkError, isNetworkError };
@@ -0,0 +1,17 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ /** Indicates that a successful response could not be parsed in the requested format. */
4
+ class ParseError extends HttpClientError {
5
+ responseType;
6
+ raw;
7
+ constructor(message, options) {
8
+ super(message, options);
9
+ this.responseType = options?.responseType;
10
+ this.raw = options?.raw;
11
+ }
12
+ }
13
+ function isParseError(error) {
14
+ return error instanceof ParseError;
15
+ }
16
+
17
+ export { ParseError, isParseError };
@@ -0,0 +1,15 @@
1
+ import { REQUEST_BUILDER_ERROR_CODES } from '../constants/requestBuilderErrorCodes.js';
2
+ import { HttpClientError } from './HttpClientError.js';
3
+
4
+ class RequestBuilderError extends HttpClientError {
5
+ code;
6
+ constructor(message, code = REQUEST_BUILDER_ERROR_CODES.INVALID_CONFIG) {
7
+ super(message);
8
+ this.code = code;
9
+ }
10
+ }
11
+ function isRequestBuilderError(error) {
12
+ return error instanceof RequestBuilderError;
13
+ }
14
+
15
+ export { RequestBuilderError, isRequestBuilderError };
@@ -0,0 +1,10 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ /** Indicates that an adapter could not prepare a request before sending it. */
4
+ class RequestPreparationError extends HttpClientError {
5
+ }
6
+ function isRequestPreparationError(error) {
7
+ return error instanceof RequestPreparationError;
8
+ }
9
+
10
+ export { RequestPreparationError, isRequestPreparationError };
@@ -0,0 +1,15 @@
1
+ import { HttpClientError } from './HttpClientError.js';
2
+
3
+ /** Indicates that the request exceeded the configured timeout. */
4
+ class TimeoutError extends HttpClientError {
5
+ timeout;
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.timeout = options?.timeout;
9
+ }
10
+ }
11
+ function isTimeoutError(error) {
12
+ return error instanceof TimeoutError;
13
+ }
14
+
15
+ export { TimeoutError, isTimeoutError };
@@ -0,0 +1,16 @@
1
+ export { HttpClient } from './core/HttpClient.js';
2
+ export { HttpRequestBuilder } from './core/HttpRequestBuilder.js';
3
+ export { FetchAdapter } from './adapters/FetchAdapter.js';
4
+ export { XhrAdapter } from './adapters/XhrAdapter.js';
5
+ export { HTTP_METHODS } from './constants/httpMethods.js';
6
+ export { HTTP_STATUS_CODES } from './constants/httpStatusCodes.js';
7
+ export { HTTP_RESPONSE_TYPES } from './constants/httpResponseTypes.js';
8
+ export { REQUEST_BUILDER_ERROR_CODES } from './constants/requestBuilderErrorCodes.js';
9
+ export { AbortError, isAbortError } from './errors/AbortError.js';
10
+ export { HttpClientError, isHttpClientError } from './errors/HttpClientError.js';
11
+ export { HttpResponseError, isHttpResponseError } from './errors/HttpResponseError.js';
12
+ export { NetworkError, isNetworkError } from './errors/NetworkError.js';
13
+ export { ParseError, isParseError } from './errors/ParseError.js';
14
+ export { RequestBuilderError, isRequestBuilderError } from './errors/RequestBuilderError.js';
15
+ export { RequestPreparationError, isRequestPreparationError } from './errors/RequestPreparationError.js';
16
+ export { TimeoutError, isTimeoutError } from './errors/TimeoutError.js';
@@ -0,0 +1,92 @@
1
+ function splitUrl(value) {
2
+ const hashIndex = value.indexOf('#');
3
+ const beforeHash = hashIndex === -1 ? value : value.slice(0, hashIndex);
4
+ const hash = hashIndex === -1 ? '' : value.slice(hashIndex);
5
+ const queryIndex = beforeHash.indexOf('?');
6
+ return {
7
+ path: queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex),
8
+ query: queryIndex === -1 ? '' : beforeHash.slice(queryIndex + 1),
9
+ hash
10
+ };
11
+ }
12
+ function joinPaths(basePath, path) {
13
+ if (!path) {
14
+ return basePath;
15
+ }
16
+ if (!basePath) {
17
+ return path;
18
+ }
19
+ return `${basePath.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
20
+ }
21
+ function getAbsoluteUrl(value) {
22
+ try {
23
+ return new URL(value);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ function serializeParams(params) {
30
+ if (params === undefined) {
31
+ return '';
32
+ }
33
+ const searchParams = new URLSearchParams();
34
+ for (const [key, value] of Object.entries(params)) {
35
+ if (value === null || value === undefined) {
36
+ continue;
37
+ }
38
+ const values = Array.isArray(value) ? value : [value];
39
+ for (const item of values) {
40
+ if (item === null || item === undefined) {
41
+ continue;
42
+ }
43
+ searchParams.append(key, String(item));
44
+ }
45
+ }
46
+ return searchParams.toString();
47
+ }
48
+ function appendParams(url, params) {
49
+ const query = serializeParams(params);
50
+ if (!query) {
51
+ return url;
52
+ }
53
+ const { path, query: existingQuery, hash } = splitUrl(url);
54
+ const combinedQuery = [existingQuery, query].filter(Boolean).join('&');
55
+ return `${path}?${combinedQuery}${hash}`;
56
+ }
57
+ function resolveRelativeUrl(baseUrl, url) {
58
+ const base = splitUrl(baseUrl);
59
+ const request = splitUrl(url);
60
+ const query = [base.query, request.query].filter(Boolean).join('&');
61
+ return `${joinPaths(base.path, request.path)}${query ? `?${query}` : ''}${request.hash}`;
62
+ }
63
+ function resolveAbsoluteUrl(base, url) {
64
+ const request = splitUrl(url);
65
+ base.pathname = joinPaths(base.pathname, request.path);
66
+ base.hash = request.hash;
67
+ if (request.query) {
68
+ const requestParams = new URLSearchParams(request.query);
69
+ requestParams.forEach((value, key) => {
70
+ base.searchParams.append(key, value);
71
+ });
72
+ }
73
+ return base.toString();
74
+ }
75
+ /**
76
+ * Resolves a request URL against an optional base URL, preserves existing query values and fragments,
77
+ * and appends serialized params before the fragment.
78
+ */
79
+ function buildUrl(baseUrl, url, params) {
80
+ const absoluteUrl = getAbsoluteUrl(url);
81
+ if (absoluteUrl !== undefined) {
82
+ return appendParams(absoluteUrl.toString(), params);
83
+ }
84
+ if (baseUrl === undefined) {
85
+ return appendParams(url, params);
86
+ }
87
+ const absoluteBaseUrl = getAbsoluteUrl(baseUrl);
88
+ const fullUrl = absoluteBaseUrl === undefined ? resolveRelativeUrl(baseUrl, url) : resolveAbsoluteUrl(absoluteBaseUrl, url);
89
+ return appendParams(fullUrl, params);
90
+ }
91
+
92
+ export { buildUrl };
@@ -0,0 +1,6 @@
1
+ /** Returns a non-empty Error message or the provided fallback for unknown error values. */
2
+ function getErrorMessage(error, fallback) {
3
+ return error instanceof Error && error.message ? error.message : fallback;
4
+ }
5
+
6
+ export { getErrorMessage };
@@ -0,0 +1,6 @@
1
+ /** Applies a custom status predicate or accepts the standard successful HTTP status range. */
2
+ function isStatusAccepted(status, validateStatus) {
3
+ return validateStatus === undefined ? status >= 200 && status < 300 : validateStatus(status);
4
+ }
5
+
6
+ export { isStatusAccepted };
@@ -0,0 +1,27 @@
1
+ /** Merges header maps from left to right using case-insensitive names while preserving the latest name casing. */
2
+ function mergeHeaders(...sources) {
3
+ const result = {};
4
+ const normalizedKeys = new Map();
5
+ for (const source of sources) {
6
+ if (source === undefined) {
7
+ continue;
8
+ }
9
+ for (const [key, value] of Object.entries(source)) {
10
+ const normalizedKey = key.toLowerCase();
11
+ const previousKey = normalizedKeys.get(normalizedKey);
12
+ if (previousKey !== undefined) {
13
+ delete result[previousKey];
14
+ }
15
+ result[key] = value;
16
+ normalizedKeys.set(normalizedKey, key);
17
+ }
18
+ }
19
+ return result;
20
+ }
21
+ /** Checks whether a header map contains a name using case-insensitive comparison. */
22
+ function hasHeader(headers, name) {
23
+ const normalizedName = name.toLowerCase();
24
+ return Object.keys(headers).some(key => key.toLowerCase() === normalizedName);
25
+ }
26
+
27
+ export { hasHeader, mergeHeaders };
@@ -0,0 +1,35 @@
1
+ function isPresentParamPrimitive(value) {
2
+ return value !== null && value !== undefined;
3
+ }
4
+ /**
5
+ * Merges query params from left to right. `null` / `undefined` remove a key; nullish array items are dropped.
6
+ * Returns undefined when no params remain.
7
+ */
8
+ function mergeParams(...sources) {
9
+ const result = {};
10
+ for (const source of sources) {
11
+ if (source === undefined) {
12
+ continue;
13
+ }
14
+ for (const [key, value] of Object.entries(source)) {
15
+ if (value === null || value === undefined) {
16
+ delete result[key];
17
+ continue;
18
+ }
19
+ if (Array.isArray(value)) {
20
+ const items = value.filter(isPresentParamPrimitive);
21
+ if (items.length === 0) {
22
+ delete result[key];
23
+ }
24
+ else {
25
+ result[key] = [...items];
26
+ }
27
+ continue;
28
+ }
29
+ result[key] = value;
30
+ }
31
+ }
32
+ return Object.keys(result).length === 0 ? undefined : result;
33
+ }
34
+
35
+ export { mergeParams };
@@ -0,0 +1,41 @@
1
+ import { hasHeader } from './mergeHeaders.js';
2
+
3
+ function hasTag(value, tag) {
4
+ return Object.prototype.toString.call(value) === tag;
5
+ }
6
+ function isRequestBody(value) {
7
+ return (typeof value === 'string' ||
8
+ ArrayBuffer.isView(value) ||
9
+ hasTag(value, '[object ArrayBuffer]') ||
10
+ hasTag(value, '[object Blob]') ||
11
+ hasTag(value, '[object FormData]') ||
12
+ hasTag(value, '[object URLSearchParams]'));
13
+ }
14
+ function isUrlSearchParams(value) {
15
+ return hasTag(value, '[object URLSearchParams]');
16
+ }
17
+ /**
18
+ * Converts request data into a transport-compatible body.
19
+ * Mutates the provided headers by adding a default Content-Type for JSON and URLSearchParams bodies when absent.
20
+ * FormData is returned without setting Content-Type so the transport can add its boundary.
21
+ *
22
+ * @throws TypeError when data cannot be serialized as JSON.
23
+ */
24
+ function prepareRequestBody(data, headers) {
25
+ if (isRequestBody(data)) {
26
+ if (isUrlSearchParams(data) && !hasHeader(headers, 'Content-Type')) {
27
+ headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
28
+ }
29
+ return isUrlSearchParams(data) ? data.toString() : data;
30
+ }
31
+ if (!hasHeader(headers, 'Content-Type')) {
32
+ headers['Content-Type'] = 'application/json';
33
+ }
34
+ const body = JSON.stringify(data);
35
+ if (typeof body !== 'string') {
36
+ throw new TypeError('Request body cannot be serialized as JSON');
37
+ }
38
+ return body;
39
+ }
40
+
41
+ export { prepareRequestBody };