create-request 0.0.1-beta.13 → 0.0.1-beta.15
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 +232 -222
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,23 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/create-request)
|
|
4
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
|
|
|
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
|
+
- [License](#license)
|
|
22
|
+
|
|
9
23
|
## Core Features
|
|
10
24
|
|
|
11
25
|
- 🚀 **Performance** - Tiny bundle size with zero dependencies
|
|
@@ -25,17 +39,18 @@
|
|
|
25
39
|
|
|
26
40
|
**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:
|
|
27
41
|
|
|
42
|
+
### With Regular Fetch
|
|
43
|
+
|
|
28
44
|
```typescript
|
|
29
|
-
// Before: Regular fetch
|
|
30
45
|
async function createUser(userData) {
|
|
31
46
|
try {
|
|
32
|
-
const response = await fetch(
|
|
33
|
-
method:
|
|
47
|
+
const response = await fetch("https://api.example.com/users", {
|
|
48
|
+
method: "POST",
|
|
34
49
|
headers: {
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
Authorization: "Basic " + btoa("username:password"),
|
|
37
52
|
},
|
|
38
|
-
body: JSON.stringify(userData)
|
|
53
|
+
body: JSON.stringify(userData),
|
|
39
54
|
});
|
|
40
55
|
|
|
41
56
|
if (!response.ok) {
|
|
@@ -44,22 +59,26 @@ async function createUser(userData) {
|
|
|
44
59
|
const data = await response.json();
|
|
45
60
|
return data;
|
|
46
61
|
} catch (error) {
|
|
47
|
-
console.error(
|
|
62
|
+
console.error("Fetch error:", error);
|
|
48
63
|
throw error;
|
|
49
64
|
}
|
|
50
65
|
}
|
|
66
|
+
```
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
### With create-request
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import create from "create-request";
|
|
54
72
|
|
|
55
73
|
function createUser(userData) {
|
|
56
|
-
return create
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
74
|
+
return create
|
|
75
|
+
.post()
|
|
76
|
+
.withBasicAuth("username", "password")
|
|
77
|
+
.withBody(userData) // Content-Type automatically set to application/json
|
|
78
|
+
.sendTo("https://api.example.com/users")
|
|
79
|
+
.getData<User>() // Type-safe response handling
|
|
61
80
|
.catch(error => {
|
|
62
|
-
console.error(
|
|
81
|
+
console.error("Fetch error:", error);
|
|
63
82
|
throw error;
|
|
64
83
|
});
|
|
65
84
|
}
|
|
@@ -68,13 +87,14 @@ function createUser(userData) {
|
|
|
68
87
|
## Installation
|
|
69
88
|
|
|
70
89
|
```bash
|
|
90
|
+
# npm
|
|
71
91
|
npm install create-request
|
|
72
|
-
```
|
|
73
92
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```bash
|
|
93
|
+
# yarn
|
|
77
94
|
yarn add create-request
|
|
95
|
+
|
|
96
|
+
# pnpm
|
|
97
|
+
pnpm add create-request
|
|
78
98
|
```
|
|
79
99
|
|
|
80
100
|
## Basic Usage
|
|
@@ -82,16 +102,16 @@ yarn add create-request
|
|
|
82
102
|
### Creating Requests
|
|
83
103
|
|
|
84
104
|
```typescript
|
|
85
|
-
import create from
|
|
105
|
+
import create from "create-request";
|
|
86
106
|
|
|
87
107
|
// Create different request types
|
|
88
|
-
const getRequest = create.get();
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const deleteRequest = create.del();
|
|
93
|
-
const
|
|
94
|
-
const optionsRequest = create.options();
|
|
108
|
+
const getRequest = create.get(); // GET
|
|
109
|
+
const putRequest = create.put(); // PUT
|
|
110
|
+
const postRequest = create.post(); // POST
|
|
111
|
+
const patchRequest = create.patch(); // PATCH
|
|
112
|
+
const deleteRequest = create.del(); // DELETE
|
|
113
|
+
const headRequest = create.head(); // HEAD
|
|
114
|
+
const optionsRequest = create.options(); // OPTIONS
|
|
95
115
|
```
|
|
96
116
|
|
|
97
117
|
### Request Configuration
|
|
@@ -99,13 +119,14 @@ const optionsRequest = create.options();
|
|
|
99
119
|
The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
|
|
100
120
|
|
|
101
121
|
```typescript
|
|
102
|
-
import create, { RequestPriority, CredentialsPolicy, RequestMode,
|
|
122
|
+
import create, { RequestPriority, CredentialsPolicy, RequestMode, RedirectMode, ReferrerPolicy } from "create-request";
|
|
103
123
|
|
|
104
124
|
// Configure request options
|
|
105
|
-
const request = create
|
|
125
|
+
const request = create
|
|
126
|
+
.get()
|
|
106
127
|
// Basic headers
|
|
107
|
-
.withHeaders({
|
|
108
|
-
.withHeader(
|
|
128
|
+
.withHeaders({ "X-API-Key": "abc123", "Accept-Language": "en-US" })
|
|
129
|
+
.withHeader("Custom-Header", "value") // Add a single header
|
|
109
130
|
|
|
110
131
|
// Timeout settings
|
|
111
132
|
.withTimeout(5000) // Request will abort after 5 seconds
|
|
@@ -114,141 +135,101 @@ const request = create.get()
|
|
|
114
135
|
.withRetries(3) // Retry up to 3 times on failure
|
|
115
136
|
.onRetry(({ attempt, error }) => {
|
|
116
137
|
console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
|
|
138
|
+
// You can implement backoff strategy here
|
|
139
|
+
return new Promise(resolve => setTimeout(resolve, attempt * 1000));
|
|
117
140
|
})
|
|
118
141
|
|
|
119
142
|
// Authentication methods
|
|
120
|
-
.withBearerToken(
|
|
121
|
-
.withBasicAuth(
|
|
143
|
+
.withBearerToken("your-token") // Adds Authorization: Bearer your-token
|
|
144
|
+
.withBasicAuth("username", "password") // HTTP Basic Authentication
|
|
122
145
|
|
|
123
146
|
// URL parameters
|
|
124
|
-
.withQueryParams({ search:
|
|
125
|
-
.withQueryParam(
|
|
147
|
+
.withQueryParams({ search: "term", page: 1, limit: 20 })
|
|
148
|
+
.withQueryParam("filter", "active") // Add a single query parameter
|
|
126
149
|
|
|
127
150
|
// Request body configuration (for POST/PUT/PATCH)
|
|
128
|
-
.withContentType(
|
|
151
|
+
.withContentType("application/json") // Set specific content type
|
|
129
152
|
|
|
130
153
|
// Fetch API options
|
|
131
154
|
.withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
|
|
132
155
|
.withMode(RequestMode.CORS) // Controls CORS behavior
|
|
133
156
|
.withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
|
|
134
|
-
.withReferrer(
|
|
157
|
+
.withReferrer("https://example.com") // Sets request referrer
|
|
135
158
|
.withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
|
|
136
|
-
.withPriority(RequestPriority.HIGH) // Sets request priority
|
|
159
|
+
.withPriority(RequestPriority.HIGH); // Sets request priority
|
|
137
160
|
```
|
|
138
161
|
|
|
139
162
|
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:
|
|
140
163
|
|
|
141
164
|
```typescript
|
|
142
165
|
// Simple example with just what's needed
|
|
143
|
-
const searchUsers = create.get()
|
|
144
|
-
.withBearerToken(userToken)
|
|
145
|
-
.withQueryParams({ q: searchTerm, limit: 20 })
|
|
146
|
-
.withTimeout(3000);
|
|
166
|
+
const searchUsers = create.get().withBearerToken(userToken).withQueryParams({ q: searchTerm, limit: 20 }).withTimeout(3000);
|
|
147
167
|
|
|
148
168
|
// Now execute the request
|
|
149
|
-
const users = await searchUsers.sendTo(
|
|
169
|
+
const users = await searchUsers.sendTo("https://api.example.com/users").getData();
|
|
150
170
|
```
|
|
151
171
|
|
|
152
172
|
You can also create reusable base requests with common settings:
|
|
153
173
|
|
|
154
174
|
```typescript
|
|
155
175
|
// Create base authenticated request
|
|
156
|
-
const apiBase = create
|
|
176
|
+
const apiBase = create
|
|
177
|
+
.get()
|
|
157
178
|
.withHeaders({
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
"X-API-Version": "1.2",
|
|
180
|
+
"Accept-Language": "en-US",
|
|
160
181
|
})
|
|
161
182
|
.withBearerToken(authToken)
|
|
162
183
|
.withTimeout(5000)
|
|
163
184
|
.withRetries(2);
|
|
164
185
|
|
|
165
186
|
// Use the base request for different endpoints
|
|
166
|
-
const users = await apiBase.sendTo(
|
|
167
|
-
const products = await apiBase.sendTo(
|
|
187
|
+
const users = await apiBase.sendTo("https://api.example.com/users").getData();
|
|
188
|
+
const products = await apiBase.sendTo("https://api.example.com/products").getData();
|
|
168
189
|
```
|
|
169
190
|
|
|
170
191
|
### Request Bodies (POST/PUT/PATCH)
|
|
171
192
|
|
|
172
193
|
```typescript
|
|
173
|
-
// JSON body
|
|
174
|
-
const jsonRequest = create.post()
|
|
175
|
-
.withBody({ name: 'John', age: 30 });
|
|
194
|
+
// JSON body (Content-Type automatically set to application/json)
|
|
195
|
+
const jsonRequest = create.post().withBody({ name: "John", age: 30 });
|
|
176
196
|
|
|
177
|
-
// String body
|
|
178
|
-
const textRequest = create.post()
|
|
179
|
-
.withBody('Plain text content');
|
|
197
|
+
// String body (Content-Type automatically set to text/plain)
|
|
198
|
+
const textRequest = create.post().withBody("Plain text content");
|
|
180
199
|
|
|
181
200
|
// Form data
|
|
182
201
|
const formData = new FormData();
|
|
183
|
-
formData.append(
|
|
184
|
-
formData.append(
|
|
202
|
+
formData.append("name", "John");
|
|
203
|
+
formData.append("file", fileBlob);
|
|
185
204
|
|
|
186
|
-
const formRequest = create.post()
|
|
187
|
-
|
|
205
|
+
const formRequest = create.post().withBody(formData);
|
|
206
|
+
|
|
207
|
+
// URLSearchParams (typically used for application/x-www-form-urlencoded)
|
|
208
|
+
const params = new URLSearchParams();
|
|
209
|
+
params.append("username", "john");
|
|
210
|
+
params.append("password", "secret");
|
|
211
|
+
|
|
212
|
+
const formUrlEncodedRequest = create.post().withBody(params);
|
|
188
213
|
```
|
|
189
214
|
|
|
190
215
|
### Executing Requests
|
|
191
216
|
|
|
192
217
|
```typescript
|
|
193
218
|
// Simple execution
|
|
194
|
-
const response = await request.sendTo(
|
|
219
|
+
const response = await request.sendTo("https://api.example.com/endpoint");
|
|
195
220
|
|
|
196
221
|
// With direct data extraction
|
|
197
|
-
const jsonData = await request.sendTo(
|
|
198
|
-
const textData = await request.sendTo(
|
|
199
|
-
const blobData = await request.sendTo(
|
|
200
|
-
const bodyStream = await request.sendTo(
|
|
201
|
-
const arrayBuffer = await request.sendTo(
|
|
222
|
+
const jsonData = await request.sendTo("https://api.example.com/endpoint").getJson();
|
|
223
|
+
const textData = await request.sendTo("https://api.example.com/endpoint").getText();
|
|
224
|
+
const blobData = await request.sendTo("https://api.example.com/endpoint").getBlob();
|
|
225
|
+
const bodyStream = await request.sendTo("https://api.example.com/endpoint").getBody();
|
|
226
|
+
const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
|
|
202
227
|
|
|
203
228
|
// Using the data selector API to extract specific data
|
|
204
|
-
const userData = await request.sendTo(
|
|
205
|
-
.getData(data => data.results.users);
|
|
229
|
+
const userData = await request.sendTo("https://api.example.com/users").getData(data => data.results.users);
|
|
206
230
|
|
|
207
231
|
// Using the data selector without a selector function just returns the full JSON response
|
|
208
|
-
const fullData = await request.sendTo(
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Data Selection
|
|
212
|
-
|
|
213
|
-
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// Extract specific properties from nested structures
|
|
217
|
-
const posts = await request
|
|
218
|
-
.sendTo('https://api.example.com/feed')
|
|
219
|
-
.getData(data => data.feed.posts);
|
|
220
|
-
|
|
221
|
-
// Transform data in the selector function
|
|
222
|
-
const usernames = await request
|
|
223
|
-
.sendTo('https://api.example.com/users')
|
|
224
|
-
.getData(data => data.users.map(user => user.username));
|
|
225
|
-
|
|
226
|
-
// Apply filtering in the selector
|
|
227
|
-
const activeUsers = await request
|
|
228
|
-
.sendTo('https://api.example.com/users')
|
|
229
|
-
.getData(data => data.users.filter(user => user.isActive));
|
|
230
|
-
|
|
231
|
-
// Combine data from complex nested structures
|
|
232
|
-
const combinedData = await request
|
|
233
|
-
.sendTo('https://api.example.com/dashboard')
|
|
234
|
-
.getData(data => ({
|
|
235
|
-
userCount: data.stats.users.total,
|
|
236
|
-
recentPosts: data.content.recent.slice(0, 5),
|
|
237
|
-
notifications: data.user.notifications.unread
|
|
238
|
-
}));
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
When a selector fails, the error message will contain the original response data to help diagnose the issue:
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
try {
|
|
245
|
-
const result = await request
|
|
246
|
-
.sendTo('https://api.example.com/data')
|
|
247
|
-
.getData(data => data.results.items); // Will fail if structure is different
|
|
248
|
-
} catch (error) {
|
|
249
|
-
console.error(error);
|
|
250
|
-
// Error message includes the original response data
|
|
251
|
-
}
|
|
232
|
+
const fullData = await request.sendTo("https://api.example.com/data").getData();
|
|
252
233
|
```
|
|
253
234
|
|
|
254
235
|
### Error Handling
|
|
@@ -257,13 +238,13 @@ All errors from requests are instances of `RequestError` with detailed informati
|
|
|
257
238
|
|
|
258
239
|
```typescript
|
|
259
240
|
try {
|
|
260
|
-
const data = await request.sendTo(
|
|
241
|
+
const data = await request.sendTo("https://api.example.com/data").getJson();
|
|
261
242
|
} catch (error) {
|
|
262
243
|
// error will always be a RequestError
|
|
263
|
-
console.log(error.message);
|
|
264
|
-
console.log(error.status);
|
|
265
|
-
console.log(error.url);
|
|
266
|
-
console.log(error.method);
|
|
244
|
+
console.log(error.message); // Error message
|
|
245
|
+
console.log(error.status); // HTTP status code (if available)
|
|
246
|
+
console.log(error.url); // Request URL
|
|
247
|
+
console.log(error.method); // HTTP method
|
|
267
248
|
console.log(error.timeoutError); // Whether it was a timeout
|
|
268
249
|
|
|
269
250
|
// Access the original response if available
|
|
@@ -277,34 +258,37 @@ try {
|
|
|
277
258
|
### Caching Requests
|
|
278
259
|
|
|
279
260
|
```typescript
|
|
280
|
-
import create, { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from
|
|
261
|
+
import create, { createMemoryStorage, createLocalStorageStorage, createSessionStorageStorage } from "create-request";
|
|
281
262
|
|
|
282
263
|
// Simple in-memory caching
|
|
283
|
-
const request = create
|
|
264
|
+
const request = create
|
|
265
|
+
.get()
|
|
284
266
|
.withCache({
|
|
285
267
|
storage: createMemoryStorage(),
|
|
286
|
-
ttl: 60000 // 1 minute in milliseconds
|
|
268
|
+
ttl: 60000, // 1 minute in milliseconds
|
|
287
269
|
})
|
|
288
|
-
.sendTo(
|
|
270
|
+
.sendTo("https://api.example.com/data");
|
|
289
271
|
|
|
290
272
|
// Using localStorage storage
|
|
291
|
-
const request = create
|
|
273
|
+
const request = create
|
|
274
|
+
.get()
|
|
292
275
|
.withCache({
|
|
293
276
|
storage: createLocalStorageStorage(),
|
|
294
277
|
ttl: 5 * 60 * 1000, // 5 minutes
|
|
295
|
-
maxSize:
|
|
296
|
-
keyPrefix:
|
|
278
|
+
maxSize: "1MB",
|
|
279
|
+
keyPrefix: "user-data",
|
|
297
280
|
})
|
|
298
|
-
.sendTo(
|
|
281
|
+
.sendTo("https://api.example.com/users");
|
|
299
282
|
|
|
300
283
|
// Using sessionStorage for per-session caching
|
|
301
|
-
const request = create
|
|
284
|
+
const request = create
|
|
285
|
+
.get()
|
|
302
286
|
.withCache({
|
|
303
287
|
storage: createSessionStorageStorage(),
|
|
304
288
|
maxEntries: 50,
|
|
305
|
-
varyByHeaders: [
|
|
289
|
+
varyByHeaders: ["x-api-version"],
|
|
306
290
|
})
|
|
307
|
-
.sendTo(
|
|
291
|
+
.sendTo("https://api.example.com/data");
|
|
308
292
|
```
|
|
309
293
|
|
|
310
294
|
## Advanced Usage
|
|
@@ -313,14 +297,31 @@ const request = create.get()
|
|
|
313
297
|
|
|
314
298
|
```typescript
|
|
315
299
|
// Create a base authenticated request
|
|
316
|
-
const authRequest = create.get()
|
|
317
|
-
.withBearerToken('token123')
|
|
318
|
-
.withTimeout(5000)
|
|
319
|
-
.withRetries(2);
|
|
300
|
+
const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
|
|
320
301
|
|
|
321
302
|
// Reuse for different endpoints
|
|
322
|
-
const users = await authRequest.sendTo(
|
|
323
|
-
const products = await authRequest.sendTo(
|
|
303
|
+
const users = await authRequest.sendTo("https://api.example.com/users").getJson();
|
|
304
|
+
const products = await authRequest.sendTo("https://api.example.com/products").getJson();
|
|
305
|
+
|
|
306
|
+
// You can also create application-wide base requests
|
|
307
|
+
const apiBase = () =>
|
|
308
|
+
create
|
|
309
|
+
.get()
|
|
310
|
+
.withHeaders({
|
|
311
|
+
"X-API-Version": "1.2",
|
|
312
|
+
"Accept-Language": "en-US",
|
|
313
|
+
})
|
|
314
|
+
.withBearerToken(getAuthToken()) // Get fresh token each time
|
|
315
|
+
.withTimeout(5000);
|
|
316
|
+
|
|
317
|
+
// Use throughout your application
|
|
318
|
+
function getUsers() {
|
|
319
|
+
return apiBase().sendTo("https://api.example.com/users").getData();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getProducts() {
|
|
323
|
+
return apiBase().sendTo("https://api.example.com/products").getData();
|
|
324
|
+
}
|
|
324
325
|
```
|
|
325
326
|
|
|
326
327
|
### Request Cancellation
|
|
@@ -328,95 +329,56 @@ const products = await authRequest.sendTo('https://api.example.com/products').ge
|
|
|
328
329
|
```typescript
|
|
329
330
|
const controller = new AbortController();
|
|
330
331
|
|
|
331
|
-
const request = create.get()
|
|
332
|
-
.withTimeout(10000)
|
|
333
|
-
.withAbortController(controller);
|
|
332
|
+
const request = create.get().withTimeout(10000).withAbortController(controller);
|
|
334
333
|
|
|
335
334
|
// Later, cancel the request if needed
|
|
336
335
|
setTimeout(() => controller.abort(), 2000);
|
|
337
336
|
|
|
338
337
|
try {
|
|
339
|
-
const data = await request.sendTo(
|
|
338
|
+
const data = await request.sendTo("https://api.example.com/slow-endpoint").getJson();
|
|
340
339
|
} catch (error) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
interface User {
|
|
350
|
-
id: number;
|
|
351
|
-
name: string;
|
|
352
|
-
email: string;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
interface ApiResponse {
|
|
356
|
-
users: User[];
|
|
357
|
-
pagination: {
|
|
358
|
-
total: number;
|
|
359
|
-
page: number;
|
|
340
|
+
if (error.name === "AbortError") {
|
|
341
|
+
console.log("Request was cancelled by user");
|
|
342
|
+
} else if (error.timeoutError) {
|
|
343
|
+
console.log("Request timed out");
|
|
344
|
+
} else {
|
|
345
|
+
console.log("Other error:", error.message);
|
|
360
346
|
}
|
|
361
347
|
}
|
|
348
|
+
```
|
|
362
349
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Type the full response
|
|
366
|
-
const response = await userRequest
|
|
367
|
-
.sendTo('https://api.example.com/users')
|
|
368
|
-
.getJson<ApiResponse>();
|
|
369
|
-
|
|
370
|
-
// Or use getData with type parameters
|
|
371
|
-
const users = await userRequest
|
|
372
|
-
.sendTo('https://api.example.com/users')
|
|
373
|
-
.getData<ApiResponse, User[]>(data => data.users);
|
|
350
|
+
### Data Selection
|
|
374
351
|
|
|
375
|
-
|
|
376
|
-
const fullData = await userRequest
|
|
377
|
-
.sendTo('https://api.example.com/users')
|
|
378
|
-
.getData<ApiResponse>();
|
|
352
|
+
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
379
353
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
354
|
+
```typescript
|
|
355
|
+
// Extract specific properties from nested structures
|
|
356
|
+
const posts = await request.sendTo("https://api.example.com/feed").getData(data => data.feed.posts);
|
|
383
357
|
|
|
384
|
-
|
|
358
|
+
// Transform data in the selector function
|
|
359
|
+
const usernames = await request.sendTo("https://api.example.com/users").getData(data => data.users.map(user => user.username));
|
|
385
360
|
|
|
386
|
-
|
|
387
|
-
const
|
|
361
|
+
// Apply filtering in the selector
|
|
362
|
+
const activeUsers = await request.sendTo("https://api.example.com/users").getData(data => data.users.filter(user => user.isActive));
|
|
388
363
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
console.log('Data:', data);
|
|
396
|
-
})
|
|
397
|
-
.catch(error => {
|
|
398
|
-
console.error('Error:', error.message);
|
|
399
|
-
});
|
|
364
|
+
// Combine data from complex nested structures
|
|
365
|
+
const combinedData = await request.sendTo("https://api.example.com/dashboard").getData(data => ({
|
|
366
|
+
userCount: data.stats.users.total,
|
|
367
|
+
recentPosts: data.content.recent.slice(0, 5),
|
|
368
|
+
notifications: data.user.notifications.unread,
|
|
369
|
+
}));
|
|
400
370
|
```
|
|
401
371
|
|
|
402
|
-
|
|
372
|
+
When a selector fails, the error message will contain the original response data to help diagnose the issue:
|
|
403
373
|
|
|
404
374
|
```typescript
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Include user ID from authorization header in the cache key
|
|
413
|
-
const authHeader = headers?.['Authorization'] || '';
|
|
414
|
-
const userMatch = authHeader.match(/User-(\d+)/);
|
|
415
|
-
const userId = userMatch ? userMatch[1] : 'anonymous';
|
|
416
|
-
return `${method}:${userId}:${url}`;
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
.sendTo('https://api.example.com/data');
|
|
375
|
+
try {
|
|
376
|
+
// This will fail if the response structure doesn't match expectations
|
|
377
|
+
const result = await request.sendTo("https://api.example.com/data").getData(data => data.results.items);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error(error);
|
|
380
|
+
// Error message includes the original response data for debugging
|
|
381
|
+
}
|
|
420
382
|
```
|
|
421
383
|
|
|
422
384
|
### Implementing Custom Storage Providers
|
|
@@ -424,9 +386,9 @@ const request = create.get()
|
|
|
424
386
|
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
425
387
|
|
|
426
388
|
```typescript
|
|
427
|
-
import { StorageProvider } from
|
|
389
|
+
import { StorageProvider } from "create-request";
|
|
428
390
|
|
|
429
|
-
// Create a
|
|
391
|
+
// Create a namespaced storage provider
|
|
430
392
|
export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
431
393
|
return {
|
|
432
394
|
get(key: string): string | null {
|
|
@@ -461,26 +423,65 @@ export function createNamespacedStorage(namespace: string): StorageProvider {
|
|
|
461
423
|
Object.keys(localStorage)
|
|
462
424
|
.filter(key => key.startsWith(`${namespace}:`))
|
|
463
425
|
.forEach(key => localStorage.removeItem(key));
|
|
464
|
-
}
|
|
426
|
+
},
|
|
465
427
|
};
|
|
466
428
|
}
|
|
467
429
|
|
|
468
430
|
// Usage:
|
|
469
|
-
const request = create
|
|
431
|
+
const request = create
|
|
432
|
+
.get()
|
|
470
433
|
.withCache({
|
|
471
|
-
storage: createNamespacedStorage(
|
|
472
|
-
ttl: 24 * 60 * 60 * 1000 // 24 hours
|
|
434
|
+
storage: createNamespacedStorage("api-cache"),
|
|
435
|
+
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
|
473
436
|
})
|
|
474
|
-
.sendTo(
|
|
437
|
+
.sendTo("https://api.example.com/data");
|
|
475
438
|
```
|
|
476
439
|
|
|
477
|
-
|
|
440
|
+
## TypeScript Support
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
interface User {
|
|
444
|
+
id: number;
|
|
445
|
+
name: string;
|
|
446
|
+
email: string;
|
|
447
|
+
isActive: boolean;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
interface ApiResponse<T> {
|
|
451
|
+
data: T;
|
|
452
|
+
meta: {
|
|
453
|
+
total: number;
|
|
454
|
+
page: number;
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Type the full response
|
|
459
|
+
const response = await create.get().sendTo("https://api.example.com/users").getJson<ApiResponse<User[]>>();
|
|
478
460
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
461
|
+
// Or use getData with type parameters
|
|
462
|
+
const users = await create
|
|
463
|
+
.get()
|
|
464
|
+
.sendTo("https://api.example.com/users")
|
|
465
|
+
.getData<ApiResponse<User[]>, User[]>(data => data.data);
|
|
466
|
+
|
|
467
|
+
// TypeScript knows the types
|
|
468
|
+
users.forEach(user => {
|
|
469
|
+
console.log(`${user.name} (${user.email}): ${user.isActive ? "Active" : "Inactive"}`);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Function with proper types
|
|
473
|
+
async function getUserById(id: number): Promise<User> {
|
|
474
|
+
return create
|
|
475
|
+
.get()
|
|
476
|
+
.withQueryParam("id", id)
|
|
477
|
+
.sendTo("https://api.example.com/users")
|
|
478
|
+
.getData<ApiResponse<User[]>, User>(data => {
|
|
479
|
+
const user = data.data[0];
|
|
480
|
+
if (!user) throw new Error(`User with ID ${id} not found`);
|
|
481
|
+
return user;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
```
|
|
484
485
|
|
|
485
486
|
## CSRF Protection
|
|
486
487
|
|
|
@@ -502,9 +503,9 @@ You can configure CSRF settings globally for all requests:
|
|
|
502
503
|
|
|
503
504
|
```typescript
|
|
504
505
|
// Configure CSRF settings for all requests
|
|
505
|
-
create.config.setCsrfToken(
|
|
506
|
-
create.config.setCsrfHeaderName(
|
|
507
|
-
create.config.setXsrfCookieName(
|
|
506
|
+
create.config.setCsrfToken("your-csrf-token");
|
|
507
|
+
create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name
|
|
508
|
+
create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name
|
|
508
509
|
create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
509
510
|
create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
510
511
|
```
|
|
@@ -515,8 +516,9 @@ You can also configure CSRF protection on individual requests:
|
|
|
515
516
|
|
|
516
517
|
```typescript
|
|
517
518
|
// Configure CSRF for a specific request
|
|
518
|
-
const request = create
|
|
519
|
-
.
|
|
519
|
+
const request = create
|
|
520
|
+
.post()
|
|
521
|
+
.withCsrfToken("request-specific-token") // Set a specific token
|
|
520
522
|
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
521
523
|
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
522
524
|
```
|
|
@@ -530,7 +532,15 @@ Most modern frameworks support CSRF protection out of the box. The library works
|
|
|
530
532
|
- **Django**: Compatible with Django's CSRF middleware
|
|
531
533
|
- **Express.js + csurf**: Works with the csurf middleware token pattern
|
|
532
534
|
|
|
533
|
-
|
|
535
|
+
## Performance Considerations
|
|
536
|
+
|
|
537
|
+
create-request is designed to be lightweight and efficient:
|
|
538
|
+
|
|
539
|
+
- **Zero Dependencies**: No extra libraries to load
|
|
540
|
+
- **Tree-Shakable**: Only import what you need
|
|
541
|
+
- **Minimal Overhead**: Thin wrapper around the native Fetch API
|
|
542
|
+
- **Memory Efficient**: Doesn't create unnecessary objects
|
|
543
|
+
- **Cache Management**: Configurable caching to reduce network requests
|
|
534
544
|
|
|
535
545
|
## Browser Support
|
|
536
546
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-request",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.15",
|
|
4
4
|
"description": "A modern, chainable wrapper for fetch with automatic retries, timeouts, comprehensive error handling, and first-class TypeScript support",
|
|
5
5
|
"main": "dist/library/index.js",
|
|
6
6
|
"module": "dist/library/index.esm.js",
|