create-request 1.3.0 → 1.4.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.
- package/README.md +59 -17
- package/dist/library/BaseRequest.d.ts +225 -22
- package/dist/library/index.cjs +320 -62
- package/dist/library/index.cjs.map +1 -1
- package/dist/library/index.d.ts +1 -1
- package/dist/library/index.esm.js +320 -62
- package/dist/library/index.esm.js.map +1 -1
- package/dist/library/index.esm.min.js +1 -1
- package/dist/library/index.esm.min.js.map +1 -1
- package/dist/library/index.min.cjs +1 -1
- package/dist/library/index.min.cjs.map +1 -1
- package/dist/library/types.d.ts +26 -2
- package/package.json +3 -2
|
@@ -94,6 +94,12 @@ var CacheMode;
|
|
|
94
94
|
})(CacheMode || (CacheMode = {}));
|
|
95
95
|
|
|
96
96
|
class RequestError extends Error {
|
|
97
|
+
status;
|
|
98
|
+
response;
|
|
99
|
+
url;
|
|
100
|
+
method;
|
|
101
|
+
isTimeout;
|
|
102
|
+
isAborted;
|
|
97
103
|
constructor(message, url, method, options = {}) {
|
|
98
104
|
super(message);
|
|
99
105
|
this.name = "RequestError";
|
|
@@ -192,6 +198,11 @@ class RequestError extends Error {
|
|
|
192
198
|
* Wrapper for HTTP responses with methods to transform the response data
|
|
193
199
|
*/
|
|
194
200
|
class ResponseWrapper {
|
|
201
|
+
response;
|
|
202
|
+
url;
|
|
203
|
+
method;
|
|
204
|
+
// GraphQL-specific options
|
|
205
|
+
graphQLOptions;
|
|
195
206
|
constructor(response, url, method, graphQLOptions) {
|
|
196
207
|
this.response = response;
|
|
197
208
|
this.url = url;
|
|
@@ -224,8 +235,7 @@ class ResponseWrapper {
|
|
|
224
235
|
* @throws RequestError if GraphQL response contains errors and throwOnError is enabled
|
|
225
236
|
*/
|
|
226
237
|
checkGraphQLErrors(data) {
|
|
227
|
-
|
|
228
|
-
if (!((_a = this.graphQLOptions) === null || _a === void 0 ? void 0 : _a.throwOnError) || typeof data !== "object" || data === null)
|
|
238
|
+
if (!this.graphQLOptions?.throwOnError || typeof data !== "object" || data === null)
|
|
229
239
|
return;
|
|
230
240
|
const responseData = data;
|
|
231
241
|
if (!Array.isArray(responseData.errors) || responseData.errors.length === 0)
|
|
@@ -486,7 +496,7 @@ class CsrfUtils {
|
|
|
486
496
|
return null;
|
|
487
497
|
}
|
|
488
498
|
const meta = document.querySelector(`meta[name="${metaName}"]`);
|
|
489
|
-
return
|
|
499
|
+
return meta?.getAttribute("content") || null;
|
|
490
500
|
}
|
|
491
501
|
/**
|
|
492
502
|
* Extracts CSRF token from a cookie
|
|
@@ -543,20 +553,20 @@ class CsrfUtils {
|
|
|
543
553
|
* Global configuration for create-request
|
|
544
554
|
*/
|
|
545
555
|
class Config {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
556
|
+
static instance;
|
|
557
|
+
// CSRF configuration
|
|
558
|
+
csrfHeaderName = "X-CSRF-Token";
|
|
559
|
+
xsrfCookieName = "XSRF-TOKEN";
|
|
560
|
+
xsrfHeaderName = "X-XSRF-TOKEN";
|
|
561
|
+
csrfToken = null;
|
|
562
|
+
enableAutoXsrf = true;
|
|
563
|
+
enableAntiCsrf = true; // X-Requested-With header
|
|
564
|
+
// Interceptor configuration
|
|
565
|
+
requestInterceptors = [];
|
|
566
|
+
responseInterceptors = [];
|
|
567
|
+
errorInterceptors = [];
|
|
568
|
+
nextInterceptorId = 1;
|
|
569
|
+
constructor() { }
|
|
560
570
|
/**
|
|
561
571
|
* Get the singleton instance of the Config class
|
|
562
572
|
*
|
|
@@ -839,16 +849,18 @@ class Config {
|
|
|
839
849
|
* Provides the core request building and execution capabilities.
|
|
840
850
|
*/
|
|
841
851
|
class BaseRequest {
|
|
852
|
+
url;
|
|
853
|
+
requestOptions = {
|
|
854
|
+
headers: {},
|
|
855
|
+
};
|
|
856
|
+
abortController;
|
|
857
|
+
queryParams = new URLSearchParams();
|
|
858
|
+
autoApplyCsrfProtection = true;
|
|
859
|
+
// Per-request interceptors
|
|
860
|
+
requestInterceptors = [];
|
|
861
|
+
responseInterceptors = [];
|
|
862
|
+
errorInterceptors = [];
|
|
842
863
|
constructor(url) {
|
|
843
|
-
this.requestOptions = {
|
|
844
|
-
headers: {},
|
|
845
|
-
};
|
|
846
|
-
this.queryParams = new URLSearchParams();
|
|
847
|
-
this.autoApplyCsrfProtection = true;
|
|
848
|
-
// Per-request interceptors
|
|
849
|
-
this.requestInterceptors = [];
|
|
850
|
-
this.responseInterceptors = [];
|
|
851
|
-
this.errorInterceptors = [];
|
|
852
864
|
this.url = url;
|
|
853
865
|
}
|
|
854
866
|
/**
|
|
@@ -879,7 +891,7 @@ class BaseRequest {
|
|
|
879
891
|
return Object.assign(callable, fluent);
|
|
880
892
|
}
|
|
881
893
|
validateUrl(url) {
|
|
882
|
-
if (!
|
|
894
|
+
if (!url?.trim())
|
|
883
895
|
throw new RequestError("URL cannot be empty", url, this.method);
|
|
884
896
|
if (url.includes("\0") || url.includes("\r") || url.includes("\n")) {
|
|
885
897
|
throw new RequestError("Invalid URL (control chars)", url, this.method);
|
|
@@ -889,7 +901,7 @@ class BaseRequest {
|
|
|
889
901
|
try {
|
|
890
902
|
new URL(trimmed);
|
|
891
903
|
}
|
|
892
|
-
catch
|
|
904
|
+
catch {
|
|
893
905
|
throw new RequestError(`Invalid URL: ${trimmed}`, trimmed, this.method);
|
|
894
906
|
}
|
|
895
907
|
}
|
|
@@ -957,17 +969,60 @@ class BaseRequest {
|
|
|
957
969
|
/**
|
|
958
970
|
* Configure automatic retry behavior for failed requests
|
|
959
971
|
*
|
|
960
|
-
* @param retries - Number of retry attempts before failing
|
|
972
|
+
* @param retries - Number of retry attempts before failing, or a configuration object
|
|
961
973
|
* @returns The request instance for chaining
|
|
962
|
-
* @throws RequestError if retries is not a non-negative integer
|
|
974
|
+
* @throws RequestError if retries is not a non-negative integer or invalid config
|
|
963
975
|
*
|
|
964
976
|
* @example
|
|
977
|
+
* // Simple number (backward compatible)
|
|
965
978
|
* request.withRetries(3); // Retry up to 3 times
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* // With fixed delay
|
|
982
|
+
* request.withRetries({ attempts: 3, delay: 1000 }); // Retry 3 times with 1 second delay
|
|
983
|
+
*
|
|
984
|
+
* @example
|
|
985
|
+
* // With exponential backoff function
|
|
986
|
+
* request.withRetries({
|
|
987
|
+
* attempts: 3,
|
|
988
|
+
* delay: ({ attempt }) => Math.min(1000 * Math.pow(2, attempt - 1), 10000)
|
|
989
|
+
* });
|
|
990
|
+
*
|
|
991
|
+
* @example
|
|
992
|
+
* // With delay function based on error
|
|
993
|
+
* request.withRetries({
|
|
994
|
+
* attempts: 3,
|
|
995
|
+
* delay: ({ attempt, error }) => {
|
|
996
|
+
* if (error.status === 429) return 5000; // Rate limited, wait longer
|
|
997
|
+
* return attempt * 1000; // Exponential backoff
|
|
998
|
+
* }
|
|
999
|
+
* });
|
|
966
1000
|
*/
|
|
967
1001
|
withRetries(retries) {
|
|
968
|
-
if (
|
|
969
|
-
|
|
970
|
-
|
|
1002
|
+
if (typeof retries === "number") {
|
|
1003
|
+
if (!Number.isInteger(retries) || retries < 0) {
|
|
1004
|
+
throw new RequestError(`Invalid retries: ${retries}`, this.url, this.method);
|
|
1005
|
+
}
|
|
1006
|
+
this.requestOptions.retries = retries;
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
// Validate RetryConfig
|
|
1010
|
+
if (!Number.isInteger(retries.attempts) || retries.attempts < 0) {
|
|
1011
|
+
throw new RequestError(`Invalid attempts: ${retries.attempts}`, this.url, this.method);
|
|
1012
|
+
}
|
|
1013
|
+
// Validate delay if provided
|
|
1014
|
+
if (retries.delay !== undefined) {
|
|
1015
|
+
if (typeof retries.delay === "number") {
|
|
1016
|
+
if (!Number.isFinite(retries.delay) || retries.delay < 0) {
|
|
1017
|
+
throw new RequestError(`Invalid delay: ${retries.delay}`, this.url, this.method);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
else if (typeof retries.delay !== "function") {
|
|
1021
|
+
throw new RequestError(`Invalid delay: ${typeof retries.delay}`, this.url, this.method);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
this.requestOptions.retries = retries;
|
|
1025
|
+
}
|
|
971
1026
|
return this;
|
|
972
1027
|
}
|
|
973
1028
|
/**
|
|
@@ -988,10 +1043,26 @@ class BaseRequest {
|
|
|
988
1043
|
return this;
|
|
989
1044
|
}
|
|
990
1045
|
/**
|
|
991
|
-
* Sets credentials policy
|
|
1046
|
+
* Sets the credentials policy for the request, controlling whether cookies and authentication
|
|
1047
|
+
* headers are sent with cross-origin requests.
|
|
1048
|
+
*
|
|
1049
|
+
* @param credentialsPolicy - The credentials policy to use:
|
|
1050
|
+
* - `"include"` or `CredentialsPolicy.INCLUDE`: Always send credentials (cookies, authorization headers) with the request, even for cross-origin requests.
|
|
1051
|
+
* - `"omit"` or `CredentialsPolicy.OMIT`: Never send credentials, even for same-origin requests.
|
|
1052
|
+
* - `"same-origin"` or `CredentialsPolicy.SAME_ORIGIN`: Only send credentials for same-origin requests (default behavior in most browsers).
|
|
1053
|
+
*
|
|
1054
|
+
* @returns The request instance for chaining
|
|
1055
|
+
*
|
|
992
1056
|
* @example
|
|
1057
|
+
* // Using string values
|
|
993
1058
|
* request.withCredentials("include")
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* // Using enum values
|
|
994
1062
|
* request.withCredentials(CredentialsPolicy.INCLUDE)
|
|
1063
|
+
*
|
|
1064
|
+
* @example
|
|
1065
|
+
* // Using fluent API
|
|
995
1066
|
* request.withCredentials.INCLUDE()
|
|
996
1067
|
*/
|
|
997
1068
|
get withCredentials() {
|
|
@@ -1002,28 +1073,83 @@ class BaseRequest {
|
|
|
1002
1073
|
});
|
|
1003
1074
|
}
|
|
1004
1075
|
/**
|
|
1005
|
-
* Allows providing an external AbortController to cancel the request
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1076
|
+
* Allows providing an external AbortController to cancel the request.
|
|
1077
|
+
* This is useful when you need to cancel a request from outside the request chain,
|
|
1078
|
+
*
|
|
1079
|
+
* @param controller - The AbortController to use for this request. When `controller.abort()` is called,
|
|
1080
|
+
* the request will be cancelled and throw an abort error.
|
|
1081
|
+
*
|
|
1082
|
+
* @returns The request instance for chaining
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* const controller = new AbortController();
|
|
1086
|
+
* const request = createRequest('/api/data')
|
|
1087
|
+
* .withAbortController(controller)
|
|
1088
|
+
* .getJson();
|
|
1089
|
+
*
|
|
1090
|
+
* // Later, cancel the request
|
|
1091
|
+
* controller.abort();
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* // Share abort controller across multiple requests
|
|
1095
|
+
* const controller = new AbortController();
|
|
1096
|
+
* request1.withAbortController(controller).getJson();
|
|
1097
|
+
* request2.withAbortController(controller).getJson();
|
|
1098
|
+
* // Aborting will cancel both requests
|
|
1099
|
+
* controller.abort();
|
|
1008
1100
|
*/
|
|
1009
1101
|
withAbortController(controller) {
|
|
1010
1102
|
this.abortController = controller;
|
|
1011
1103
|
return this;
|
|
1012
1104
|
}
|
|
1013
1105
|
/**
|
|
1014
|
-
* Sets the referrer for the request
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1106
|
+
* Sets the referrer URL for the request. The referrer is the URL of the page that initiated the request.
|
|
1107
|
+
* This can be used to override the default referrer that the browser would normally send.
|
|
1108
|
+
*
|
|
1109
|
+
* @param referrer - The referrer URL to send with the request. Can be:
|
|
1110
|
+
* - A full URL (e.g., "https://example.com/page")
|
|
1111
|
+
* - An empty string to omit the referrer
|
|
1112
|
+
* - A relative URL (will be resolved relative to the current page)
|
|
1113
|
+
*
|
|
1114
|
+
* @returns The request instance for chaining
|
|
1115
|
+
*
|
|
1116
|
+
* @example
|
|
1117
|
+
* request.withReferrer("https://example.com/previous-page")
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* // Omit referrer
|
|
1121
|
+
* request.withReferrer("")
|
|
1017
1122
|
*/
|
|
1018
1123
|
withReferrer(referrer) {
|
|
1019
1124
|
this.requestOptions.referrer = referrer;
|
|
1020
1125
|
return this;
|
|
1021
1126
|
}
|
|
1022
1127
|
/**
|
|
1023
|
-
* Sets referrer policy
|
|
1128
|
+
* Sets the referrer policy for the request, controlling how much referrer information
|
|
1129
|
+
* is sent with the request. This helps balance privacy and functionality.
|
|
1130
|
+
*
|
|
1131
|
+
* @param policy - The referrer policy to use:
|
|
1132
|
+
* - `"no-referrer"` or `ReferrerPolicy.NO_REFERRER`: Never send the referrer header.
|
|
1133
|
+
* - `"no-referrer-when-downgrade"` or `ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE`: Send full referrer for same-origin or HTTPS→HTTPS, omit for HTTPS→HTTP (default in most browsers).
|
|
1134
|
+
* - `"origin"` or `ReferrerPolicy.ORIGIN`: Only send the origin (scheme, host, port), not the full URL.
|
|
1135
|
+
* - `"origin-when-cross-origin"` or `ReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, only origin for cross-origin.
|
|
1136
|
+
* - `"same-origin"` or `ReferrerPolicy.SAME_ORIGIN`: Send full referrer for same-origin requests only, omit for cross-origin.
|
|
1137
|
+
* - `"strict-origin"` or `ReferrerPolicy.STRICT_ORIGIN`: Send origin for HTTPS→HTTPS or HTTP→HTTP, omit for HTTPS→HTTP.
|
|
1138
|
+
* - `"strict-origin-when-cross-origin"` or `ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, origin for cross-origin HTTPS→HTTPS, omit for HTTPS→HTTP.
|
|
1139
|
+
* - `"unsafe-url"` or `ReferrerPolicy.UNSAFE_URL`: Always send the full referrer URL (may leak sensitive information).
|
|
1140
|
+
*
|
|
1141
|
+
* @returns The request instance for chaining
|
|
1142
|
+
*
|
|
1024
1143
|
* @example
|
|
1144
|
+
* // Using string values
|
|
1025
1145
|
* request.withReferrerPolicy("no-referrer")
|
|
1146
|
+
*
|
|
1147
|
+
* @example
|
|
1148
|
+
* // Using enum values
|
|
1026
1149
|
* request.withReferrerPolicy(ReferrerPolicy.NO_REFERRER)
|
|
1150
|
+
*
|
|
1151
|
+
* @example
|
|
1152
|
+
* // Using fluent API
|
|
1027
1153
|
* request.withReferrerPolicy.NO_REFERRER()
|
|
1028
1154
|
*/
|
|
1029
1155
|
get withReferrerPolicy() {
|
|
@@ -1039,11 +1165,30 @@ class BaseRequest {
|
|
|
1039
1165
|
});
|
|
1040
1166
|
}
|
|
1041
1167
|
/**
|
|
1042
|
-
* Sets
|
|
1168
|
+
* Sets how the request handles HTTP redirects (3xx status codes).
|
|
1169
|
+
*
|
|
1170
|
+
* @param redirect - The redirect handling mode:
|
|
1171
|
+
* - `"follow"` or `RedirectMode.FOLLOW`: Automatically follow redirects. The fetch will transparently follow redirects and return the final response (default behavior).
|
|
1172
|
+
* - `"error"` or `RedirectMode.ERROR`: Treat redirects as errors. If a redirect occurs, the request will fail with an error.
|
|
1173
|
+
* - `"manual"` or `RedirectMode.MANUAL`: Return the redirect response without following it. The response will have a `type` of "opaqueredirect" and you can manually handle the redirect.
|
|
1174
|
+
*
|
|
1175
|
+
* @returns The request instance for chaining
|
|
1176
|
+
*
|
|
1043
1177
|
* @example
|
|
1178
|
+
* // Using string values
|
|
1044
1179
|
* request.withRedirect("follow")
|
|
1180
|
+
*
|
|
1181
|
+
* @example
|
|
1182
|
+
* // Using enum values
|
|
1045
1183
|
* request.withRedirect(RedirectMode.FOLLOW)
|
|
1184
|
+
*
|
|
1185
|
+
* @example
|
|
1186
|
+
* // Using fluent API
|
|
1046
1187
|
* request.withRedirect.FOLLOW()
|
|
1188
|
+
*
|
|
1189
|
+
* @example
|
|
1190
|
+
* // Fail on redirects
|
|
1191
|
+
* request.withRedirect.ERROR()
|
|
1047
1192
|
*/
|
|
1048
1193
|
get withRedirect() {
|
|
1049
1194
|
return this.createFluentSetter("redirect", {
|
|
@@ -1053,20 +1198,50 @@ class BaseRequest {
|
|
|
1053
1198
|
});
|
|
1054
1199
|
}
|
|
1055
1200
|
/**
|
|
1056
|
-
* Sets the keepalive flag for the request
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1201
|
+
* Sets the keepalive flag for the request. When enabled, the request can continue
|
|
1202
|
+
* even after the page that initiated it is closed. This is useful for analytics,
|
|
1203
|
+
* logging, or other background requests that should complete even if the user navigates away.
|
|
1204
|
+
*
|
|
1205
|
+
* @param keepalive - Whether to allow the request to outlive the page:
|
|
1206
|
+
* - `true`: The request will continue even if the page is closed or navigated away.
|
|
1207
|
+
* - `false`: The request will be cancelled if the page is closed (default).
|
|
1208
|
+
*
|
|
1209
|
+
* @returns The request instance for chaining
|
|
1210
|
+
*
|
|
1211
|
+
* @example
|
|
1212
|
+
* // Send analytics event that should complete even if user navigates away
|
|
1213
|
+
* request.withKeepAlive(true)
|
|
1059
1214
|
*/
|
|
1060
1215
|
withKeepAlive(keepalive) {
|
|
1061
1216
|
this.requestOptions.keepalive = keepalive;
|
|
1062
1217
|
return this;
|
|
1063
1218
|
}
|
|
1064
1219
|
/**
|
|
1065
|
-
* Sets
|
|
1220
|
+
* Sets the priority hint for the request, indicating to the browser how important
|
|
1221
|
+
* this request is relative to other requests. This helps the browser optimize resource loading.
|
|
1222
|
+
*
|
|
1223
|
+
* @param priority - The request priority:
|
|
1224
|
+
* - `"high"` or `RequestPriority.HIGH`: High priority - the browser should prioritize this request.
|
|
1225
|
+
* - `"low"` or `RequestPriority.LOW`: Low priority - the browser can defer this request if needed.
|
|
1226
|
+
* - `"auto"` or `RequestPriority.AUTO`: Automatic priority based on the request type (default).
|
|
1227
|
+
*
|
|
1228
|
+
* @returns The request instance for chaining
|
|
1229
|
+
*
|
|
1066
1230
|
* @example
|
|
1231
|
+
* // Using string values
|
|
1067
1232
|
* request.withPriority("high")
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* // Using enum values
|
|
1068
1236
|
* request.withPriority(RequestPriority.HIGH)
|
|
1237
|
+
*
|
|
1238
|
+
* @example
|
|
1239
|
+
* // Using fluent API
|
|
1069
1240
|
* request.withPriority.HIGH()
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* // Low priority for non-critical requests
|
|
1244
|
+
* request.withPriority.LOW()
|
|
1070
1245
|
*/
|
|
1071
1246
|
get withPriority() {
|
|
1072
1247
|
return this.createFluentSetter("priority", {
|
|
@@ -1076,22 +1251,62 @@ class BaseRequest {
|
|
|
1076
1251
|
});
|
|
1077
1252
|
}
|
|
1078
1253
|
/**
|
|
1079
|
-
* Sets the integrity hash for
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1254
|
+
* Sets the integrity hash for Subresource Integrity (SRI) verification.
|
|
1255
|
+
* This allows the browser to verify that the fetched resource hasn't been tampered with
|
|
1256
|
+
* by comparing its hash against the provided value. If the hashes don't match, the request fails.
|
|
1257
|
+
*
|
|
1258
|
+
* @param integrity - The integrity hash string in the format `"algorithm-hash"`:
|
|
1259
|
+
* - Example: `"sha256-abcdef1234567890..."` (SHA-256 hash)
|
|
1260
|
+
* - Example: `"sha384-abcdef1234567890..."` (SHA-384 hash)
|
|
1261
|
+
* - Example: `"sha512-abcdef1234567890..."` (SHA-512 hash)
|
|
1262
|
+
* - Multiple hashes can be separated by spaces: `"sha256-... sha384-..."`
|
|
1263
|
+
*
|
|
1264
|
+
* @returns The request instance for chaining
|
|
1265
|
+
*
|
|
1082
1266
|
* @example
|
|
1083
1267
|
* request.withIntegrity("sha256-abcdef1234567890...")
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* // Multiple algorithms for better compatibility
|
|
1271
|
+
* request.withIntegrity("sha256-... sha384-...")
|
|
1084
1272
|
*/
|
|
1085
1273
|
withIntegrity(integrity) {
|
|
1086
1274
|
this.requestOptions.integrity = integrity;
|
|
1087
1275
|
return this;
|
|
1088
1276
|
}
|
|
1089
1277
|
/**
|
|
1090
|
-
* Sets cache mode
|
|
1278
|
+
* Sets the cache mode for the request, controlling how the browser's HTTP cache
|
|
1279
|
+
* is used for this request.
|
|
1280
|
+
*
|
|
1281
|
+
* @param cache - The cache mode:
|
|
1282
|
+
* - `"default"` or `CacheMode.DEFAULT`: Use the browser's default cache behavior. The browser will check the cache and use it if valid, otherwise fetch from network.
|
|
1283
|
+
* - `"no-store"` or `CacheMode.NO_STORE`: Never use the cache and don't store the response in cache. Always fetch from network.
|
|
1284
|
+
* - `"reload"` or `CacheMode.RELOAD`: Bypass the cache but store the response. Always fetch from network, ignoring cached responses.
|
|
1285
|
+
* - `"no-cache"` or `CacheMode.NO_CACHE`: Check the cache but revalidate with the server. Use cached response only if server confirms it's still valid.
|
|
1286
|
+
* - `"force-cache"` or `CacheMode.FORCE_CACHE`: Use the cache if available, even if stale. Only fetch from network if not in cache.
|
|
1287
|
+
* - `"only-if-cached"` or `CacheMode.ONLY_IF_CACHED`: Only use the cache. If not in cache, return an error. Never fetch from network.
|
|
1288
|
+
*
|
|
1289
|
+
* @returns The request instance for chaining
|
|
1290
|
+
*
|
|
1091
1291
|
* @example
|
|
1292
|
+
* // Using string values
|
|
1092
1293
|
* request.withCache("no-cache")
|
|
1294
|
+
*
|
|
1295
|
+
* @example
|
|
1296
|
+
* // Using enum values
|
|
1093
1297
|
* request.withCache(CacheMode.NO_CACHE)
|
|
1298
|
+
*
|
|
1299
|
+
* @example
|
|
1300
|
+
* // Using fluent API
|
|
1094
1301
|
* request.withCache.NO_CACHE()
|
|
1302
|
+
*
|
|
1303
|
+
* @example
|
|
1304
|
+
* // Always fetch fresh data
|
|
1305
|
+
* request.withCache.RELOAD()
|
|
1306
|
+
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* // Use cache only, fail if not cached
|
|
1309
|
+
* request.withCache.ONLY_IF_CACHED()
|
|
1095
1310
|
*/
|
|
1096
1311
|
get withCache() {
|
|
1097
1312
|
const cacheOptions = {
|
|
@@ -1140,11 +1355,32 @@ class BaseRequest {
|
|
|
1140
1355
|
return this;
|
|
1141
1356
|
}
|
|
1142
1357
|
/**
|
|
1143
|
-
* Sets request mode
|
|
1358
|
+
* Sets the request mode, which determines the CORS (Cross-Origin Resource Sharing) behavior
|
|
1359
|
+
* for the request. This controls how the browser handles cross-origin requests.
|
|
1360
|
+
*
|
|
1361
|
+
* @param mode - The request mode:
|
|
1362
|
+
* - `"cors"` or `RequestMode.CORS`: Enable CORS. The browser will send CORS headers and enforce CORS rules. This is the default for most cross-origin requests.
|
|
1363
|
+
* - `"no-cors"` or `RequestMode.NO_CORS`: Disable CORS. The request is sent as a "simple" request without CORS headers. The response will be opaque (you can't read it).
|
|
1364
|
+
* - `"same-origin"` or `RequestMode.SAME_ORIGIN`: Only allow same-origin requests. Cross-origin requests will fail.
|
|
1365
|
+
* - `"navigate"` or `RequestMode.NAVIGATE`: Used for navigation requests (typically only used by the browser itself).
|
|
1366
|
+
*
|
|
1367
|
+
* @returns The request instance for chaining
|
|
1368
|
+
*
|
|
1144
1369
|
* @example
|
|
1370
|
+
* // Using string values
|
|
1145
1371
|
* request.withMode("cors")
|
|
1372
|
+
*
|
|
1373
|
+
* @example
|
|
1374
|
+
* // Using enum values
|
|
1146
1375
|
* request.withMode(RequestMode.CORS)
|
|
1376
|
+
*
|
|
1377
|
+
* @example
|
|
1378
|
+
* // Using fluent API
|
|
1147
1379
|
* request.withMode.CORS()
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* // Restrict to same-origin only
|
|
1383
|
+
* request.withMode.SAME_ORIGIN()
|
|
1148
1384
|
*/
|
|
1149
1385
|
get withMode() {
|
|
1150
1386
|
return this.createFluentSetter("mode", {
|
|
@@ -1539,7 +1775,8 @@ class BaseRequest {
|
|
|
1539
1775
|
* @throws RequestError if the request fails after all retries
|
|
1540
1776
|
*/
|
|
1541
1777
|
async executeWithRetries(url, fetchOptions) {
|
|
1542
|
-
const
|
|
1778
|
+
const retriesConfig = this.requestOptions.retries;
|
|
1779
|
+
const maxRetries = typeof retriesConfig === "number" ? retriesConfig : retriesConfig?.attempts || 0;
|
|
1543
1780
|
const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
|
|
1544
1781
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1545
1782
|
try {
|
|
@@ -1549,9 +1786,22 @@ class BaseRequest {
|
|
|
1549
1786
|
const requestError = error instanceof RequestError ? error : RequestError.networkError(url, method, error instanceof Error ? error : new Error(String(error)));
|
|
1550
1787
|
if (attempt >= maxRetries)
|
|
1551
1788
|
throw requestError;
|
|
1789
|
+
// Call onRetry callback if provided
|
|
1552
1790
|
if (this.requestOptions.onRetry) {
|
|
1553
1791
|
await this.requestOptions.onRetry({ attempt: attempt + 1, error: requestError });
|
|
1554
1792
|
}
|
|
1793
|
+
// Apply delay if configured
|
|
1794
|
+
if (typeof retriesConfig === "object" && retriesConfig.delay !== undefined) {
|
|
1795
|
+
const delay = typeof retriesConfig.delay === "function" ? retriesConfig.delay({ attempt: attempt + 1, error: requestError }) : retriesConfig.delay;
|
|
1796
|
+
// Validate delay result
|
|
1797
|
+
if (typeof delay !== "number" || !Number.isFinite(delay) || delay < 0) {
|
|
1798
|
+
throw new RequestError(`Invalid retry delay: ${delay}`, url, method);
|
|
1799
|
+
}
|
|
1800
|
+
// Wait for the delay
|
|
1801
|
+
if (delay > 0) {
|
|
1802
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1555
1805
|
}
|
|
1556
1806
|
}
|
|
1557
1807
|
// This should never happen but is needed for type safety
|
|
@@ -1668,7 +1918,7 @@ class BaseRequest {
|
|
|
1668
1918
|
// No timeout - just use external controller
|
|
1669
1919
|
if (!timeoutMs) {
|
|
1670
1920
|
return {
|
|
1671
|
-
signal: externalController
|
|
1921
|
+
signal: externalController?.signal,
|
|
1672
1922
|
cleanup: () => { },
|
|
1673
1923
|
wasTimeout: () => false,
|
|
1674
1924
|
};
|
|
@@ -1851,9 +2101,11 @@ class BaseRequest {
|
|
|
1851
2101
|
* Base class for requests that can have a body (POST, PUT, PATCH)
|
|
1852
2102
|
*/
|
|
1853
2103
|
class BodyRequest extends BaseRequest {
|
|
2104
|
+
body;
|
|
2105
|
+
bodyType;
|
|
2106
|
+
graphQLOptions = undefined;
|
|
1854
2107
|
constructor(url) {
|
|
1855
2108
|
super(url);
|
|
1856
|
-
this.graphQLOptions = undefined;
|
|
1857
2109
|
}
|
|
1858
2110
|
/**
|
|
1859
2111
|
* Sets the body of the request
|
|
@@ -1868,7 +2120,13 @@ class BodyRequest extends BaseRequest {
|
|
|
1868
2120
|
}
|
|
1869
2121
|
else if (body !== null &&
|
|
1870
2122
|
typeof body === "object" &&
|
|
1871
|
-
!(body instanceof FormData ||
|
|
2123
|
+
!(body instanceof FormData ||
|
|
2124
|
+
body instanceof Blob ||
|
|
2125
|
+
body instanceof File ||
|
|
2126
|
+
body instanceof ArrayBuffer ||
|
|
2127
|
+
ArrayBuffer.isView(body) || // Handles TypedArray and DataView
|
|
2128
|
+
body instanceof URLSearchParams ||
|
|
2129
|
+
body instanceof ReadableStream)) {
|
|
1872
2130
|
this.bodyType = BodyType.JSON;
|
|
1873
2131
|
this.setContentTypeIfNeeded("application/json");
|
|
1874
2132
|
// Validate JSON is stringifiable early
|
|
@@ -1998,9 +2256,9 @@ class BodyRequest extends BaseRequest {
|
|
|
1998
2256
|
* const data = await request.getData();
|
|
1999
2257
|
*/
|
|
2000
2258
|
class GetRequest extends BaseRequest {
|
|
2259
|
+
method = HttpMethod.GET;
|
|
2001
2260
|
constructor(url) {
|
|
2002
2261
|
super(url);
|
|
2003
|
-
this.method = HttpMethod.GET;
|
|
2004
2262
|
}
|
|
2005
2263
|
}
|
|
2006
2264
|
/**
|
|
@@ -2012,9 +2270,9 @@ class GetRequest extends BaseRequest {
|
|
|
2012
2270
|
* const response = await request.getResponse();
|
|
2013
2271
|
*/
|
|
2014
2272
|
class HeadRequest extends BaseRequest {
|
|
2273
|
+
method = HttpMethod.HEAD;
|
|
2015
2274
|
constructor(url) {
|
|
2016
2275
|
super(url);
|
|
2017
|
-
this.method = HttpMethod.HEAD;
|
|
2018
2276
|
}
|
|
2019
2277
|
}
|
|
2020
2278
|
/**
|
|
@@ -2026,9 +2284,9 @@ class HeadRequest extends BaseRequest {
|
|
|
2026
2284
|
* const response = await request.getResponse();
|
|
2027
2285
|
*/
|
|
2028
2286
|
class OptionsRequest extends BaseRequest {
|
|
2287
|
+
method = HttpMethod.OPTIONS;
|
|
2029
2288
|
constructor(url) {
|
|
2030
2289
|
super(url);
|
|
2031
|
-
this.method = HttpMethod.OPTIONS;
|
|
2032
2290
|
}
|
|
2033
2291
|
}
|
|
2034
2292
|
/**
|
|
@@ -2040,9 +2298,9 @@ class OptionsRequest extends BaseRequest {
|
|
|
2040
2298
|
* await request.getData();
|
|
2041
2299
|
*/
|
|
2042
2300
|
class DeleteRequest extends BaseRequest {
|
|
2301
|
+
method = HttpMethod.DELETE;
|
|
2043
2302
|
constructor(url) {
|
|
2044
2303
|
super(url);
|
|
2045
|
-
this.method = HttpMethod.DELETE;
|
|
2046
2304
|
}
|
|
2047
2305
|
}
|
|
2048
2306
|
/**
|
|
@@ -2055,9 +2313,9 @@ class DeleteRequest extends BaseRequest {
|
|
|
2055
2313
|
* const data = await request.getData();
|
|
2056
2314
|
*/
|
|
2057
2315
|
class PostRequest extends BodyRequest {
|
|
2316
|
+
method = HttpMethod.POST;
|
|
2058
2317
|
constructor(url) {
|
|
2059
2318
|
super(url);
|
|
2060
|
-
this.method = HttpMethod.POST;
|
|
2061
2319
|
}
|
|
2062
2320
|
}
|
|
2063
2321
|
/**
|
|
@@ -2070,9 +2328,9 @@ class PostRequest extends BodyRequest {
|
|
|
2070
2328
|
* const data = await request.getData();
|
|
2071
2329
|
*/
|
|
2072
2330
|
class PutRequest extends BodyRequest {
|
|
2331
|
+
method = HttpMethod.PUT;
|
|
2073
2332
|
constructor(url) {
|
|
2074
2333
|
super(url);
|
|
2075
|
-
this.method = HttpMethod.PUT;
|
|
2076
2334
|
}
|
|
2077
2335
|
}
|
|
2078
2336
|
/**
|
|
@@ -2085,9 +2343,9 @@ class PutRequest extends BodyRequest {
|
|
|
2085
2343
|
* const data = await request.getData();
|
|
2086
2344
|
*/
|
|
2087
2345
|
class PatchRequest extends BodyRequest {
|
|
2346
|
+
method = HttpMethod.PATCH;
|
|
2088
2347
|
constructor(url) {
|
|
2089
2348
|
super(url);
|
|
2090
|
-
this.method = HttpMethod.PATCH;
|
|
2091
2349
|
}
|
|
2092
2350
|
}
|
|
2093
2351
|
|