create-request 0.0.1-beta.2 β 0.0.1-beta.21
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 +490 -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 +47 -13
package/README.md
CHANGED
|
@@ -1,467 +1,588 @@
|
|
|
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 headRequest = create.head(); // HEAD
|
|
113
|
+
const patchRequest = create.patch(); // PATCH
|
|
114
|
+
const deleteRequest = create.del(); // DELETE
|
|
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
|
+
})
|
|
105
146
|
|
|
106
|
-
|
|
147
|
+
// Authentication methods
|
|
148
|
+
.withBearerToken("your-token") // Adds Authorization: Bearer your-token
|
|
149
|
+
.withBasicAuth("username", "password") // HTTP Basic Authentication
|
|
150
|
+
.withAuthorization("auth-scheme value") // Custom authorization header
|
|
107
151
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// With create-request: One line
|
|
152
|
+
// Add a single cookie
|
|
153
|
+
.withCookie("language", "en-US")
|
|
111
154
|
|
|
112
|
-
|
|
113
|
-
.
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
// Add multiple cookies
|
|
156
|
+
.withCookies({
|
|
157
|
+
sessionId: "abc123",
|
|
158
|
+
preferences: { value: "dark-mode", secure: true },
|
|
159
|
+
tracking: { value: "enabled", sameSite: SameSitePolicy.STRICT },
|
|
116
160
|
})
|
|
117
|
-
|
|
118
|
-
|
|
161
|
+
|
|
162
|
+
// URL parameters
|
|
163
|
+
.withQueryParams({ search: "term", page: 1, limit: 20 })
|
|
164
|
+
.withQueryParam("filter", "active") // Add a single query parameter
|
|
165
|
+
|
|
166
|
+
// Request body configuration (for POST/PUT/PATCH)
|
|
167
|
+
.withContentType("application/json") // Set specific content type
|
|
168
|
+
|
|
169
|
+
// Fetch API options
|
|
170
|
+
.withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
|
|
171
|
+
.withMode(RequestMode.CORS) // Controls CORS behavior
|
|
172
|
+
.withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
|
|
173
|
+
.withReferrer("https://example.com") // Sets request referrer
|
|
174
|
+
.withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
|
|
175
|
+
.withPriority(RequestPriority.HIGH) // Sets request priority
|
|
176
|
+
.withKeepAlive(true); // Keeps connection alive after the page is unloaded
|
|
119
177
|
```
|
|
120
178
|
|
|
121
|
-
|
|
179
|
+
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
180
|
|
|
123
181
|
```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();
|
|
182
|
+
// Simple example with just what's needed
|
|
183
|
+
const searchUsers = create
|
|
184
|
+
.get()
|
|
185
|
+
.withBearerToken(userToken)
|
|
186
|
+
.withQueryParams({ q: searchTerm, limit: 20 })
|
|
187
|
+
.withTimeout(3000);
|
|
188
|
+
|
|
189
|
+
// Now execute the request
|
|
190
|
+
const users = await searchUsers.sendTo("https://api.example.com/users").getData();
|
|
138
191
|
```
|
|
139
192
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
### Request Creation
|
|
143
|
-
|
|
144
|
-
Create requests using the appropriate factory method:
|
|
193
|
+
You can also create reusable base requests with common settings:
|
|
145
194
|
|
|
146
195
|
```typescript
|
|
147
|
-
//
|
|
148
|
-
create
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Request Configuration
|
|
196
|
+
// Create base authenticated request
|
|
197
|
+
const apiBase = create
|
|
198
|
+
.get()
|
|
199
|
+
.withHeaders({
|
|
200
|
+
"X-API-Version": "1.2",
|
|
201
|
+
"Accept-Language": "en-US",
|
|
202
|
+
})
|
|
203
|
+
.withBearerToken(authToken)
|
|
204
|
+
.withTimeout(5000)
|
|
205
|
+
.withRetries(2);
|
|
160
206
|
|
|
161
|
-
|
|
207
|
+
// Use the base request for different endpoints
|
|
208
|
+
const users = await apiBase.sendTo("https://api.example.com/users").getData();
|
|
209
|
+
const products = await apiBase.sendTo("https://api.example.com/products").getData();
|
|
210
|
+
```
|
|
162
211
|
|
|
163
|
-
|
|
212
|
+
### Request Bodies (POST/PUT/PATCH)
|
|
164
213
|
|
|
165
214
|
```typescript
|
|
166
|
-
|
|
215
|
+
// JSON body (Content-Type automatically set to application/json)
|
|
216
|
+
const jsonRequest = create.post().withBody({ name: "John", age: 30 });
|
|
167
217
|
|
|
168
|
-
//
|
|
169
|
-
.
|
|
218
|
+
// String body (Content-Type automatically set to text/plain)
|
|
219
|
+
const textRequest = create.post().withBody("Plain text content");
|
|
170
220
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
region: 'us-east'
|
|
176
|
-
})
|
|
221
|
+
// Form data
|
|
222
|
+
const formData = new FormData();
|
|
223
|
+
formData.append("name", "John");
|
|
224
|
+
formData.append("file", fileBlob);
|
|
177
225
|
|
|
178
|
-
|
|
179
|
-
.withContentType('application/json')
|
|
226
|
+
const formRequest = create.post().withBody(formData);
|
|
180
227
|
|
|
181
|
-
//
|
|
182
|
-
|
|
228
|
+
// URLSearchParams (typically used for application/x-www-form-urlencoded)
|
|
229
|
+
const params = new URLSearchParams();
|
|
230
|
+
params.append("username", "john");
|
|
231
|
+
params.append("password", "secret");
|
|
183
232
|
|
|
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)
|
|
233
|
+
const formUrlEncodedRequest = create.post().withBody(params);
|
|
234
|
+
```
|
|
195
235
|
|
|
196
|
-
|
|
197
|
-
.withRedirect(RedirectMode.FOLLOW)
|
|
236
|
+
### Executing Requests
|
|
198
237
|
|
|
199
|
-
|
|
200
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
// Simple execution
|
|
240
|
+
const response = await request.sendTo("https://api.example.com/endpoint");
|
|
241
|
+
|
|
242
|
+
// With direct data extraction
|
|
243
|
+
const jsonData = await request.sendTo("https://api.example.com/endpoint").getJson();
|
|
244
|
+
const textData = await request.sendTo("https://api.example.com/endpoint").getText();
|
|
245
|
+
const blobData = await request.sendTo("https://api.example.com/endpoint").getBlob();
|
|
246
|
+
const bodyStream = await request.sendTo("https://api.example.com/endpoint").getBody();
|
|
247
|
+
const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
|
|
248
|
+
|
|
249
|
+
// Using the data selector API to extract specific data
|
|
250
|
+
const userData = await request
|
|
251
|
+
.sendTo("https://api.example.com/users")
|
|
252
|
+
.getData(data => data.results.users);
|
|
253
|
+
|
|
254
|
+
// Using the data selector without a selector function just returns the full JSON response
|
|
255
|
+
const fullData = await request.sendTo("https://api.example.com/data").getData();
|
|
256
|
+
```
|
|
201
257
|
|
|
202
|
-
|
|
203
|
-
.withReferrer('https://example.com')
|
|
258
|
+
### Error Handling
|
|
204
259
|
|
|
205
|
-
|
|
206
|
-
.withKeepAlive(true)
|
|
260
|
+
All errors from requests are instances of `RequestError` with detailed information:
|
|
207
261
|
|
|
208
|
-
|
|
209
|
-
|
|
262
|
+
```typescript
|
|
263
|
+
try {
|
|
264
|
+
const data = await request.sendTo("https://api.example.com/data").getJson();
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// error will always be a RequestError
|
|
267
|
+
console.log(error.message); // Error message
|
|
268
|
+
console.log(error.status); // HTTP status code (if available)
|
|
269
|
+
console.log(error.url); // Request URL
|
|
270
|
+
console.log(error.method); // HTTP method
|
|
271
|
+
console.log(error.timeoutError); // Whether it was a timeout
|
|
272
|
+
|
|
273
|
+
// Access the original response if available
|
|
274
|
+
if (error.response) {
|
|
275
|
+
// Raw Response object is available
|
|
276
|
+
console.log(error.response.status);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
210
279
|
```
|
|
211
280
|
|
|
212
|
-
|
|
281
|
+
### Caching Requests
|
|
213
282
|
|
|
214
283
|
```typescript
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
.
|
|
224
|
-
|
|
284
|
+
import create, {
|
|
285
|
+
createMemoryStorage,
|
|
286
|
+
createLocalStorageStorage,
|
|
287
|
+
createSessionStorageStorage,
|
|
288
|
+
} from "create-request";
|
|
289
|
+
|
|
290
|
+
// Simple in-memory caching
|
|
291
|
+
const request = create
|
|
292
|
+
.get()
|
|
293
|
+
.withCache({
|
|
294
|
+
storage: createMemoryStorage(),
|
|
295
|
+
ttl: 60000, // 1 minute in milliseconds
|
|
296
|
+
})
|
|
297
|
+
.sendTo("https://api.example.com/data");
|
|
298
|
+
|
|
299
|
+
// Using localStorage storage
|
|
300
|
+
const request = create
|
|
301
|
+
.get()
|
|
302
|
+
.withCache({
|
|
303
|
+
storage: createLocalStorageStorage(),
|
|
304
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
305
|
+
maxSize: "1MB",
|
|
306
|
+
keyPrefix: "user-data",
|
|
307
|
+
})
|
|
308
|
+
.sendTo("https://api.example.com/users");
|
|
309
|
+
|
|
310
|
+
// Using sessionStorage for per-session caching
|
|
311
|
+
const request = create
|
|
312
|
+
.get()
|
|
313
|
+
.withCache({
|
|
314
|
+
storage: createSessionStorageStorage(),
|
|
315
|
+
maxEntries: 50,
|
|
316
|
+
varyByHeaders: ["x-api-version"],
|
|
317
|
+
})
|
|
318
|
+
.sendTo("https://api.example.com/data");
|
|
225
319
|
```
|
|
226
320
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
// Basic authentication
|
|
231
|
-
.withBasicAuth('username', 'password')
|
|
321
|
+
## Advanced Usage
|
|
232
322
|
|
|
233
|
-
|
|
234
|
-
.withBearerToken('your-jwt-token')
|
|
323
|
+
### Request Reuse
|
|
235
324
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
325
|
+
```typescript
|
|
326
|
+
// Create a base authenticated request
|
|
327
|
+
const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
|
|
239
328
|
|
|
240
|
-
|
|
329
|
+
// Reuse for different endpoints
|
|
330
|
+
const users = await authRequest.sendTo("https://api.example.com/users").getJson();
|
|
331
|
+
const products = await authRequest.sendTo("https://api.example.com/products").getJson();
|
|
241
332
|
|
|
242
|
-
|
|
333
|
+
// You can also create application-wide base requests
|
|
334
|
+
const apiBase = () =>
|
|
335
|
+
create
|
|
336
|
+
.get()
|
|
337
|
+
.withHeaders({
|
|
338
|
+
"X-API-Version": "1.2",
|
|
339
|
+
"Accept-Language": "en-US",
|
|
340
|
+
})
|
|
341
|
+
.withBearerToken(getAuthToken()) // Get fresh token each time
|
|
342
|
+
.withTimeout(5000);
|
|
243
343
|
|
|
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');
|
|
344
|
+
// Use throughout your application
|
|
345
|
+
function getUsers() {
|
|
346
|
+
return apiBase().sendTo("https://api.example.com/users").getData();
|
|
347
|
+
}
|
|
273
348
|
|
|
349
|
+
function getProducts() {
|
|
350
|
+
return apiBase().sendTo("https://api.example.com/products").getData();
|
|
351
|
+
}
|
|
274
352
|
```
|
|
275
353
|
|
|
276
|
-
###
|
|
277
|
-
|
|
278
|
-
The library provides convenient methods for handling responses that can be chained directly after `sendTo()`:
|
|
354
|
+
### Request Cancellation
|
|
279
355
|
|
|
280
356
|
```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
|
-
```
|
|
357
|
+
const controller = new AbortController();
|
|
312
358
|
|
|
313
|
-
|
|
359
|
+
const request = create.get().withTimeout(10000).withAbortController(controller);
|
|
314
360
|
|
|
315
|
-
|
|
361
|
+
// Later, cancel the request if needed
|
|
362
|
+
setTimeout(() => controller.abort(), 2000);
|
|
316
363
|
|
|
317
|
-
```typescript
|
|
318
364
|
try {
|
|
319
|
-
const data = await
|
|
320
|
-
.sendTo('https://api.example.com/data')
|
|
321
|
-
.json();
|
|
365
|
+
const data = await request.sendTo("https://api.example.com/slow-endpoint").getJson();
|
|
322
366
|
} 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)
|
|
367
|
+
if (error.name === "AbortError") {
|
|
368
|
+
console.log("Request was cancelled by user");
|
|
369
|
+
} else if (error.timeoutError) {
|
|
370
|
+
console.log("Request timed out");
|
|
371
|
+
} else {
|
|
372
|
+
console.log("Other error:", error.message);
|
|
330
373
|
}
|
|
331
374
|
}
|
|
332
375
|
```
|
|
333
376
|
|
|
334
|
-
|
|
377
|
+
### Data Selection
|
|
335
378
|
|
|
336
|
-
|
|
379
|
+
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
337
380
|
|
|
338
381
|
```typescript
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
.sendTo(
|
|
342
|
-
.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
382
|
+
// Extract specific properties from nested structures
|
|
383
|
+
const posts = await request
|
|
384
|
+
.sendTo("https://api.example.com/feed")
|
|
385
|
+
.getData(data => data.feed.posts);
|
|
386
|
+
|
|
387
|
+
// Transform data in the selector function
|
|
388
|
+
const usernames = await request
|
|
389
|
+
.sendTo("https://api.example.com/users")
|
|
390
|
+
.getData(data => data.users.map(user => user.username));
|
|
391
|
+
|
|
392
|
+
// Apply filtering in the selector
|
|
393
|
+
const activeUsers = await request
|
|
394
|
+
.sendTo("https://api.example.com/users")
|
|
395
|
+
.getData(data => data.users.filter(user => user.isActive));
|
|
396
|
+
|
|
397
|
+
// Combine data from complex nested structures
|
|
398
|
+
const combinedData = await request
|
|
399
|
+
.sendTo("https://api.example.com/dashboard")
|
|
400
|
+
.getData(data => ({
|
|
401
|
+
userCount: data.stats.users.total,
|
|
402
|
+
recentPosts: data.content.recent.slice(0, 5),
|
|
403
|
+
notifications: data.user.notifications.unread,
|
|
404
|
+
}));
|
|
354
405
|
```
|
|
355
406
|
|
|
356
|
-
|
|
407
|
+
When a selector fails, the error message will contain the original response data to help diagnose the issue:
|
|
357
408
|
|
|
358
409
|
```typescript
|
|
359
|
-
const controller = new AbortController();
|
|
360
|
-
|
|
361
|
-
// Pass the controller to the request
|
|
362
410
|
try {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.sendTo(
|
|
366
|
-
.
|
|
411
|
+
// This will fail if the response structure doesn't match expectations
|
|
412
|
+
const result = await request
|
|
413
|
+
.sendTo("https://api.example.com/data")
|
|
414
|
+
.getData(data => data.results.items);
|
|
367
415
|
} catch (error) {
|
|
368
|
-
|
|
416
|
+
console.error(error);
|
|
417
|
+
// Error message includes the original response data for debugging
|
|
369
418
|
}
|
|
370
|
-
|
|
371
|
-
// Cancel the request after 2 seconds
|
|
372
|
-
setTimeout(() => controller.abort(), 2000);
|
|
373
419
|
```
|
|
374
420
|
|
|
375
|
-
###
|
|
421
|
+
### Implementing Custom Storage Providers
|
|
422
|
+
|
|
423
|
+
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
376
424
|
|
|
377
425
|
```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
|
-
```
|
|
426
|
+
import { StorageProvider } from "create-request";
|
|
392
427
|
|
|
393
|
-
|
|
428
|
+
// Create a namespaced storage provider
|
|
429
|
+
export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
430
|
+
return {
|
|
431
|
+
get(key: string): string | null {
|
|
432
|
+
return localStorage.getItem(`${namespace}:${key}`);
|
|
433
|
+
},
|
|
394
434
|
|
|
395
|
-
|
|
396
|
-
|
|
435
|
+
set(key: string, value: string): void {
|
|
436
|
+
try {
|
|
437
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
438
|
+
} catch (e) {
|
|
439
|
+
// Handle quota exceeded errors
|
|
440
|
+
console.warn("Storage quota exceeded, clearing cache");
|
|
441
|
+
this.clear();
|
|
442
|
+
try {
|
|
443
|
+
localStorage.setItem(`${namespace}:${key}`, value);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.error("Failed to set cache item even after clearing cache");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
397
449
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
450
|
+
has(key: string): boolean {
|
|
451
|
+
return localStorage.getItem(`${namespace}:${key}`) !== null;
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
delete(key: string): void {
|
|
455
|
+
localStorage.removeItem(`${namespace}:${key}`);
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
clear(): void {
|
|
459
|
+
// Only clear items in our namespace
|
|
460
|
+
Object.keys(localStorage)
|
|
461
|
+
.filter(key => key.startsWith(`${namespace}:`))
|
|
462
|
+
.forEach(key => localStorage.removeItem(key));
|
|
463
|
+
},
|
|
464
|
+
};
|
|
408
465
|
}
|
|
409
466
|
|
|
410
|
-
//
|
|
411
|
-
const
|
|
412
|
-
.
|
|
413
|
-
.
|
|
467
|
+
// Usage:
|
|
468
|
+
const request = create
|
|
469
|
+
.get()
|
|
470
|
+
.withCache({
|
|
471
|
+
storage: createNamespacedStorage("api-cache"),
|
|
472
|
+
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
|
473
|
+
})
|
|
474
|
+
.sendTo("https://api.example.com/data");
|
|
414
475
|
```
|
|
415
476
|
|
|
416
|
-
|
|
477
|
+
## TypeScript Support
|
|
417
478
|
|
|
418
479
|
```typescript
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'analytics-opt-in': 'true'
|
|
426
|
-
})
|
|
427
|
-
.sendTo('https://api.example.com/dashboard');
|
|
480
|
+
interface User {
|
|
481
|
+
id: number;
|
|
482
|
+
name: string;
|
|
483
|
+
email: string;
|
|
484
|
+
isActive: boolean;
|
|
485
|
+
}
|
|
428
486
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
487
|
+
interface ApiResponse<T> {
|
|
488
|
+
data: T;
|
|
489
|
+
meta: {
|
|
490
|
+
total: number;
|
|
491
|
+
page: number;
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Type the full response
|
|
496
|
+
const response = await create
|
|
497
|
+
.get()
|
|
498
|
+
.sendTo("https://api.example.com/users")
|
|
499
|
+
.getJson<ApiResponse<User[]>>();
|
|
500
|
+
|
|
501
|
+
// Or use getData with type parameters
|
|
502
|
+
const users = await create
|
|
503
|
+
.get()
|
|
504
|
+
.sendTo("https://api.example.com/users")
|
|
505
|
+
.getData<ApiResponse<User[]>, User[]>(data => data.data);
|
|
506
|
+
|
|
507
|
+
// TypeScript knows the types
|
|
508
|
+
users.forEach(user => {
|
|
509
|
+
console.log(`${user.name} (${user.email}): ${user.isActive ? "Active" : "Inactive"}`);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Function with proper types
|
|
513
|
+
async function getUserById(id: number): Promise<User> {
|
|
514
|
+
return create
|
|
515
|
+
.get()
|
|
516
|
+
.withQueryParam("id", id)
|
|
517
|
+
.sendTo("https://api.example.com/users")
|
|
518
|
+
.getData<ApiResponse<User[]>, User>(data => {
|
|
519
|
+
const user = data.data[0];
|
|
520
|
+
if (!user) throw new Error(`User with ID ${id} not found`);
|
|
521
|
+
return user;
|
|
522
|
+
});
|
|
523
|
+
}
|
|
434
524
|
```
|
|
435
525
|
|
|
436
|
-
##
|
|
526
|
+
## CSRF Protection
|
|
527
|
+
|
|
528
|
+
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
529
|
|
|
438
|
-
###
|
|
530
|
+
### How CSRF Protection Works
|
|
439
531
|
|
|
440
|
-
|
|
532
|
+
The library employs multiple strategies to protect against CSRF attacks:
|
|
441
533
|
|
|
442
|
-
|
|
534
|
+
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
535
|
|
|
444
|
-
|
|
536
|
+
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
537
|
|
|
446
|
-
|
|
538
|
+
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
539
|
|
|
448
|
-
|
|
540
|
+
### Global CSRF Configuration
|
|
449
541
|
|
|
450
|
-
|
|
542
|
+
You can configure CSRF settings globally for all requests:
|
|
451
543
|
|
|
452
|
-
|
|
544
|
+
```typescript
|
|
545
|
+
// Configure CSRF settings for all requests
|
|
546
|
+
create.config.setCsrfToken("your-csrf-token");
|
|
547
|
+
create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name
|
|
548
|
+
create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name
|
|
549
|
+
create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
550
|
+
create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
551
|
+
```
|
|
453
552
|
|
|
454
|
-
|
|
553
|
+
### Per-Request CSRF Settings
|
|
455
554
|
|
|
456
|
-
|
|
555
|
+
You can also configure CSRF protection on individual requests:
|
|
457
556
|
|
|
458
|
-
|
|
557
|
+
```typescript
|
|
558
|
+
// Configure CSRF for a specific request
|
|
559
|
+
const request = create
|
|
560
|
+
.post()
|
|
561
|
+
.withCsrfToken("request-specific-token") // Set a specific token
|
|
562
|
+
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
563
|
+
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
564
|
+
```
|
|
459
565
|
|
|
460
|
-
|
|
566
|
+
### Integrating with Backend Frameworks
|
|
461
567
|
|
|
462
|
-
|
|
568
|
+
Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
|
|
463
569
|
|
|
464
|
-
|
|
570
|
+
- **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
|
|
571
|
+
- **Rails**: Works with the Rails CSRF token system
|
|
572
|
+
- **Django**: Compatible with Django's CSRF middleware
|
|
573
|
+
- **Express.js + csurf**: Works with the csurf middleware token pattern
|
|
574
|
+
|
|
575
|
+
## Performance Considerations
|
|
576
|
+
|
|
577
|
+
create-request is designed to be lightweight and efficient:
|
|
578
|
+
|
|
579
|
+
- **Zero Dependencies**: No extra libraries to load
|
|
580
|
+
- **Tree-Shakable**: Only import what you need
|
|
581
|
+
- **Minimal Overhead**: Thin wrapper around the native Fetch API
|
|
582
|
+
- **Memory Efficient**: Doesn't create unnecessary objects
|
|
583
|
+
- **Cache Management**: Configurable caching to reduce network requests
|
|
584
|
+
|
|
585
|
+
## Browser Support
|
|
465
586
|
|
|
466
587
|
This library works with all browsers that support the Fetch API:
|
|
467
588
|
|
|
@@ -471,104 +592,33 @@ This library works with all browsers that support the Fetch API:
|
|
|
471
592
|
- Edge 14+
|
|
472
593
|
- Opera 29+
|
|
473
594
|
|
|
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
|
-
```
|
|
595
|
+
## Comparison of JavaScript HTTP Client Libraries
|
|
596
|
+
|
|
597
|
+
| Feature | create-request | Fetch | Axios | SuperAgent | Got | Ky | node-fetch | Redaxios |
|
|
598
|
+
| ------------------- | -------------- | ------ | ------- | ---------- | ------- | ------ | ---------- | -------- |
|
|
599
|
+
| **Size (min+gzip)** | ~5.1KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
|
|
600
|
+
| **Browser** | Modern | Modern | IE11+ | IE9+ | β No | Modern | β No | Modern |
|
|
601
|
+
| **Node.js** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
602
|
+
| **HTTP/2** | β
| β
| β
| β
| β
| β
| β | β |
|
|
603
|
+
| **Auto Retries** | β
| β | π οΈ | β
| β
| β
| β | β |
|
|
604
|
+
| **Cancellation** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
605
|
+
| **Caching** | β
| β | β | β
| β
| β | β | β |
|
|
606
|
+
| **Auto JSON** | β
| β | β
| β
| β
| β
| β | β
|
|
|
607
|
+
| **Timeout** | β
| β | β
| β
| β
| β
| β
| β
|
|
|
608
|
+
| **TypeScript** | β
| β
| β
| β
| β
| β
| β
| β
|
|
|
609
|
+
| **Streaming** | β
| β
| β
| β
| β
| β
| β
| β |
|
|
610
|
+
| **Progress** | β | β | β
| β
| β
| β
| β | β |
|
|
611
|
+
| **Middleware** | β | β | β
| β
| β
| β
| β | β |
|
|
612
|
+
| **Cookies** | β
| β
| π οΈ | β
| β
| β | β | β |
|
|
613
|
+
| **Pagination API** | β | β | β | β | β
| β | β | β |
|
|
614
|
+
| **Zero Deps** | β
| β
| β | β | β | β
| β
| β
|
|
|
615
|
+
| **Chainable API** | β
| β | β | β
| β
| β
| β | β |
|
|
616
|
+
| **CSRF Protection** | β
| β | β
| β | β | β | β | β |
|
|
617
|
+
|
|
618
|
+
**Notes:**
|
|
619
|
+
|
|
620
|
+
- "Modern" browser support: Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+, Opera 29+
|
|
621
|
+
- π οΈ Feature requires additional plugins or adapters (not available out-of-the-box)
|
|
572
622
|
|
|
573
623
|
## License
|
|
574
624
|
|