@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,218 @@
1
+ 'use strict';
2
+
3
+ var httpMethods = require('../constants/httpMethods.cjs');
4
+ var httpResponseTypes = require('../constants/httpResponseTypes.cjs');
5
+ var HttpClientError = require('../errors/HttpClientError.cjs');
6
+ var HttpResponseError = require('../errors/HttpResponseError.cjs');
7
+ var NetworkError = require('../errors/NetworkError.cjs');
8
+ var ParseError = require('../errors/ParseError.cjs');
9
+ var RequestPreparationError = require('../errors/RequestPreparationError.cjs');
10
+ var TimeoutError = require('../errors/TimeoutError.cjs');
11
+ var AbortError = require('../errors/AbortError.cjs');
12
+ var assertValidFetchAdapterOptions = require('../asserts/assertValidFetchAdapterOptions.cjs');
13
+ var buildUrl = require('../utilities/buildUrl.cjs');
14
+ var getErrorMessage = require('../utilities/getErrorMessage.cjs');
15
+ var mergeHeaders = require('../utilities/mergeHeaders.cjs');
16
+ var prepareRequestBody = require('../utilities/prepareRequestBody.cjs');
17
+ var isStatusAccepted = require('../utilities/isStatusAccepted.cjs');
18
+
19
+ function extractResponseHeaders(headers) {
20
+ const result = {};
21
+ headers.forEach((value, key) => {
22
+ result[key] = value;
23
+ });
24
+ return result;
25
+ }
26
+ function prepareFetchRequest(config) {
27
+ const { url, method, headers = {}, params, data, signal: userSignal, timeout, baseUrl: baseURL } = config;
28
+ let timeoutId;
29
+ let removeAbortListener;
30
+ const cleanup = () => {
31
+ if (timeoutId !== undefined) {
32
+ clearTimeout(timeoutId);
33
+ }
34
+ removeAbortListener?.();
35
+ };
36
+ try {
37
+ const fullUrl = buildUrl.buildUrl(baseURL, url, params);
38
+ const requestHeaders = mergeHeaders.mergeHeaders(headers);
39
+ const body = data !== undefined && method !== httpMethods.HTTP_METHODS.GET && method !== httpMethods.HTTP_METHODS.HEAD
40
+ ? prepareRequestBody.prepareRequestBody(data, requestHeaders)
41
+ : undefined;
42
+ if (timeout) {
43
+ const controller = new AbortController();
44
+ timeoutId = setTimeout(() => controller.abort(), timeout);
45
+ if (userSignal) {
46
+ if (userSignal.aborted) {
47
+ throw new AbortError.AbortError('Request was aborted', { cause: userSignal.reason, config });
48
+ }
49
+ const abort = () => controller.abort();
50
+ userSignal.addEventListener('abort', abort, { once: true });
51
+ removeAbortListener = () => userSignal.removeEventListener('abort', abort);
52
+ }
53
+ return {
54
+ fullUrl,
55
+ requestHeaders,
56
+ body,
57
+ signal: controller.signal,
58
+ userSignal,
59
+ timeout,
60
+ cleanup
61
+ };
62
+ }
63
+ return {
64
+ fullUrl,
65
+ requestHeaders,
66
+ body,
67
+ signal: userSignal,
68
+ userSignal,
69
+ timeout,
70
+ cleanup
71
+ };
72
+ }
73
+ catch (error) {
74
+ cleanup();
75
+ throw error;
76
+ }
77
+ }
78
+ function resolveCredentials(withCredentials, adapterCredentials) {
79
+ if (withCredentials === true) {
80
+ return 'include';
81
+ }
82
+ if (withCredentials === false) {
83
+ return 'same-origin';
84
+ }
85
+ return adapterCredentials ?? 'same-origin';
86
+ }
87
+ function createFetchError(error, request, config) {
88
+ if (error instanceof HttpClientError.HttpClientError) {
89
+ return error;
90
+ }
91
+ if (request.userSignal?.aborted) {
92
+ return new AbortError.AbortError('Request was aborted', { cause: request.userSignal.reason ?? error, config });
93
+ }
94
+ if (request.timeout && error instanceof DOMException && error.name === 'AbortError') {
95
+ return new TimeoutError.TimeoutError(`Request timed out after ${request.timeout}ms`, {
96
+ cause: error,
97
+ config,
98
+ timeout: request.timeout
99
+ });
100
+ }
101
+ return new NetworkError.NetworkError(getErrorMessage.getErrorMessage(error, 'Network request failed'), { cause: error, config });
102
+ }
103
+ async function parseResponseBody(response, config, responseType) {
104
+ if (response.status === 204 || response.headers.get('content-length') === '0') {
105
+ return undefined;
106
+ }
107
+ switch (responseType) {
108
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.TEXT:
109
+ return response.text();
110
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.BLOB:
111
+ return response.blob();
112
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.ARRAY_BUFFER:
113
+ return response.arrayBuffer();
114
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM:
115
+ if (response.body === null) {
116
+ throw new ParseError.ParseError('Streaming response body is not available', {
117
+ config,
118
+ responseType: httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM
119
+ });
120
+ }
121
+ return response.body;
122
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.JSON:
123
+ default: {
124
+ const resolvedResponseType = responseType ?? httpResponseTypes.HTTP_RESPONSE_TYPES.JSON;
125
+ const text = await response.text();
126
+ if (!text) {
127
+ return undefined;
128
+ }
129
+ try {
130
+ return JSON.parse(text);
131
+ }
132
+ catch (error) {
133
+ throw new ParseError.ParseError('Failed to parse response body as JSON', {
134
+ cause: error,
135
+ config,
136
+ responseType: resolvedResponseType,
137
+ raw: text
138
+ });
139
+ }
140
+ }
141
+ }
142
+ }
143
+ class FetchAdapter {
144
+ options;
145
+ constructor(options = {}) {
146
+ assertValidFetchAdapterOptions.assertValidFetchAdapterOptions(options);
147
+ this.options = { ...options };
148
+ }
149
+ async request(config) {
150
+ let request;
151
+ try {
152
+ request = prepareFetchRequest(config);
153
+ }
154
+ catch (error) {
155
+ if (error instanceof HttpClientError.HttpClientError) {
156
+ throw error;
157
+ }
158
+ throw new RequestPreparationError.RequestPreparationError('Failed to prepare HTTP request', { cause: error, config });
159
+ }
160
+ let response;
161
+ try {
162
+ response = await fetch(request.fullUrl, {
163
+ ...this.options,
164
+ method: config.method,
165
+ headers: request.requestHeaders,
166
+ body: request.body,
167
+ credentials: resolveCredentials(config.withCredentials, this.options.credentials),
168
+ signal: request.signal
169
+ });
170
+ }
171
+ catch (error) {
172
+ request.cleanup();
173
+ throw createFetchError(error, request, config);
174
+ }
175
+ let statusAccepted;
176
+ try {
177
+ statusAccepted = isStatusAccepted.isStatusAccepted(response.status, config.validateStatus);
178
+ }
179
+ catch (error) {
180
+ request.cleanup();
181
+ throw error;
182
+ }
183
+ try {
184
+ if (!statusAccepted) {
185
+ let errorData;
186
+ try {
187
+ const text = await response.text();
188
+ try {
189
+ errorData = JSON.parse(text);
190
+ }
191
+ catch {
192
+ errorData = text || undefined;
193
+ }
194
+ }
195
+ catch {
196
+ errorData = undefined;
197
+ }
198
+ throw new HttpResponseError.HttpResponseError(`Request failed with status code ${response.status}`, response.status, response.statusText, extractResponseHeaders(response.headers), config, errorData);
199
+ }
200
+ const responseData = await parseResponseBody(response, config, config.responseType);
201
+ return {
202
+ data: responseData,
203
+ status: response.status,
204
+ statusText: response.statusText,
205
+ headers: extractResponseHeaders(response.headers),
206
+ config
207
+ };
208
+ }
209
+ catch (error) {
210
+ throw createFetchError(error, request, config);
211
+ }
212
+ finally {
213
+ request.cleanup();
214
+ }
215
+ }
216
+ }
217
+
218
+ exports.FetchAdapter = FetchAdapter;
@@ -0,0 +1,357 @@
1
+ 'use strict';
2
+
3
+ var httpMethods = require('../constants/httpMethods.cjs');
4
+ var httpResponseTypes = require('../constants/httpResponseTypes.cjs');
5
+ var HttpClientError = require('../errors/HttpClientError.cjs');
6
+ var HttpResponseError = require('../errors/HttpResponseError.cjs');
7
+ var NetworkError = require('../errors/NetworkError.cjs');
8
+ var ParseError = require('../errors/ParseError.cjs');
9
+ var RequestPreparationError = require('../errors/RequestPreparationError.cjs');
10
+ var TimeoutError = require('../errors/TimeoutError.cjs');
11
+ var AbortError = require('../errors/AbortError.cjs');
12
+ var assertValidXhrAdapterOptions = require('../asserts/assertValidXhrAdapterOptions.cjs');
13
+ var buildUrl = require('../utilities/buildUrl.cjs');
14
+ var getErrorMessage = require('../utilities/getErrorMessage.cjs');
15
+ var mergeHeaders = require('../utilities/mergeHeaders.cjs');
16
+ var prepareRequestBody = require('../utilities/prepareRequestBody.cjs');
17
+ var isStatusAccepted = require('../utilities/isStatusAccepted.cjs');
18
+
19
+ function parseResponseHeaders(rawHeaders) {
20
+ const result = {};
21
+ const lines = rawHeaders.trim().split('\r\n');
22
+ for (const line of lines) {
23
+ const index = line.indexOf(': ');
24
+ if (index > 0) {
25
+ const key = line.slice(0, index).toLowerCase();
26
+ const value = line.slice(index + 2);
27
+ result[key] = value;
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+ function createResponseStream(xhr) {
33
+ let controller;
34
+ let textLength = 0;
35
+ const encoder = new TextEncoder();
36
+ const data = new ReadableStream({
37
+ start(value) {
38
+ controller = value;
39
+ }
40
+ });
41
+ return {
42
+ data,
43
+ append() {
44
+ const text = xhr.responseText;
45
+ const chunk = text.slice(textLength);
46
+ if (chunk) {
47
+ controller?.enqueue(encoder.encode(chunk));
48
+ textLength = text.length;
49
+ }
50
+ },
51
+ close() {
52
+ controller?.close();
53
+ },
54
+ error(reason) {
55
+ controller?.error(reason);
56
+ }
57
+ };
58
+ }
59
+ async function readBlobAsText(blob) {
60
+ if (typeof blob.text === 'function') {
61
+ return blob.text();
62
+ }
63
+ return new Promise((resolve, reject) => {
64
+ const reader = new FileReader();
65
+ reader.onload = () => {
66
+ if (typeof reader.result !== 'string') {
67
+ reject(new TypeError('Failed to read response Blob as text'));
68
+ return;
69
+ }
70
+ resolve(reader.result);
71
+ };
72
+ reader.onerror = () => {
73
+ reject(reader.error ?? new TypeError('Failed to read response Blob as text'));
74
+ };
75
+ reader.readAsText(blob);
76
+ });
77
+ }
78
+ function hasEmptyResponseBody(xhr) {
79
+ return xhr.status === 204 || xhr.getResponseHeader('content-length') === '0';
80
+ }
81
+ function getResponseBody(xhr, config, responseType) {
82
+ if (hasEmptyResponseBody(xhr)) {
83
+ return undefined;
84
+ }
85
+ switch (responseType) {
86
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.ARRAY_BUFFER:
87
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.BLOB:
88
+ return xhr.response;
89
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.TEXT:
90
+ return xhr.response;
91
+ case httpResponseTypes.HTTP_RESPONSE_TYPES.JSON:
92
+ default: {
93
+ const resolvedResponseType = responseType ?? httpResponseTypes.HTTP_RESPONSE_TYPES.JSON;
94
+ const text = xhr.response;
95
+ if (!text) {
96
+ return undefined;
97
+ }
98
+ try {
99
+ return JSON.parse(text);
100
+ }
101
+ catch (error) {
102
+ throw new ParseError.ParseError('Failed to parse response body as JSON', {
103
+ cause: error,
104
+ config,
105
+ responseType: resolvedResponseType,
106
+ raw: text
107
+ });
108
+ }
109
+ }
110
+ }
111
+ }
112
+ function setResponseType(xhr, responseType) {
113
+ if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.ARRAY_BUFFER) {
114
+ // XHR accepts only a lowercase DOM value.
115
+ xhr.responseType = 'arraybuffer';
116
+ }
117
+ else if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.BLOB) {
118
+ xhr.responseType = 'blob';
119
+ }
120
+ else {
121
+ xhr.responseType = 'text';
122
+ }
123
+ }
124
+ function assertStreamingSupported(responseType, config) {
125
+ if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM &&
126
+ (typeof ReadableStream === 'undefined' || typeof TextEncoder === 'undefined')) {
127
+ throw new RequestPreparationError.RequestPreparationError('Streaming responses are not supported in this environment', { config });
128
+ }
129
+ }
130
+ function createResponse(xhr, config, data) {
131
+ return {
132
+ data,
133
+ status: xhr.status,
134
+ statusText: xhr.statusText,
135
+ headers: parseResponseHeaders(xhr.getAllResponseHeaders()),
136
+ config
137
+ };
138
+ }
139
+ async function getErrorResponseText(xhr) {
140
+ if (xhr.responseType === 'blob') {
141
+ const blob = xhr.response;
142
+ if (!(blob instanceof Blob)) {
143
+ return undefined;
144
+ }
145
+ return readBlobAsText(blob);
146
+ }
147
+ if (xhr.responseType === 'arraybuffer') {
148
+ const arrayBuffer = xhr.response;
149
+ if (typeof TextDecoder === 'undefined' || !(arrayBuffer instanceof ArrayBuffer)) {
150
+ return undefined;
151
+ }
152
+ return new TextDecoder().decode(arrayBuffer);
153
+ }
154
+ return xhr.responseText;
155
+ }
156
+ async function getErrorData(xhr) {
157
+ try {
158
+ const text = await getErrorResponseText(xhr);
159
+ if (!text) {
160
+ return undefined;
161
+ }
162
+ try {
163
+ return JSON.parse(text);
164
+ }
165
+ catch {
166
+ return text;
167
+ }
168
+ }
169
+ catch {
170
+ return undefined;
171
+ }
172
+ }
173
+ async function createResponseError(xhr, config) {
174
+ return new HttpResponseError.HttpResponseError(`Request failed with status code ${xhr.status}`, xhr.status, xhr.statusText, parseResponseHeaders(xhr.getAllResponseHeaders()), config, await getErrorData(xhr));
175
+ }
176
+ function resolveRequestConfig(config, options) {
177
+ return {
178
+ ...config,
179
+ ...(config.responseType === undefined && options.responseType !== undefined
180
+ ? { responseType: options.responseType }
181
+ : {}),
182
+ ...(config.timeout === undefined && options.timeout !== undefined ? { timeout: options.timeout } : {}),
183
+ ...(config.withCredentials === undefined && options.withCredentials !== undefined
184
+ ? { withCredentials: options.withCredentials }
185
+ : {})
186
+ };
187
+ }
188
+ function prepareXhrRequest(config, options) {
189
+ const { url, method, headers = {}, params, data, signal: userSignal, timeout, responseType, baseUrl: baseURL } = config;
190
+ const fullUrl = buildUrl.buildUrl(baseURL, url, params);
191
+ if (userSignal?.aborted) {
192
+ throw new AbortError.AbortError('Request was aborted', { cause: userSignal.reason, config });
193
+ }
194
+ assertStreamingSupported(responseType, config);
195
+ const xhr = new XMLHttpRequest();
196
+ xhr.open(method, fullUrl, true);
197
+ xhr.withCredentials = config.withCredentials === true;
198
+ if (options.mimeType !== undefined) {
199
+ xhr.overrideMimeType(options.mimeType);
200
+ }
201
+ if (timeout !== undefined && timeout > 0) {
202
+ xhr.timeout = timeout;
203
+ }
204
+ setResponseType(xhr, responseType);
205
+ const requestHeaders = mergeHeaders.mergeHeaders(headers);
206
+ const body = data !== undefined && method !== httpMethods.HTTP_METHODS.GET && method !== httpMethods.HTTP_METHODS.HEAD
207
+ ? prepareRequestBody.prepareRequestBody(data, requestHeaders)
208
+ : undefined;
209
+ for (const [key, value] of Object.entries(requestHeaders)) {
210
+ xhr.setRequestHeader(key, value);
211
+ }
212
+ const onAbort = () => xhr.abort();
213
+ userSignal?.addEventListener('abort', onAbort, { once: true });
214
+ return {
215
+ xhr,
216
+ body,
217
+ cleanup() {
218
+ userSignal?.removeEventListener('abort', onAbort);
219
+ }
220
+ };
221
+ }
222
+ function configureXhrEventHandlers(request, config, options, resolve, reject) {
223
+ const { xhr } = request;
224
+ const { signal: userSignal, timeout, responseType } = config;
225
+ const { onDownloadProgress, onUploadProgress } = options;
226
+ let responseStream;
227
+ let streamResponseResolved = false;
228
+ let statusValidation;
229
+ const isSuccessful = () => {
230
+ if (statusValidation === undefined) {
231
+ try {
232
+ statusValidation = { accepted: isStatusAccepted.isStatusAccepted(xhr.status, config.validateStatus) };
233
+ }
234
+ catch (error) {
235
+ statusValidation = { error };
236
+ }
237
+ }
238
+ if ('error' in statusValidation) {
239
+ throw statusValidation.error;
240
+ }
241
+ return statusValidation.accepted;
242
+ };
243
+ const resolveStream = () => {
244
+ if (streamResponseResolved || !isSuccessful()) {
245
+ return;
246
+ }
247
+ responseStream = createResponseStream(xhr);
248
+ streamResponseResolved = true;
249
+ resolve(createResponse(xhr, config, responseStream.data));
250
+ };
251
+ const rejectOrFailStream = (error) => {
252
+ request.cleanup();
253
+ if (streamResponseResolved) {
254
+ responseStream?.error(error);
255
+ return;
256
+ }
257
+ reject(error);
258
+ };
259
+ if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM) {
260
+ xhr.onreadystatechange = () => {
261
+ if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
262
+ try {
263
+ resolveStream();
264
+ }
265
+ catch (error) {
266
+ request.cleanup();
267
+ reject(error);
268
+ xhr.abort();
269
+ }
270
+ }
271
+ };
272
+ }
273
+ if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM || onDownloadProgress !== undefined) {
274
+ xhr.onprogress = event => {
275
+ responseStream?.append();
276
+ onDownloadProgress?.(event, config);
277
+ };
278
+ }
279
+ if (request.body !== undefined && onUploadProgress !== undefined) {
280
+ xhr.upload.onprogress = event => {
281
+ onUploadProgress(event, config);
282
+ };
283
+ }
284
+ xhr.onload = async () => {
285
+ request.cleanup();
286
+ let successful;
287
+ try {
288
+ successful = isSuccessful();
289
+ }
290
+ catch (error) {
291
+ reject(error);
292
+ return;
293
+ }
294
+ if (!successful) {
295
+ reject(await createResponseError(xhr, config));
296
+ return;
297
+ }
298
+ if (responseType === httpResponseTypes.HTTP_RESPONSE_TYPES.STREAM) {
299
+ resolveStream();
300
+ responseStream?.append();
301
+ responseStream?.close();
302
+ return;
303
+ }
304
+ try {
305
+ const responseData = getResponseBody(xhr, config, responseType);
306
+ resolve(createResponse(xhr, config, responseData));
307
+ }
308
+ catch (error) {
309
+ reject(error instanceof HttpClientError.HttpClientError
310
+ ? error
311
+ : new NetworkError.NetworkError(getErrorMessage.getErrorMessage(error, 'Network request failed'), { cause: error, config }));
312
+ }
313
+ };
314
+ xhr.onerror = event => {
315
+ rejectOrFailStream(new NetworkError.NetworkError('Network request failed', { cause: event, config }));
316
+ };
317
+ xhr.onabort = event => {
318
+ rejectOrFailStream(new AbortError.AbortError('Request was aborted', { cause: userSignal?.reason ?? event, config }));
319
+ };
320
+ xhr.ontimeout = event => {
321
+ const timeoutMs = timeout ?? 0;
322
+ rejectOrFailStream(new TimeoutError.TimeoutError(`Request timed out after ${timeoutMs}ms`, {
323
+ cause: event,
324
+ config,
325
+ timeout: timeoutMs
326
+ }));
327
+ };
328
+ }
329
+ class XhrAdapter {
330
+ options;
331
+ constructor(options = {}) {
332
+ assertValidXhrAdapterOptions.assertValidXhrAdapterOptions(options);
333
+ this.options = { ...options };
334
+ }
335
+ request(config) {
336
+ const resolvedConfig = resolveRequestConfig(config, this.options);
337
+ return new Promise((resolve, reject) => {
338
+ let request;
339
+ try {
340
+ request = prepareXhrRequest(resolvedConfig, this.options);
341
+ configureXhrEventHandlers(request, resolvedConfig, this.options, resolve, reject);
342
+ request.xhr.send(request.body);
343
+ }
344
+ catch (error) {
345
+ request?.cleanup();
346
+ reject(error instanceof HttpClientError.HttpClientError
347
+ ? error
348
+ : new RequestPreparationError.RequestPreparationError('Failed to prepare HTTP request', {
349
+ cause: error,
350
+ config: resolvedConfig
351
+ }));
352
+ }
353
+ });
354
+ }
355
+ }
356
+
357
+ exports.XhrAdapter = XhrAdapter;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ var RequestBuilderError = require('../errors/RequestBuilderError.cjs');
4
+
5
+ function assertNonBlankString(value, message, code) {
6
+ if (typeof value !== 'string' || value.trim().length === 0) {
7
+ throw new RequestBuilderError.RequestBuilderError(message, code);
8
+ }
9
+ }
10
+
11
+ exports.assertNonBlankString = assertNonBlankString;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ var requestBuilderErrorCodes = require('../constants/requestBuilderErrorCodes.cjs');
4
+ var RequestBuilderError = require('../errors/RequestBuilderError.cjs');
5
+
6
+ /** Validates an HTTP client adapter. */
7
+ function assertValidAdapter(adapter) {
8
+ if (adapter === null ||
9
+ (typeof adapter !== 'object' && typeof adapter !== 'function') ||
10
+ typeof adapter.request !== 'function') {
11
+ throw new RequestBuilderError.RequestBuilderError('Adapter must implement a request method', requestBuilderErrorCodes.REQUEST_BUILDER_ERROR_CODES.INVALID_ADAPTER);
12
+ }
13
+ }
14
+
15
+ exports.assertValidAdapter = assertValidAdapter;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var requestBuilderErrorCodes = require('../constants/requestBuilderErrorCodes.cjs');
4
+ var assertNonBlankString = require('./assertNonBlankString.cjs');
5
+
6
+ function assertValidBaseUrl(baseUrl) {
7
+ assertNonBlankString.assertNonBlankString(baseUrl, 'Base URL must be a non-empty string', requestBuilderErrorCodes.REQUEST_BUILDER_ERROR_CODES.INVALID_BASE_URL);
8
+ }
9
+
10
+ exports.assertValidBaseUrl = assertValidBaseUrl;
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var httpMethods = require('../constants/httpMethods.cjs');
4
+ var requestBuilderErrorCodes = require('../constants/requestBuilderErrorCodes.cjs');
5
+ var RequestBuilderError = require('../errors/RequestBuilderError.cjs');
6
+
7
+ /** Validates that a request method supports a body and that the body is explicitly defined. */
8
+ function assertValidBody(method, data) {
9
+ if (method === httpMethods.HTTP_METHODS.GET || method === httpMethods.HTTP_METHODS.HEAD) {
10
+ throw new RequestBuilderError.RequestBuilderError('Body is not allowed for GET or HEAD requests', requestBuilderErrorCodes.REQUEST_BUILDER_ERROR_CODES.INVALID_BODY);
11
+ }
12
+ if (data === undefined) {
13
+ throw new RequestBuilderError.RequestBuilderError('Body must not be undefined', requestBuilderErrorCodes.REQUEST_BUILDER_ERROR_CODES.INVALID_BODY);
14
+ }
15
+ }
16
+
17
+ exports.assertValidBody = assertValidBody;