create-request 0.0.1-beta.2 β 0.0.1-beta.20
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 +492 -440
- package/dist/library/BaseRequest.d.ts +145 -6
- package/dist/library/BodyRequest.d.ts +13 -1
- package/dist/library/ResponseWrapper.d.ts +69 -30
- package/dist/library/enums.d.ts +37 -8
- package/dist/library/index.d.ts +15 -4
- package/dist/library/index.esm.js +1 -616
- package/dist/library/index.esm.js.map +1 -0
- package/dist/library/index.js +1 -635
- package/dist/library/index.js.map +1 -0
- package/dist/library/requestFactories.d.ts +60 -1
- package/dist/library/requestMethods.d.ts +60 -0
- package/dist/library/types/cache.d.ts +62 -0
- package/dist/library/types.d.ts +14 -3
- package/dist/library/utils/CacheManager.d.ts +82 -0
- package/dist/library/utils/CacheStorage.d.ts +49 -0
- package/dist/library/utils/Config.d.ts +125 -0
- package/dist/library/utils/CookieUtils.d.ts +9 -0
- package/dist/library/utils/CsrfUtils.d.ts +24 -0
- package/dist/library/utils/StorageProvider.d.ts +33 -0
- package/dist/library/utils/sizeUtils.d.ts +8 -0
- package/package.json +40 -13
package/README.md
CHANGED
|
@@ -1,467 +1,590 @@
|
|
|
1
1
|
# create-request
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/create-request)
|
|
4
|
-
[](https://github.com/
|
|
4
|
+
[](https://github.com/DanielAmenou/create-request/blob/main/LICENSE)
|
|
5
5
|
[](https://bundlephobia.com/package/create-request)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
6
7
|
|
|
7
8
|
`create-request` is a modern TypeScript library that transforms how you make API calls. Built as an elegant wrapper around the native Fetch API, it provides a chainable, fluent interface that dramatically reduces boilerplate while adding powerful features like automatic retries, timeout handling, and comprehensive error management.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Core Features](#core-features)
|
|
13
|
+
- [Why create-request](#why-create-request)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Basic Usage](#basic-usage)
|
|
16
|
+
- [Advanced Usage](#advanced-usage)
|
|
17
|
+
- [TypeScript Support](#typescript-support)
|
|
18
|
+
- [CSRF Protection](#csrf-protection)
|
|
19
|
+
- [Performance Considerations](#performance-considerations)
|
|
20
|
+
- [Browser Support](#browser-support)
|
|
21
|
+
- [Comparison of JavaScript HTTP Client Libraries](#comparison-of-javascript-http-client-libraries)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Core Features
|
|
25
|
+
|
|
26
|
+
- π **Performance** - Tiny bundle size with zero dependencies
|
|
27
|
+
- π§ **Error Handling** - Detailed error info with custom error class
|
|
28
|
+
- βοΈ **Chainable API** - Build and execute requests with a fluent interface
|
|
29
|
+
- β±οΈ **Timeout Support** - Set timeouts for requests with automatic aborts
|
|
30
|
+
- π‘οΈ **Type Safety** - Full TypeScript support with intelligent type inference
|
|
31
|
+
- π **Auth Helpers** - Simple methods for common authentication patterns
|
|
32
|
+
- π **Data Selection** - Extract and transform specific data from responses
|
|
33
|
+
- π **Automatic Retries** - Retry failed requests with customizable settings
|
|
34
|
+
- π **Reduced Boilerplate** - Write 60% less code for common API operations
|
|
35
|
+
- π **CSRF Protection** - Built-in safeguards against cross-site request forgery
|
|
36
|
+
- π **Response Caching** - Flexible caching system with multiple storage options
|
|
37
|
+
- π **Request Cancellation** - Abort requests on demand with AbortController integration
|
|
10
38
|
|
|
11
39
|
## Why create-request?
|
|
12
40
|
|
|
13
|
-
**
|
|
41
|
+
**API interactions often require repetitive code patterns** - handling HTTP status checks, parsing responses, managing errors, and dealing with TypeScript types. `create-request` provides a clean, efficient solution with an elegant API that separates request building from execution:
|
|
42
|
+
|
|
43
|
+
### With Regular Fetch
|
|
14
44
|
|
|
15
45
|
```typescript
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
46
|
+
async function createUser(userData) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch("https://api.example.com/users", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
Authorization: "Basic " + btoa("username:password"),
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify(userData),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
59
|
+
}
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
return data;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("Fetch error:", error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
24
67
|
```
|
|
25
68
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Write Less Code** - Accomplish in one line what might take dozens with raw fetch
|
|
29
|
-
- **Fewer Bugs** - Strong typing and smart defaults prevent common API bugs
|
|
30
|
-
- **Self-Documenting** - Chainable API clearly expresses intent for easier code reviews
|
|
31
|
-
- **Universal** - Works the same in browser and Node.js environments
|
|
32
|
-
|
|
33
|
-
## Overview
|
|
69
|
+
### With create-request
|
|
34
70
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
import create from "create-request";
|
|
73
|
+
|
|
74
|
+
function createUser(userData) {
|
|
75
|
+
return create
|
|
76
|
+
.post()
|
|
77
|
+
.withBasicAuth("username", "password")
|
|
78
|
+
.withBody(userData) // Content-Type automatically set to application/json
|
|
79
|
+
.sendTo("https://api.example.com/users")
|
|
80
|
+
.getData<User>() // Type-safe response handling
|
|
81
|
+
.catch(error => {
|
|
82
|
+
console.error("Fetch error:", error);
|
|
83
|
+
throw error;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
```
|
|
51
87
|
|
|
52
88
|
## Installation
|
|
53
89
|
|
|
54
90
|
```bash
|
|
91
|
+
# npm
|
|
55
92
|
npm install create-request
|
|
56
|
-
```
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```bash
|
|
94
|
+
# yarn
|
|
61
95
|
yarn add create-request
|
|
96
|
+
|
|
97
|
+
# pnpm
|
|
98
|
+
pnpm add create-request
|
|
62
99
|
```
|
|
63
100
|
|
|
64
|
-
##
|
|
101
|
+
## Basic Usage
|
|
65
102
|
|
|
66
|
-
###
|
|
103
|
+
### Creating Requests
|
|
67
104
|
|
|
68
105
|
```typescript
|
|
69
|
-
create
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.log(`${error.status} error from ${error.method} ${error.url}`);
|
|
80
|
-
console.log(`Was it a timeout? ${error.timeoutError}`);
|
|
81
|
-
|
|
82
|
-
// Original error data still available:
|
|
83
|
-
console.log(error.response);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
106
|
+
import create from "create-request";
|
|
107
|
+
|
|
108
|
+
// Create different request types
|
|
109
|
+
const getRequest = create.get(); // GET
|
|
110
|
+
const putRequest = create.put(); // PUT
|
|
111
|
+
const postRequest = create.post(); // POST
|
|
112
|
+
const patchRequest = create.patch(); // PATCH
|
|
113
|
+
const deleteRequest = create.del(); // DELETE
|
|
114
|
+
const headRequest = create.head(); // HEAD
|
|
115
|
+
const optionsRequest = create.options(); // OPTIONS
|
|
86
116
|
```
|
|
87
117
|
|
|
88
|
-
###
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
interface User {
|
|
92
|
-
id: number;
|
|
93
|
-
name: string;
|
|
94
|
-
email: string;
|
|
95
|
-
}
|
|
118
|
+
### Request Configuration
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
const users = await create.get()
|
|
99
|
-
.sendTo('https://api.example.com/users')
|
|
100
|
-
.json<User[]>();
|
|
120
|
+
The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
|
|
101
121
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
```typescript
|
|
123
|
+
import create, {
|
|
124
|
+
RequestPriority,
|
|
125
|
+
CredentialsPolicy,
|
|
126
|
+
RequestMode,
|
|
127
|
+
RedirectMode,
|
|
128
|
+
ReferrerPolicy,
|
|
129
|
+
} from "create-request";
|
|
130
|
+
|
|
131
|
+
// Configure request options
|
|
132
|
+
const request = create
|
|
133
|
+
.get()
|
|
134
|
+
// Basic headers
|
|
135
|
+
.withHeaders({ "X-API-Key": "abc123", "Accept-Language": "en-US" })
|
|
136
|
+
.withHeader("Custom-Header", "value") // Add a single header
|
|
137
|
+
|
|
138
|
+
// Timeout settings
|
|
139
|
+
.withTimeout(5000) // Request will abort after 5 seconds
|
|
140
|
+
|
|
141
|
+
// Automatic retry configuration
|
|
142
|
+
.withRetries(3) // Retry up to 3 times on failure
|
|
143
|
+
.onRetry(({ attempt, error }) => {
|
|
144
|
+
console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
|
|
145
|
+
// You can implement backoff strategy here
|
|
146
|
+
return new Promise(resolve => setTimeout(resolve, attempt * 1000));
|
|
147
|
+
})
|
|
105
148
|
|
|
106
|
-
|
|
149
|
+
// Authentication methods
|
|
150
|
+
.withBearerToken("your-token") // Adds Authorization: Bearer your-token
|
|
151
|
+
.withBasicAuth("username", "password") // HTTP Basic Authentication
|
|
152
|
+
.withAuthorization("auth-scheme value") // Custom authorization header
|
|
107
153
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// With create-request: One line
|
|
154
|
+
// Add a single cookie
|
|
155
|
+
.withCookie("language", "en-US")
|
|
111
156
|
|
|
112
|
-
|
|
113
|
-
.
|
|
114
|
-
|
|
115
|
-
|
|
157
|
+
// Add multiple cookies
|
|
158
|
+
.withCookies({
|
|
159
|
+
sessionId: "abc123",
|
|
160
|
+
preferences: { value: "dark-mode", secure: true },
|
|
161
|
+
tracking: { value: "enabled", sameSite: SameSitePolicy.STRICT },
|
|
116
162
|
})
|
|
117
|
-
|
|
118
|
-
|
|
163
|
+
|
|
164
|
+
// URL parameters
|
|
165
|
+
.withQueryParams({ search: "term", page: 1, limit: 20 })
|
|
166
|
+
.withQueryParam("filter", "active") // Add a single query parameter
|
|
167
|
+
|
|
168
|
+
// Request body configuration (for POST/PUT/PATCH)
|
|
169
|
+
.withContentType("application/json") // Set specific content type
|
|
170
|
+
|
|
171
|
+
// Fetch API options
|
|
172
|
+
.withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
|
|
173
|
+
.withMode(RequestMode.CORS) // Controls CORS behavior
|
|
174
|
+
.withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
|
|
175
|
+
.withReferrer("https://example.com") // Sets request referrer
|
|
176
|
+
.withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
|
|
177
|
+
.withPriority(RequestPriority.HIGH) // Sets request priority
|
|
178
|
+
.withKeepAlive(true); // Keeps connection alive after the page is unloaded
|
|
119
179
|
```
|
|
120
180
|
|
|
121
|
-
|
|
181
|
+
Each configuration method returns the request object, allowing for a fluent interface where methods can be chained together. You can configure only what you need for a specific request:
|
|
122
182
|
|
|
123
183
|
```typescript
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const newUser = await create.post()
|
|
134
|
-
.withBody({ name: 'Daniel Amenou', email: 'daniel@example.com' })
|
|
135
|
-
.withBearerToken('your-token-here')
|
|
136
|
-
.sendTo('https://api.example.com/users')
|
|
137
|
-
.json();
|
|
184
|
+
// Simple example with just what's needed
|
|
185
|
+
const searchUsers = create
|
|
186
|
+
.get()
|
|
187
|
+
.withBearerToken(userToken)
|
|
188
|
+
.withQueryParams({ q: searchTerm, limit: 20 })
|
|
189
|
+
.withTimeout(3000);
|
|
190
|
+
|
|
191
|
+
// Now execute the request
|
|
192
|
+
const users = await searchUsers.sendTo("https://api.example.com/users").getData();
|
|
138
193
|
```
|
|
139
194
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
### Request Creation
|
|
143
|
-
|
|
144
|
-
Create requests using the appropriate factory method:
|
|
195
|
+
You can also create reusable base requests with common settings:
|
|
145
196
|
|
|
146
197
|
```typescript
|
|
147
|
-
//
|
|
148
|
-
create
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Request Configuration
|
|
198
|
+
// Create base authenticated request
|
|
199
|
+
const apiBase = create
|
|
200
|
+
.get()
|
|
201
|
+
.withHeaders({
|
|
202
|
+
"X-API-Version": "1.2",
|
|
203
|
+
"Accept-Language": "en-US",
|
|
204
|
+
})
|
|
205
|
+
.withBearerToken(authToken)
|
|
206
|
+
.withTimeout(5000)
|
|
207
|
+
.withRetries(2);
|
|
160
208
|
|
|
161
|
-
|
|
209
|
+
// Use the base request for different endpoints
|
|
210
|
+
const users = await apiBase.sendTo("https://api.example.com/users").getData();
|
|
211
|
+
const products = await apiBase.sendTo("https://api.example.com/products").getData();
|
|
212
|
+
```
|
|
162
213
|
|
|
163
|
-
|
|
214
|
+
### Request Bodies (POST/PUT/PATCH)
|
|
164
215
|
|
|
165
216
|
```typescript
|
|
166
|
-
|
|
217
|
+
// JSON body (Content-Type automatically set to application/json)
|
|
218
|
+
const jsonRequest = create.post().withBody({ name: "John", age: 30 });
|
|
167
219
|
|
|
168
|
-
//
|
|
169
|
-
.
|
|
220
|
+
// String body (Content-Type automatically set to text/plain)
|
|
221
|
+
const textRequest = create.post().withBody("Plain text content");
|
|
170
222
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
region: 'us-east'
|
|
176
|
-
})
|
|
223
|
+
// Form data
|
|
224
|
+
const formData = new FormData();
|
|
225
|
+
formData.append("name", "John");
|
|
226
|
+
formData.append("file", fileBlob);
|
|
177
227
|
|
|
178
|
-
|
|
179
|
-
.withContentType('application/json')
|
|
228
|
+
const formRequest = create.post().withBody(formData);
|
|
180
229
|
|
|
181
|
-
//
|
|
182
|
-
|
|
230
|
+
// URLSearchParams (typically used for application/x-www-form-urlencoded)
|
|
231
|
+
const params = new URLSearchParams();
|
|
232
|
+
params.append("username", "john");
|
|
233
|
+
params.append("password", "secret");
|
|
183
234
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.onRetry(({ retryCount, error }) => {
|
|
187
|
-
console.log(`Retry ${retryCount} after error: ${error.message}`);
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// Use an external abort controller
|
|
191
|
-
.withAbortController(myAbortController)
|
|
192
|
-
|
|
193
|
-
// Set credentials policy - uses same-origin by default
|
|
194
|
-
.withCredentials(CredentialsPolicy.INCLUDE)
|
|
235
|
+
const formUrlEncodedRequest = create.post().withBody(params);
|
|
236
|
+
```
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
.withRedirect(RedirectMode.FOLLOW)
|
|
238
|
+
### Executing Requests
|
|
198
239
|
|
|
199
|
-
|
|
200
|
-
|
|
240
|
+
```typescript
|
|
241
|
+
// Simple execution
|
|
242
|
+
const response = await request.sendTo("https://api.example.com/endpoint");
|
|
243
|
+
|
|
244
|
+
// With direct data extraction
|
|
245
|
+
const jsonData = await request.sendTo("https://api.example.com/endpoint").getJson();
|
|
246
|
+
const textData = await request.sendTo("https://api.example.com/endpoint").getText();
|
|
247
|
+
const blobData = await request.sendTo("https://api.example.com/endpoint").getBlob();
|
|
248
|
+
const bodyStream = await request.sendTo("https://api.example.com/endpoint").getBody();
|
|
249
|
+
const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
|
|
250
|
+
|
|
251
|
+
// Using the data selector API to extract specific data
|
|
252
|
+
const userData = await request
|
|
253
|
+
.sendTo("https://api.example.com/users")
|
|
254
|
+
.getData(data => data.results.users);
|
|
255
|
+
|
|
256
|
+
// Using the data selector without a selector function just returns the full JSON response
|
|
257
|
+
const fullData = await request.sendTo("https://api.example.com/data").getData();
|
|
258
|
+
```
|
|
201
259
|
|
|
202
|
-
|
|
203
|
-
.withReferrer('https://example.com')
|
|
260
|
+
### Error Handling
|
|
204
261
|
|
|
205
|
-
|
|
206
|
-
.withKeepAlive(true)
|
|
262
|
+
All errors from requests are instances of `RequestError` with detailed information:
|
|
207
263
|
|
|
208
|
-
|
|
209
|
-
|
|
264
|
+
```typescript
|
|
265
|
+
try {
|
|
266
|
+
const data = await request.sendTo("https://api.example.com/data").getJson();
|
|
267
|
+
} catch (error) {
|
|
268
|
+
// error will always be a RequestError
|
|
269
|
+
console.log(error.message); // Error message
|
|
270
|
+
console.log(error.status); // HTTP status code (if available)
|
|
271
|
+
console.log(error.url); // Request URL
|
|
272
|
+
console.log(error.method); // HTTP method
|
|
273
|
+
console.log(error.timeoutError); // Whether it was a timeout
|
|
274
|
+
|
|
275
|
+
// Access the original response if available
|
|
276
|
+
if (error.response) {
|
|
277
|
+
// Raw Response object is available
|
|
278
|
+
console.log(error.response.status);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
210
281
|
```
|
|
211
282
|
|
|
212
|
-
|
|
283
|
+
### Caching Requests
|
|
213
284
|
|
|
214
285
|
```typescript
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.
|
|
224
|
-
|
|
286
|
+
import create, {
|
|
287
|
+
createMemoryStorage,
|
|
288
|
+
createLocalStorageStorage,
|
|
289
|
+
createSessionStorageStorage,
|
|
290
|
+
} from "create-request";
|
|
291
|
+
|
|
292
|
+
// Simple in-memory caching
|
|
293
|
+
const request = create
|
|
294
|
+
.get()
|
|
295
|
+
.withCache({
|
|
296
|
+
storage: createMemoryStorage(),
|
|
297
|
+
ttl: 60000, // 1 minute in milliseconds
|
|
298
|
+
})
|
|
299
|
+
.sendTo("https://api.example.com/data");
|
|
300
|
+
|
|
301
|
+
// Using localStorage storage
|
|
302
|
+
const request = create
|
|
303
|
+
.get()
|
|
304
|
+
.withCache({
|
|
305
|
+
storage: createLocalStorageStorage(),
|
|
306
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
307
|
+
maxSize: "1MB",
|
|
308
|
+
keyPrefix: "user-data",
|
|
309
|
+
})
|
|
310
|
+
.sendTo("https://api.example.com/users");
|
|
311
|
+
|
|
312
|
+
// Using sessionStorage for per-session caching
|
|
313
|
+
const request = create
|
|
314
|
+
.get()
|
|
315
|
+
.withCache({
|
|
316
|
+
storage: createSessionStorageStorage(),
|
|
317
|
+
maxEntries: 50,
|
|
318
|
+
varyByHeaders: ["x-api-version"],
|
|
319
|
+
})
|
|
320
|
+
.sendTo("https://api.example.com/data");
|
|
225
321
|
```
|
|
226
322
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// Basic authentication
|
|
231
|
-
.withBasicAuth('username', 'password')
|
|
323
|
+
## Advanced Usage
|
|
232
324
|
|
|
233
|
-
|
|
234
|
-
.withBearerToken('your-jwt-token')
|
|
325
|
+
### Request Reuse
|
|
235
326
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
327
|
+
```typescript
|
|
328
|
+
// Create a base authenticated request
|
|
329
|
+
const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
|
|
239
330
|
|
|
240
|
-
|
|
331
|
+
// Reuse for different endpoints
|
|
332
|
+
const users = await authRequest.sendTo("https://api.example.com/users").getJson();
|
|
333
|
+
const products = await authRequest.sendTo("https://api.example.com/products").getJson();
|
|
241
334
|
|
|
242
|
-
|
|
335
|
+
// You can also create application-wide base requests
|
|
336
|
+
const apiBase = () =>
|
|
337
|
+
create
|
|
338
|
+
.get()
|
|
339
|
+
.withHeaders({
|
|
340
|
+
"X-API-Version": "1.2",
|
|
341
|
+
"Accept-Language": "en-US",
|
|
342
|
+
})
|
|
343
|
+
.withBearerToken(getAuthToken()) // Get fresh token each time
|
|
344
|
+
.withTimeout(5000);
|
|
243
345
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
.
|
|
247
|
-
|
|
248
|
-
// String body
|
|
249
|
-
.withBody('Plain text content')
|
|
250
|
-
|
|
251
|
-
// FormData
|
|
252
|
-
const form = new FormData();
|
|
253
|
-
form.append('file', fileBlob);
|
|
254
|
-
.withBody(form)
|
|
255
|
-
|
|
256
|
-
// Blob body
|
|
257
|
-
const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
|
|
258
|
-
const request = create.post()
|
|
259
|
-
.withBody(blob)
|
|
260
|
-
.sendTo('https://api.example.com/upload');
|
|
261
|
-
|
|
262
|
-
// ArrayBuffer body
|
|
263
|
-
const buffer = new ArrayBuffer(8);
|
|
264
|
-
const request = create.post()
|
|
265
|
-
.withBody(buffer)
|
|
266
|
-
.sendTo('https://api.example.com/upload');
|
|
267
|
-
|
|
268
|
-
// ReadableStream body
|
|
269
|
-
const stream = new ReadableStream();
|
|
270
|
-
const request = create.post()
|
|
271
|
-
.withBody(stream)
|
|
272
|
-
.sendTo('https://api.example.com/upload');
|
|
346
|
+
// Use throughout your application
|
|
347
|
+
function getUsers() {
|
|
348
|
+
return apiBase().sendTo("https://api.example.com/users").getData();
|
|
349
|
+
}
|
|
273
350
|
|
|
351
|
+
function getProducts() {
|
|
352
|
+
return apiBase().sendTo("https://api.example.com/products").getData();
|
|
353
|
+
}
|
|
274
354
|
```
|
|
275
355
|
|
|
276
|
-
###
|
|
277
|
-
|
|
278
|
-
The library provides convenient methods for handling responses that can be chained directly after `sendTo()`:
|
|
356
|
+
### Request Cancellation
|
|
279
357
|
|
|
280
358
|
```typescript
|
|
281
|
-
|
|
282
|
-
const jsonData = await create.get()
|
|
283
|
-
.sendTo('https://api.example.com/data')
|
|
284
|
-
.json<MyDataType>();
|
|
285
|
-
|
|
286
|
-
// Get as text
|
|
287
|
-
const text = await create.get()
|
|
288
|
-
.sendTo('https://api.example.com/text')
|
|
289
|
-
.text();
|
|
290
|
-
|
|
291
|
-
// Get as blob
|
|
292
|
-
const blob = await create.get()
|
|
293
|
-
.sendTo('https://api.example.com/file')
|
|
294
|
-
.blob();
|
|
295
|
-
|
|
296
|
-
// Get as ArrayBuffer
|
|
297
|
-
const buffer = await create.get()
|
|
298
|
-
.sendTo('https://api.example.com/binary')
|
|
299
|
-
.arrayBuffer();
|
|
300
|
-
|
|
301
|
-
// Get as ReadableStream
|
|
302
|
-
const stream = await create.get()
|
|
303
|
-
.sendTo('https://api.example.com/stream')
|
|
304
|
-
.body();
|
|
305
|
-
|
|
306
|
-
// For more control, you can access the raw response first
|
|
307
|
-
const response = await create.get().sendTo('https://api.example.com/data');
|
|
308
|
-
const status = response.status;
|
|
309
|
-
const headers = response.headers;
|
|
310
|
-
const data = await response.json();
|
|
311
|
-
```
|
|
359
|
+
const controller = new AbortController();
|
|
312
360
|
|
|
313
|
-
|
|
361
|
+
const request = create.get().withTimeout(10000).withAbortController(controller);
|
|
314
362
|
|
|
315
|
-
|
|
363
|
+
// Later, cancel the request if needed
|
|
364
|
+
setTimeout(() => controller.abort(), 2000);
|
|
316
365
|
|
|
317
|
-
```typescript
|
|
318
366
|
try {
|
|
319
|
-
const data = await
|
|
320
|
-
.sendTo('https://api.example.com/data')
|
|
321
|
-
.json();
|
|
367
|
+
const data = await request.sendTo("https://api.example.com/slow-endpoint").getJson();
|
|
322
368
|
} catch (error) {
|
|
323
|
-
if (error
|
|
324
|
-
console.log(
|
|
325
|
-
|
|
326
|
-
console.log(
|
|
327
|
-
|
|
328
|
-
console.log(error.
|
|
329
|
-
console.log(error.response); // Raw response (if available)
|
|
369
|
+
if (error.name === "AbortError") {
|
|
370
|
+
console.log("Request was cancelled by user");
|
|
371
|
+
} else if (error.timeoutError) {
|
|
372
|
+
console.log("Request timed out");
|
|
373
|
+
} else {
|
|
374
|
+
console.log("Other error:", error.message);
|
|
330
375
|
}
|
|
331
376
|
}
|
|
332
377
|
```
|
|
333
378
|
|
|
334
|
-
|
|
379
|
+
### Data Selection
|
|
335
380
|
|
|
336
|
-
|
|
381
|
+
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
337
382
|
|
|
338
383
|
```typescript
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
.sendTo(
|
|
342
|
-
.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
384
|
+
// Extract specific properties from nested structures
|
|
385
|
+
const posts = await request
|
|
386
|
+
.sendTo("https://api.example.com/feed")
|
|
387
|
+
.getData(data => data.feed.posts);
|
|
388
|
+
|
|
389
|
+
// Transform data in the selector function
|
|
390
|
+
const usernames = await request
|
|
391
|
+
.sendTo("https://api.example.com/users")
|
|
392
|
+
.getData(data => data.users.map(user => user.username));
|
|
393
|
+
|
|
394
|
+
// Apply filtering in the selector
|
|
395
|
+
const activeUsers = await request
|
|
396
|
+
.sendTo("https://api.example.com/users")
|
|
397
|
+
.getData(data => data.users.filter(user => user.isActive));
|
|
398
|
+
|
|
399
|
+
// Combine data from complex nested structures
|
|
400
|
+
const combinedData = await request
|
|
401
|
+
.sendTo("https://api.example.com/dashboard")
|
|
402
|
+
.getData(data => ({
|
|
403
|
+
userCount: data.stats.users.total,
|
|
404
|
+
recentPosts: data.content.recent.slice(0, 5),
|
|
405
|
+
notifications: data.user.notifications.unread,
|
|
406
|
+
}));
|
|
354
407
|
```
|
|
355
408
|
|
|
356
|
-
|
|
409
|
+
When a selector fails, the error message will contain the original response data to help diagnose the issue:
|
|
357
410
|
|
|
358
411
|
```typescript
|
|
359
|
-
const controller = new AbortController();
|
|
360
|
-
|
|
361
|
-
// Pass the controller to the request
|
|
362
412
|
try {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.sendTo(
|
|
366
|
-
.
|
|
413
|
+
// This will fail if the response structure doesn't match expectations
|
|
414
|
+
const result = await request
|
|
415
|
+
.sendTo("https://api.example.com/data")
|
|
416
|
+
.getData(data => data.results.items);
|
|
367
417
|
} catch (error) {
|
|
368
|
-
|
|
418
|
+
console.error(error);
|
|
419
|
+
// Error message includes the original response data for debugging
|
|
369
420
|
}
|
|
370
|
-
|
|
371
|
-
// Cancel the request after 2 seconds
|
|
372
|
-
setTimeout(() => controller.abort(), 2000);
|
|
373
421
|
```
|
|
374
422
|
|
|
375
|
-
###
|
|
423
|
+
### Implementing Custom Storage Providers
|
|
424
|
+
|
|
425
|
+
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
376
426
|
|
|
377
427
|
```typescript
|
|
378
|
-
import {
|
|
379
|
-
|
|
380
|
-
// High priority for critical resources
|
|
381
|
-
const userProfile = await create.get()
|
|
382
|
-
.withPriority(RequestPriority.HIGH)
|
|
383
|
-
.sendTo('https://api.example.com/user/profile')
|
|
384
|
-
.json();
|
|
385
|
-
|
|
386
|
-
// Low priority for non-critical resources
|
|
387
|
-
const recommendations = await create.get()
|
|
388
|
-
.withPriority(RequestPriority.LOW)
|
|
389
|
-
.sendTo('https://api.example.com/recommendations')
|
|
390
|
-
.json();
|
|
391
|
-
```
|
|
428
|
+
import { StorageProvider } from "create-request";
|
|
392
429
|
|
|
393
|
-
|
|
430
|
+
// Create a namespaced storage provider
|
|
431
|
+
export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
432
|
+
return {
|
|
433
|
+
get(key: string): string | null {
|
|
434
|
+
return localStorage.getItem(`${namespace}:${key}`);
|
|
435
|
+
},
|
|
394
436
|
|
|
395
|
-
|
|
396
|
-
|
|
437
|
+
set(key: string, value: string): void {
|
|
438
|
+
try {
|
|
439
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
440
|
+
} catch (e) {
|
|
441
|
+
// Handle quota exceeded errors
|
|
442
|
+
console.warn("Storage quota exceeded, clearing cache");
|
|
443
|
+
this.clear();
|
|
444
|
+
try {
|
|
445
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
446
|
+
} catch (e) {
|
|
447
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
},
|
|
397
451
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
452
|
+
has(key: string): boolean {
|
|
453
|
+
return localStorage.getItem(`${namespace}:${key}`) !== null;
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
delete(key: string): void {
|
|
457
|
+
localStorage.removeItem(`${namespace}:${key}`);
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
clear(): void {
|
|
461
|
+
// Only clear items in our namespace
|
|
462
|
+
Object.keys(localStorage)
|
|
463
|
+
.filter(key => key.startsWith(`${namespace}:`))
|
|
464
|
+
.forEach(key => localStorage.removeItem(key));
|
|
465
|
+
},
|
|
466
|
+
};
|
|
408
467
|
}
|
|
409
468
|
|
|
410
|
-
//
|
|
411
|
-
const
|
|
412
|
-
.
|
|
413
|
-
.
|
|
469
|
+
// Usage:
|
|
470
|
+
const request = create
|
|
471
|
+
.get()
|
|
472
|
+
.withCache({
|
|
473
|
+
storage: createNamespacedStorage("api-cache"),
|
|
474
|
+
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
|
475
|
+
})
|
|
476
|
+
.sendTo("https://api.example.com/data");
|
|
414
477
|
```
|
|
415
478
|
|
|
416
|
-
|
|
479
|
+
## TypeScript Support
|
|
417
480
|
|
|
418
481
|
```typescript
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'analytics-opt-in': 'true'
|
|
426
|
-
})
|
|
427
|
-
.sendTo('https://api.example.com/dashboard');
|
|
482
|
+
interface User {
|
|
483
|
+
id: number;
|
|
484
|
+
name: string;
|
|
485
|
+
email: string;
|
|
486
|
+
isActive: boolean;
|
|
487
|
+
}
|
|
428
488
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
489
|
+
interface ApiResponse<T> {
|
|
490
|
+
data: T;
|
|
491
|
+
meta: {
|
|
492
|
+
total: number;
|
|
493
|
+
page: number;
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Type the full response
|
|
498
|
+
const response = await create
|
|
499
|
+
.get()
|
|
500
|
+
.sendTo("https://api.example.com/users")
|
|
501
|
+
.getJson<ApiResponse<User[]>>();
|
|
502
|
+
|
|
503
|
+
// Or use getData with type parameters
|
|
504
|
+
const users = await create
|
|
505
|
+
.get()
|
|
506
|
+
.sendTo("https://api.example.com/users")
|
|
507
|
+
.getData<ApiResponse<User[]>, User[]>(data => data.data);
|
|
508
|
+
|
|
509
|
+
// TypeScript knows the types
|
|
510
|
+
users.forEach(user => {
|
|
511
|
+
console.log(`${user.name} (${user.email}): ${user.isActive ? "Active" : "Inactive"}`);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Function with proper types
|
|
515
|
+
async function getUserById(id: number): Promise<User> {
|
|
516
|
+
return create
|
|
517
|
+
.get()
|
|
518
|
+
.withQueryParam("id", id)
|
|
519
|
+
.sendTo("https://api.example.com/users")
|
|
520
|
+
.getData<ApiResponse<User[]>, User>(data => {
|
|
521
|
+
const user = data.data[0];
|
|
522
|
+
if (!user) throw new Error(`User with ID ${id} not found`);
|
|
523
|
+
return user;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
434
526
|
```
|
|
435
527
|
|
|
436
|
-
##
|
|
528
|
+
## CSRF Protection
|
|
529
|
+
|
|
530
|
+
Cross-Site Request Forgery (CSRF) is a type of security vulnerability where unauthorized commands are executed on behalf of an authenticated user. `create-request` provides built-in protection mechanisms to help prevent CSRF attacks.
|
|
437
531
|
|
|
438
|
-
###
|
|
532
|
+
### How CSRF Protection Works
|
|
439
533
|
|
|
440
|
-
|
|
534
|
+
The library employs multiple strategies to protect against CSRF attacks:
|
|
441
535
|
|
|
442
|
-
|
|
536
|
+
1. **Automatic X-Requested-With Header**: By default, all requests include the `X-Requested-With: XMLHttpRequest` header, which helps servers identify legitimate AJAX requests.
|
|
443
537
|
|
|
444
|
-
|
|
538
|
+
2. **CSRF Token Support**: The library can automatically include CSRF tokens in request headers, which servers can validate to ensure the request came from your application.
|
|
445
539
|
|
|
446
|
-
|
|
540
|
+
3. **XSRF Cookie Reading**: For frameworks that use the double-submit cookie pattern (like Laravel, Rails, or Django), the library can automatically read XSRF tokens from cookies and include them in request headers.
|
|
447
541
|
|
|
448
|
-
|
|
542
|
+
### Global CSRF Configuration
|
|
449
543
|
|
|
450
|
-
|
|
544
|
+
You can configure CSRF settings globally for all requests:
|
|
451
545
|
|
|
452
|
-
|
|
546
|
+
```typescript
|
|
547
|
+
// Configure CSRF settings for all requests
|
|
548
|
+
create.config.setCsrfToken("your-csrf-token");
|
|
549
|
+
create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name
|
|
550
|
+
create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name
|
|
551
|
+
create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
552
|
+
create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
553
|
+
```
|
|
453
554
|
|
|
454
|
-
|
|
555
|
+
### Per-Request CSRF Settings
|
|
455
556
|
|
|
456
|
-
|
|
557
|
+
You can also configure CSRF protection on individual requests:
|
|
457
558
|
|
|
458
|
-
|
|
559
|
+
```typescript
|
|
560
|
+
// Configure CSRF for a specific request
|
|
561
|
+
const request = create
|
|
562
|
+
.post()
|
|
563
|
+
.withCsrfToken("request-specific-token") // Set a specific token
|
|
564
|
+
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
565
|
+
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
566
|
+
```
|
|
459
567
|
|
|
460
|
-
|
|
568
|
+
### Integrating with Backend Frameworks
|
|
461
569
|
|
|
462
|
-
|
|
570
|
+
Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
|
|
463
571
|
|
|
464
|
-
|
|
572
|
+
- **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
|
|
573
|
+
- **Rails**: Works with the Rails CSRF token system
|
|
574
|
+
- **Django**: Compatible with Django's CSRF middleware
|
|
575
|
+
- **Express.js + csurf**: Works with the csurf middleware token pattern
|
|
576
|
+
|
|
577
|
+
## Performance Considerations
|
|
578
|
+
|
|
579
|
+
create-request is designed to be lightweight and efficient:
|
|
580
|
+
|
|
581
|
+
- **Zero Dependencies**: No extra libraries to load
|
|
582
|
+
- **Tree-Shakable**: Only import what you need
|
|
583
|
+
- **Minimal Overhead**: Thin wrapper around the native Fetch API
|
|
584
|
+
- **Memory Efficient**: Doesn't create unnecessary objects
|
|
585
|
+
- **Cache Management**: Configurable caching to reduce network requests
|
|
586
|
+
|
|
587
|
+
## Browser Support
|
|
465
588
|
|
|
466
589
|
This library works with all browsers that support the Fetch API:
|
|
467
590
|
|
|
@@ -471,104 +594,33 @@ This library works with all browsers that support the Fetch API:
|
|
|
471
594
|
- Edge 14+
|
|
472
595
|
- Opera 29+
|
|
473
596
|
|
|
474
|
-
## Comparison
|
|
475
|
-
|
|
476
|
-
| Feature
|
|
477
|
-
|
|
478
|
-
|
|
|
479
|
-
|
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
|
|
|
484
|
-
|
|
|
485
|
-
|
|
|
486
|
-
|
|
|
487
|
-
|
|
|
488
|
-
|
|
|
489
|
-
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
### Why Not axios?est` offers:
|
|
503
|
-
|
|
504
|
-
While axios is popular, `create-request` offers:er)
|
|
505
|
-
- Smaller bundle size (~85% smaller)n
|
|
506
|
-
- More complete TypeScript integration
|
|
507
|
-
- Built-in retries without addons- Modern features like priority hints
|
|
508
|
-
- Modern features like priority hintsainable API
|
|
509
|
-
- A more intuitive, chainable API
|
|
510
|
-
|
|
511
|
-
## Real-world Examples
|
|
512
|
-
t for Authentication
|
|
513
|
-
### API Client for Authentication
|
|
514
|
-
|
|
515
|
-
```typescripta reusable authentication client
|
|
516
|
-
// Create a reusable authentication client
|
|
517
|
-
function createAuthClient(baseUrl) {
|
|
518
|
-
return {{
|
|
519
|
-
login: async (email, password) => {
|
|
520
|
-
return create.post()
|
|
521
|
-
.withBody({ email, password })eout(3000)
|
|
522
|
-
.withTimeout(3000) .sendTo(`${baseUrl}/auth/login`)
|
|
523
|
-
.sendTo(`${baseUrl}/auth/login`) .json();
|
|
524
|
-
.json();
|
|
525
|
-
},
|
|
526
|
-
en) => {
|
|
527
|
-
refreshToken: async (refreshToken) => {t()
|
|
528
|
-
return create.post()
|
|
529
|
-
.withBody({ refreshToken })ries(2)
|
|
530
|
-
.withRetries(2) .sendTo(`${baseUrl}/auth/refresh`)
|
|
531
|
-
.sendTo(`${baseUrl}/auth/refresh`) .json();
|
|
532
|
-
.json(); }
|
|
533
|
-
} };
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Use it in your applicationst authClient = createAuthClient('https://api.example.com');
|
|
538
|
-
const authClient = createAuthClient('https://api.example.com');const { token, user } = await authClient.login('user@example.com', 'password');
|
|
539
|
-
const { token, user } = await authClient.login('user@example.com', 'password');
|
|
540
|
-
```
|
|
541
|
-
ad with Progress
|
|
542
|
-
### File Upload with Progress
|
|
543
|
-
|
|
544
|
-
```typescript
|
|
545
|
-
// Upload a file with abort capabilityconst uploadFile = async (file, onProgress) => {
|
|
546
|
-
const uploadFile = async (file, onProgress) => {Controller();
|
|
547
|
-
const controller = new AbortController();
|
|
548
|
-
const form = new FormData();
|
|
549
|
-
const form = new FormData();append('file', file);
|
|
550
|
-
form.append('file', file);
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
return await create.post()
|
|
554
|
-
.withBody(form)oller(controller)
|
|
555
|
-
.withAbortController(controller)or large files
|
|
556
|
-
.withTimeout(60000) // 1 minute timeout for large filesries(2)
|
|
557
|
-
.withRetries(2)s://api.example.com/upload')
|
|
558
|
-
.sendTo('https://api.example.com/upload')
|
|
559
|
-
.json();
|
|
560
|
-
} catch (error) {f (error instanceof RequestError) {
|
|
561
|
-
if (error instanceof RequestError) {ror(`Upload failed: ${error.message}`);
|
|
562
|
-
console.error(`Upload failed: ${error.message}`); }
|
|
563
|
-
} throw error;
|
|
564
|
-
throw error;
|
|
565
|
-
}
|
|
566
|
-
turn {
|
|
567
|
-
return { abort: () => controller.abort()
|
|
568
|
-
abort: () => controller.abort();
|
|
569
|
-
};};
|
|
570
|
-
};
|
|
571
|
-
```
|
|
597
|
+
## Comparison of JavaScript HTTP Client Libraries
|
|
598
|
+
|
|
599
|
+
| Feature | create-request | Fetch | Axios | SuperAgent | Got | Ky | node-fetch | Redaxios |
|
|
600
|
+
| ------------------- | -------------- | ------ | ------- | ---------- | ------- | ------ | ---------- | -------- |
|
|
601
|
+
| **Size (min+gzip)** | ~5.1KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
|
|
602
|
+
| **Browser** | Modern | Modern | IE11+ | IE9+ | β No | Modern | β No | Modern |
|
|
603
|
+
| **Node.js** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
604
|
+
| **HTTP/2** | β
| β
| β
| β
| β
| β
| β | β |
|
|
605
|
+
| **Auto Retries** | β
| β | π οΈ | β
| β
| β
| β | β |
|
|
606
|
+
| **Cancellation** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
607
|
+
| **Caching** | β
| β | β | β
| β
| β | β | β |
|
|
608
|
+
| **Auto JSON** | β
| β | β
| β
| β
| β
| β | β
|
|
|
609
|
+
| **Timeout** | β
| β | β
| β
| β
| β
| β
| β
|
|
|
610
|
+
| **TypeScript** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
611
|
+
| **Streaming** | β
| β
| β
| β
| β
| β
| β
| β |
|
|
612
|
+
| **Progress** | β | β | β
| β
| β
| β
| β | β |
|
|
613
|
+
| **Middleware** | β | β | β
| β
| β
| β
| β | β |
|
|
614
|
+
| **Cookies** | β
| β
| π οΈ | β
| β
| β | β | β |
|
|
615
|
+
| **Pagination API** | β | β | β | β | β
| β | β | β |
|
|
616
|
+
| **Zero Deps** | β
| β
| β | β | β | β
| β
| β
|
|
|
617
|
+
| **Chainable API** | β
| β | β | β
| β
| β
| β | β |
|
|
618
|
+
| **CSRF Protection** | β
| β | β
| β | β | β | β | β |
|
|
619
|
+
|
|
620
|
+
**Notes:**
|
|
621
|
+
|
|
622
|
+
- "Modern" browser support: Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+, Opera 29+
|
|
623
|
+
- π οΈ Feature requires additional plugins or adapters (not available out-of-the-box)
|
|
572
624
|
|
|
573
625
|
## License
|
|
574
626
|
|