create-request 0.0.1-beta.22 → 0.0.1-beta.24
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 +366 -216
- package/dist/library/BaseRequest.d.ts +207 -51
- package/dist/library/BodyRequest.d.ts +25 -2
- package/dist/library/RequestError.d.ts +5 -2
- package/dist/library/ResponseWrapper.d.ts +17 -24
- package/dist/library/enums.d.ts +0 -11
- package/dist/library/index.cjs +2109 -0
- package/dist/library/index.cjs.map +1 -0
- package/dist/library/index.d.ts +2 -6
- package/dist/library/index.esm.js +2097 -1
- package/dist/library/index.esm.js.map +1 -1
- package/dist/library/index.esm.min.js +1 -0
- package/dist/library/index.esm.min.js.map +1 -0
- package/dist/library/index.min.cjs +1 -0
- package/dist/library/index.min.cjs.map +1 -0
- package/dist/library/requestFactories.d.ts +32 -30
- package/dist/library/requestMethods.d.ts +25 -18
- package/dist/library/types.d.ts +43 -4
- package/dist/library/utils/Config.d.ts +96 -0
- package/package.json +35 -12
- package/dist/library/index.js +0 -1
- package/dist/library/index.js.map +0 -1
- package/dist/library/types/cache.d.ts +0 -62
- package/dist/library/utils/CacheManager.d.ts +0 -82
- package/dist/library/utils/CacheStorage.d.ts +0 -49
- package/dist/library/utils/StorageProvider.d.ts +0 -33
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
- [Installation](#installation)
|
|
15
15
|
- [Basic Usage](#basic-usage)
|
|
16
16
|
- [Advanced Usage](#advanced-usage)
|
|
17
|
+
- [GraphQL Support](#graphql-support)
|
|
18
|
+
- [Interceptors](#interceptors)
|
|
17
19
|
- [TypeScript Support](#typescript-support)
|
|
18
20
|
- [CSRF Protection](#csrf-protection)
|
|
19
21
|
- [Performance Considerations](#performance-considerations)
|
|
@@ -33,8 +35,9 @@
|
|
|
33
35
|
- 🔁 **Automatic Retries** - Retry failed requests with customizable settings
|
|
34
36
|
- 📉 **Reduced Boilerplate** - Write 60% less code for common API operations
|
|
35
37
|
- 🔒 **CSRF Protection** - Built-in safeguards against cross-site request forgery
|
|
36
|
-
- 📝 **Response Caching** - Flexible caching system with multiple storage options
|
|
37
38
|
- 🛑 **Request Cancellation** - Abort requests on demand with AbortController integration
|
|
39
|
+
- 🔌 **Interceptors** - Global and per-request interceptors for requests, responses, and errors
|
|
40
|
+
- 🔷 **GraphQL Support** - Built-in GraphQL query and mutation helpers
|
|
38
41
|
|
|
39
42
|
## Why create-request?
|
|
40
43
|
|
|
@@ -73,10 +76,9 @@ import create from "create-request";
|
|
|
73
76
|
|
|
74
77
|
function createUser(userData) {
|
|
75
78
|
return create
|
|
76
|
-
.post()
|
|
79
|
+
.post("https://api.example.com/users")
|
|
77
80
|
.withBasicAuth("username", "password")
|
|
78
81
|
.withBody(userData) // Content-Type automatically set to application/json
|
|
79
|
-
.sendTo("https://api.example.com/users")
|
|
80
82
|
.getData<User>() // Type-safe response handling
|
|
81
83
|
.catch(error => {
|
|
82
84
|
console.error("Fetch error:", error);
|
|
@@ -105,14 +107,14 @@ pnpm add create-request
|
|
|
105
107
|
```typescript
|
|
106
108
|
import create from "create-request";
|
|
107
109
|
|
|
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
|
|
110
|
+
// Create different request types with URL
|
|
111
|
+
const getRequest = create.get("https://api.example.com/users"); // GET
|
|
112
|
+
const putRequest = create.put("https://api.example.com/users/1"); // PUT
|
|
113
|
+
const postRequest = create.post("https://api.example.com/users"); // POST
|
|
114
|
+
const headRequest = create.head("https://api.example.com/users/1"); // HEAD
|
|
115
|
+
const patchRequest = create.patch("https://api.example.com/users/1"); // PATCH
|
|
116
|
+
const deleteRequest = create.del("https://api.example.com/users/1"); // DELETE
|
|
117
|
+
const optionsRequest = create.options("https://api.example.com/users"); // OPTIONS
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
### Request Configuration
|
|
@@ -123,14 +125,14 @@ The library provides a comprehensive set of configuration methods that can be ch
|
|
|
123
125
|
import create, {
|
|
124
126
|
RequestPriority,
|
|
125
127
|
CredentialsPolicy,
|
|
126
|
-
RequestMode,
|
|
127
128
|
RedirectMode,
|
|
128
129
|
ReferrerPolicy,
|
|
130
|
+
SameSitePolicy,
|
|
129
131
|
} from "create-request";
|
|
130
132
|
|
|
131
133
|
// Configure request options
|
|
132
134
|
const request = create
|
|
133
|
-
.get()
|
|
135
|
+
.get("https://api.example.com/users")
|
|
134
136
|
// Basic headers
|
|
135
137
|
.withHeaders({ "X-API-Key": "abc123", "Accept-Language": "en-US" })
|
|
136
138
|
.withHeader("Custom-Header", "value") // Add a single header
|
|
@@ -159,20 +161,22 @@ const request = create
|
|
|
159
161
|
tracking: { value: "enabled", sameSite: SameSitePolicy.STRICT },
|
|
160
162
|
})
|
|
161
163
|
|
|
162
|
-
// URL parameters
|
|
163
|
-
.withQueryParams({ search: "term", page: 1, limit: 20 })
|
|
164
|
+
// URL parameters (supports arrays, null/undefined filtering, and all types)
|
|
165
|
+
.withQueryParams({ search: "term", page: 1, limit: 20, tags: ["js", "ts"] })
|
|
164
166
|
.withQueryParam("filter", "active") // Add a single query parameter
|
|
167
|
+
.withQueryParam("ids", [1, 2, 3]) // Array values create multiple query params
|
|
165
168
|
|
|
166
169
|
// Request body configuration (for POST/PUT/PATCH)
|
|
167
170
|
.withContentType("application/json") // Set specific content type
|
|
168
171
|
|
|
169
172
|
// Fetch API options
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
.
|
|
173
|
+
// Note: These methods support three styles - Fluent API (shown below), Enum-based (e.g., .withMode(RequestMode.CORS)), or String-based (e.g., .withMode("cors"))
|
|
174
|
+
.withCredentials.INCLUDE() // Includes cookies with cross-origin requests
|
|
175
|
+
.withMode.CORS() // Controls CORS behavior
|
|
176
|
+
.withRedirect.FOLLOW() // Controls redirect behavior (follow, error, manual)
|
|
173
177
|
.withReferrer("https://example.com") // Sets request referrer
|
|
174
|
-
.withReferrerPolicy
|
|
175
|
-
.withPriority
|
|
178
|
+
.withReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE() // Controls referrer policy
|
|
179
|
+
.withPriority.HIGH() // Sets request priority
|
|
176
180
|
.withKeepAlive(true); // Keeps connection alive after the page is unloaded
|
|
177
181
|
```
|
|
178
182
|
|
|
@@ -180,79 +184,152 @@ Each configuration method returns the request object, allowing for a fluent inte
|
|
|
180
184
|
|
|
181
185
|
```typescript
|
|
182
186
|
// Simple example with just what's needed
|
|
183
|
-
const
|
|
184
|
-
.get()
|
|
187
|
+
const users = await create
|
|
188
|
+
.get("https://api.example.com/users")
|
|
185
189
|
.withBearerToken(userToken)
|
|
186
190
|
.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();
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
You can also create reusable base requests with common settings:
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
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);
|
|
206
|
-
|
|
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();
|
|
191
|
+
.withTimeout(3000)
|
|
192
|
+
.getData();
|
|
210
193
|
```
|
|
211
194
|
|
|
212
195
|
### Request Bodies (POST/PUT/PATCH)
|
|
213
196
|
|
|
214
197
|
```typescript
|
|
215
198
|
// JSON body (Content-Type automatically set to application/json)
|
|
216
|
-
const jsonRequest = create
|
|
199
|
+
const jsonRequest = create
|
|
200
|
+
.post("https://api.example.com/users")
|
|
201
|
+
.withBody({ name: "John", age: 30 });
|
|
217
202
|
|
|
218
203
|
// String body (Content-Type automatically set to text/plain)
|
|
219
|
-
const textRequest = create
|
|
204
|
+
const textRequest = create
|
|
205
|
+
.post("https://api.example.com/users")
|
|
206
|
+
.withBody("Plain text content");
|
|
220
207
|
|
|
221
208
|
// Form data
|
|
222
209
|
const formData = new FormData();
|
|
223
210
|
formData.append("name", "John");
|
|
224
211
|
formData.append("file", fileBlob);
|
|
225
212
|
|
|
226
|
-
const formRequest = create.post().withBody(formData);
|
|
213
|
+
const formRequest = create.post("https://api.example.com/users").withBody(formData);
|
|
227
214
|
|
|
228
215
|
// URLSearchParams (typically used for application/x-www-form-urlencoded)
|
|
229
216
|
const params = new URLSearchParams();
|
|
230
217
|
params.append("username", "john");
|
|
231
218
|
params.append("password", "secret");
|
|
232
219
|
|
|
233
|
-
const formUrlEncodedRequest = create.post().withBody(params);
|
|
220
|
+
const formUrlEncodedRequest = create.post("https://api.example.com/login").withBody(params);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### GraphQL Requests
|
|
224
|
+
|
|
225
|
+
The library provides built-in support for GraphQL queries and mutations:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// GraphQL query without variables
|
|
229
|
+
const users = await create
|
|
230
|
+
.post("https://api.example.com/graphql")
|
|
231
|
+
.withGraphQL("query { users { id name email } }")
|
|
232
|
+
.getJson();
|
|
233
|
+
|
|
234
|
+
// GraphQL query with variables
|
|
235
|
+
const user = await create
|
|
236
|
+
.post("https://api.example.com/graphql")
|
|
237
|
+
.withGraphQL("query GetUser($id: ID!) { user(id: $id) { name email } }", { id: "123" })
|
|
238
|
+
.getJson();
|
|
239
|
+
|
|
240
|
+
// GraphQL mutation
|
|
241
|
+
const result = await create
|
|
242
|
+
.post("https://api.example.com/graphql")
|
|
243
|
+
.withGraphQL(
|
|
244
|
+
"mutation CreateUser($name: String!) { createUser(name: $name) { id name } }",
|
|
245
|
+
{ name: "John Doe" }
|
|
246
|
+
)
|
|
247
|
+
.getJson();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `withGraphQL` method automatically:
|
|
251
|
+
|
|
252
|
+
- Formats the body as JSON with `query` and optional `variables` properties
|
|
253
|
+
- Sets `Content-Type` to `application/json`
|
|
254
|
+
- Trims whitespace from the query string
|
|
255
|
+
- Validates the query is non-empty
|
|
256
|
+
- Validates variables are a plain object (not arrays or null)
|
|
257
|
+
|
|
258
|
+
### Query Parameters Advanced Features
|
|
259
|
+
|
|
260
|
+
The library supports advanced query parameter handling:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Array values create multiple query params with the same key
|
|
264
|
+
const request = create.get("https://api.example.com/search").withQueryParams({
|
|
265
|
+
tags: ["javascript", "typescript", "node"], // ?tags=javascript&tags=typescript&tags=node
|
|
266
|
+
page: 1,
|
|
267
|
+
active: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Null and undefined values are automatically filtered out
|
|
271
|
+
const filtered = create.get("https://api.example.com/users").withQueryParams({
|
|
272
|
+
name: "John",
|
|
273
|
+
age: null, // Ignored
|
|
274
|
+
email: undefined, // Ignored
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Supports all JavaScript types (strings, numbers, booleans, arrays)
|
|
278
|
+
const typed = create.get("https://api.example.com/data").withQueryParams({
|
|
279
|
+
page: 1, // Number
|
|
280
|
+
active: true, // Boolean
|
|
281
|
+
tags: ["js", "ts"], // Array
|
|
282
|
+
name: "John", // String
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Merge with existing query params in URL
|
|
286
|
+
const merged = create
|
|
287
|
+
.get("https://api.example.com/users?existing=value")
|
|
288
|
+
.withQueryParams({ new: "param" }); // Both existing and new params included
|
|
234
289
|
```
|
|
235
290
|
|
|
236
291
|
### Executing Requests
|
|
237
292
|
|
|
238
293
|
```typescript
|
|
239
|
-
//
|
|
240
|
-
const response = await
|
|
294
|
+
// Get the full response
|
|
295
|
+
const response = await create.get("https://api.example.com/endpoint").getResponse();
|
|
241
296
|
|
|
242
297
|
// With direct data extraction
|
|
243
|
-
const jsonData = await
|
|
244
|
-
const textData = await
|
|
245
|
-
const blobData = await
|
|
246
|
-
const bodyStream = await
|
|
247
|
-
const arrayBuffer = await request.sendTo("https://api.example.com/endpoint").getArrayBuffer();
|
|
298
|
+
const jsonData = await create.get("https://api.example.com/endpoint").getJson();
|
|
299
|
+
const textData = await create.get("https://api.example.com/endpoint").getText();
|
|
300
|
+
const blobData = await create.get("https://api.example.com/endpoint").getBlob();
|
|
301
|
+
const bodyStream = await create.get("https://api.example.com/endpoint").getBody();
|
|
248
302
|
|
|
249
303
|
// Using the data selector API to extract specific data
|
|
250
|
-
const userData = await
|
|
251
|
-
.
|
|
304
|
+
const userData = await create
|
|
305
|
+
.get("https://api.example.com/users")
|
|
252
306
|
.getData(data => data.results.users);
|
|
253
307
|
|
|
254
308
|
// Using the data selector without a selector function just returns the full JSON response
|
|
255
|
-
const fullData = await
|
|
309
|
+
const fullData = await create.get("https://api.example.com/data").getData();
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### ResponseWrapper Properties
|
|
313
|
+
|
|
314
|
+
When you use `getResponse()`, you get a `ResponseWrapper` object that provides convenient access to response properties and methods:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
const response = await create.get("https://api.example.com/users").getResponse();
|
|
318
|
+
|
|
319
|
+
// Access response properties directly
|
|
320
|
+
console.log(response.status); // HTTP status code (e.g., 200)
|
|
321
|
+
console.log(response.statusText); // Status text (e.g., "OK")
|
|
322
|
+
console.log(response.ok); // Boolean: true if status is 200-299
|
|
323
|
+
console.log(response.headers); // Headers object
|
|
324
|
+
console.log(response.url); // Request URL
|
|
325
|
+
console.log(response.method); // HTTP method
|
|
326
|
+
console.log(response.raw); // Raw Response object from fetch
|
|
327
|
+
|
|
328
|
+
// Use wrapper methods for body parsing
|
|
329
|
+
const json = await response.getJson();
|
|
330
|
+
const text = await response.getText();
|
|
331
|
+
const blob = await response.getBlob();
|
|
332
|
+
const stream = response.getBody(); // ReadableStream or null
|
|
256
333
|
```
|
|
257
334
|
|
|
258
335
|
### Error Handling
|
|
@@ -261,14 +338,15 @@ All errors from requests are instances of `RequestError` with detailed informati
|
|
|
261
338
|
|
|
262
339
|
```typescript
|
|
263
340
|
try {
|
|
264
|
-
const data = await
|
|
341
|
+
const data = await create.get("https://api.example.com/data").getJson();
|
|
265
342
|
} catch (error) {
|
|
266
343
|
// error will always be a RequestError
|
|
267
344
|
console.log(error.message); // Error message
|
|
268
345
|
console.log(error.status); // HTTP status code (if available)
|
|
269
346
|
console.log(error.url); // Request URL
|
|
270
347
|
console.log(error.method); // HTTP method
|
|
271
|
-
console.log(error.
|
|
348
|
+
console.log(error.isTimeout); // Whether it was a timeout
|
|
349
|
+
console.log(error.isAborted); // Whether it was aborted/cancelled
|
|
272
350
|
|
|
273
351
|
// Access the original response if available
|
|
274
352
|
if (error.response) {
|
|
@@ -278,77 +356,186 @@ try {
|
|
|
278
356
|
}
|
|
279
357
|
```
|
|
280
358
|
|
|
281
|
-
|
|
359
|
+
## Advanced Usage
|
|
282
360
|
|
|
283
|
-
|
|
284
|
-
import create, {
|
|
285
|
-
createMemoryStorage,
|
|
286
|
-
createLocalStorageStorage,
|
|
287
|
-
createSessionStorageStorage,
|
|
288
|
-
} from "create-request";
|
|
361
|
+
### GraphQL Support
|
|
289
362
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
363
|
+
`create-request` provides first-class support for GraphQL with the `withGraphQL` method:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Simple query
|
|
367
|
+
const query = "query { user { name email } }";
|
|
368
|
+
const data = await create
|
|
369
|
+
.post("https://api.example.com/graphql")
|
|
370
|
+
.withGraphQL(query)
|
|
371
|
+
.getJson();
|
|
372
|
+
|
|
373
|
+
// Query with variables
|
|
374
|
+
const queryWithVars = "query GetUser($id: ID!) { user(id: $id) { name email } }";
|
|
375
|
+
const user = await create
|
|
376
|
+
.post("https://api.example.com/graphql")
|
|
377
|
+
.withGraphQL(queryWithVars, { id: "123" })
|
|
378
|
+
.getJson();
|
|
379
|
+
|
|
380
|
+
// Mutation
|
|
381
|
+
const mutation =
|
|
382
|
+
"mutation CreateUser($name: String!) { createUser(name: $name) { id name } }";
|
|
383
|
+
const result = await create
|
|
384
|
+
.post("https://api.example.com/graphql")
|
|
385
|
+
.withGraphQL(mutation, { name: "John Doe" })
|
|
386
|
+
.getJson();
|
|
387
|
+
|
|
388
|
+
// Complex variables with nested objects
|
|
389
|
+
const complexQuery =
|
|
390
|
+
"query SearchUsers($filters: UserFilters!) { users(filters: $filters) { id name } }";
|
|
391
|
+
const results = await create
|
|
392
|
+
.post("https://api.example.com/graphql")
|
|
393
|
+
.withGraphQL(complexQuery, {
|
|
394
|
+
filters: {
|
|
395
|
+
name: "John",
|
|
396
|
+
age: 30,
|
|
397
|
+
tags: ["active", "verified"],
|
|
398
|
+
metadata: {
|
|
399
|
+
source: "web",
|
|
400
|
+
verified: true,
|
|
401
|
+
},
|
|
402
|
+
},
|
|
296
403
|
})
|
|
297
|
-
.
|
|
404
|
+
.getJson();
|
|
405
|
+
```
|
|
298
406
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
407
|
+
### Interceptors
|
|
408
|
+
|
|
409
|
+
Interceptors allow you to modify requests, transform responses, or handle errors globally or per-request. This is perfect for adding authentication tokens, logging, error recovery, and more.
|
|
410
|
+
|
|
411
|
+
#### Global Interceptors
|
|
412
|
+
|
|
413
|
+
Global interceptors apply to all requests:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Add a global request interceptor (modify all requests)
|
|
417
|
+
const requestInterceptorId = create.config.addRequestInterceptor(config => {
|
|
418
|
+
// Add auth token to all requests
|
|
419
|
+
config.headers["Authorization"] = `Bearer ${getToken()}`;
|
|
420
|
+
// Modify URL, headers, body, etc.
|
|
421
|
+
return config;
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Add a global response interceptor (transform all responses)
|
|
425
|
+
const responseInterceptorId = create.config.addResponseInterceptor(response => {
|
|
426
|
+
console.log(`Response received: ${response.status}`);
|
|
427
|
+
// Transform or modify the response
|
|
428
|
+
return response;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Add a global error interceptor (handle all errors)
|
|
432
|
+
const errorInterceptorId = create.config.addErrorInterceptor(error => {
|
|
433
|
+
console.error("Request failed:", error.message);
|
|
434
|
+
// Can throw to propagate error, or return ResponseWrapper to recover
|
|
435
|
+
throw error;
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Remove interceptors when no longer needed
|
|
439
|
+
create.config.removeRequestInterceptor(requestInterceptorId);
|
|
440
|
+
create.config.removeResponseInterceptor(responseInterceptorId);
|
|
441
|
+
create.config.removeErrorInterceptor(errorInterceptorId);
|
|
442
|
+
|
|
443
|
+
// Clear all interceptors at once
|
|
444
|
+
create.config.clearInterceptors();
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Per-Request Interceptors
|
|
448
|
+
|
|
449
|
+
Per-request interceptors apply only to a specific request:
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// Request interceptor - modify request configuration
|
|
453
|
+
const data = await create
|
|
454
|
+
.get("https://api.example.com/users")
|
|
455
|
+
.withRequestInterceptor(config => {
|
|
456
|
+
config.headers["X-Custom-Header"] = "value";
|
|
457
|
+
config.url = "https://api.example.com/modified-url"; // Can modify URL
|
|
458
|
+
return config;
|
|
307
459
|
})
|
|
308
|
-
.
|
|
460
|
+
.getJson();
|
|
461
|
+
|
|
462
|
+
// Response interceptor - transform response
|
|
463
|
+
const transformed = await create
|
|
464
|
+
.get("https://api.example.com/users")
|
|
465
|
+
.withResponseInterceptor(response => {
|
|
466
|
+
console.log(`Got response with status ${response.status}`);
|
|
467
|
+
return response;
|
|
468
|
+
})
|
|
469
|
+
.getJson();
|
|
309
470
|
|
|
310
|
-
//
|
|
311
|
-
const
|
|
312
|
-
.get()
|
|
313
|
-
.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
471
|
+
// Error interceptor - handle or recover from errors
|
|
472
|
+
const recovered = await create
|
|
473
|
+
.get("https://api.example.com/users")
|
|
474
|
+
.withErrorInterceptor(error => {
|
|
475
|
+
// Option 1: Throw to propagate error
|
|
476
|
+
throw error;
|
|
477
|
+
|
|
478
|
+
// Option 2: Return a ResponseWrapper to recover from error
|
|
479
|
+
// return new ResponseWrapper(fallbackResponse, error.url, error.method);
|
|
317
480
|
})
|
|
318
|
-
.
|
|
481
|
+
.getJson();
|
|
319
482
|
```
|
|
320
483
|
|
|
321
|
-
|
|
484
|
+
#### Interceptor Execution Order
|
|
485
|
+
|
|
486
|
+
Interceptors execute in a specific order:
|
|
322
487
|
|
|
323
|
-
|
|
488
|
+
1. **Request interceptors**: Global interceptors run first (in registration order), then per-request interceptors (in registration order)
|
|
489
|
+
2. **Response interceptors**: Per-request interceptors run first (in registration order), then global interceptors (in reverse registration order)
|
|
490
|
+
3. **Error interceptors**: Per-request interceptors run first (in registration order), then global interceptors (in reverse registration order)
|
|
324
491
|
|
|
325
492
|
```typescript
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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);
|
|
343
|
-
|
|
344
|
-
// Use throughout your application
|
|
345
|
-
function getUsers() {
|
|
346
|
-
return apiBase().sendTo("https://api.example.com/users").getData();
|
|
347
|
-
}
|
|
493
|
+
// Request: Global 1 → Global 2 → Per-request 1 → Per-request 2
|
|
494
|
+
// Response: Per-request 1 → Per-request 2 → Global 2 → Global 1
|
|
495
|
+
const data = await create
|
|
496
|
+
.get("https://api.example.com/users")
|
|
497
|
+
.withRequestInterceptor(() => console.log("Per-request 1"))
|
|
498
|
+
.withRequestInterceptor(() => console.log("Per-request 2"))
|
|
499
|
+
.getJson();
|
|
500
|
+
```
|
|
348
501
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
502
|
+
#### Advanced Interceptor Patterns
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// Short-circuit request with early response
|
|
506
|
+
const cached = await create
|
|
507
|
+
.get("https://api.example.com/users")
|
|
508
|
+
.withRequestInterceptor(() => {
|
|
509
|
+
// Return early response from cache
|
|
510
|
+
return new Response(JSON.stringify(cachedData), {
|
|
511
|
+
status: 200,
|
|
512
|
+
headers: { "Content-Type": "application/json" },
|
|
513
|
+
});
|
|
514
|
+
})
|
|
515
|
+
.getJson();
|
|
516
|
+
|
|
517
|
+
// Recover from error with fallback
|
|
518
|
+
const fallback = await create
|
|
519
|
+
.get("https://api.example.com/users")
|
|
520
|
+
.withErrorInterceptor(error => {
|
|
521
|
+
// Return fallback response instead of throwing
|
|
522
|
+
const fallbackResponse = new Response(JSON.stringify({ users: [] }), {
|
|
523
|
+
status: 200,
|
|
524
|
+
headers: { "Content-Type": "application/json" },
|
|
525
|
+
});
|
|
526
|
+
return new ResponseWrapper(fallbackResponse, error.url, error.method);
|
|
527
|
+
})
|
|
528
|
+
.getJson();
|
|
529
|
+
|
|
530
|
+
// Async interceptors
|
|
531
|
+
const asyncData = await create
|
|
532
|
+
.get("https://api.example.com/users")
|
|
533
|
+
.withRequestInterceptor(async config => {
|
|
534
|
+
const token = await getTokenAsync();
|
|
535
|
+
config.headers["Authorization"] = `Bearer ${token}`;
|
|
536
|
+
return config;
|
|
537
|
+
})
|
|
538
|
+
.getJson();
|
|
352
539
|
```
|
|
353
540
|
|
|
354
541
|
### Request Cancellation
|
|
@@ -356,17 +543,20 @@ function getProducts() {
|
|
|
356
543
|
```typescript
|
|
357
544
|
const controller = new AbortController();
|
|
358
545
|
|
|
359
|
-
const request = create
|
|
546
|
+
const request = create
|
|
547
|
+
.get("https://api.example.com/slow-endpoint")
|
|
548
|
+
.withTimeout(10000)
|
|
549
|
+
.withAbortController(controller);
|
|
360
550
|
|
|
361
551
|
// Later, cancel the request if needed
|
|
362
552
|
setTimeout(() => controller.abort(), 2000);
|
|
363
553
|
|
|
364
554
|
try {
|
|
365
|
-
const data = await request.
|
|
555
|
+
const data = await request.getJson();
|
|
366
556
|
} catch (error) {
|
|
367
557
|
if (error.name === "AbortError") {
|
|
368
558
|
console.log("Request was cancelled by user");
|
|
369
|
-
} else if (error.
|
|
559
|
+
} else if (error.isTimeout) {
|
|
370
560
|
console.log("Request timed out");
|
|
371
561
|
} else {
|
|
372
562
|
console.log("Other error:", error.message);
|
|
@@ -374,104 +564,71 @@ try {
|
|
|
374
564
|
}
|
|
375
565
|
```
|
|
376
566
|
|
|
567
|
+
### URL Handling
|
|
568
|
+
|
|
569
|
+
The library handles both absolute and relative URLs, and automatically merges query parameters:
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
// Relative URLs (preserved as-is)
|
|
573
|
+
const relative = await create.get("/api/users").getJson();
|
|
574
|
+
|
|
575
|
+
// Absolute URLs
|
|
576
|
+
const absolute = await create.get("https://api.example.com/users").getJson();
|
|
577
|
+
|
|
578
|
+
// Merging query params with existing URL params
|
|
579
|
+
const merged = await create
|
|
580
|
+
.get("https://api.example.com/users?page=1")
|
|
581
|
+
.withQueryParams({ limit: 20, sort: "name" })
|
|
582
|
+
.getJson();
|
|
583
|
+
// Result: https://api.example.com/users?page=1&limit=20&sort=name
|
|
584
|
+
|
|
585
|
+
// Special characters and unicode are properly encoded
|
|
586
|
+
const encoded = await create
|
|
587
|
+
.get("https://api.example.com/search")
|
|
588
|
+
.withQueryParams({ name: "用户名", filter: "status:active" })
|
|
589
|
+
.getJson();
|
|
590
|
+
```
|
|
591
|
+
|
|
377
592
|
### Data Selection
|
|
378
593
|
|
|
379
594
|
The `getData` method provides a powerful way to extract and transform specific data from API responses:
|
|
380
595
|
|
|
381
596
|
```typescript
|
|
382
597
|
// Extract specific properties from nested structures
|
|
383
|
-
const posts = await
|
|
384
|
-
.
|
|
598
|
+
const posts = await create
|
|
599
|
+
.get("https://api.example.com/feed")
|
|
385
600
|
.getData(data => data.feed.posts);
|
|
386
601
|
|
|
387
602
|
// Transform data in the selector function
|
|
388
|
-
const usernames = await
|
|
389
|
-
.
|
|
603
|
+
const usernames = await create
|
|
604
|
+
.get("https://api.example.com/users")
|
|
390
605
|
.getData(data => data.users.map(user => user.username));
|
|
391
606
|
|
|
392
607
|
// Apply filtering in the selector
|
|
393
|
-
const activeUsers = await
|
|
394
|
-
.
|
|
608
|
+
const activeUsers = await create
|
|
609
|
+
.get("https://api.example.com/users")
|
|
395
610
|
.getData(data => data.users.filter(user => user.isActive));
|
|
396
611
|
|
|
397
612
|
// Combine data from complex nested structures
|
|
398
|
-
const combinedData = await
|
|
399
|
-
.
|
|
400
|
-
.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
notifications: data.user.notifications.unread,
|
|
404
|
-
}));
|
|
613
|
+
const combinedData = await create.get("https://api.example.com/dashboard").getData(data => ({
|
|
614
|
+
userCount: data.stats.users.total,
|
|
615
|
+
recentPosts: data.content.recent.slice(0, 5),
|
|
616
|
+
notifications: data.user.notifications.unread,
|
|
617
|
+
}));
|
|
405
618
|
```
|
|
406
619
|
|
|
407
|
-
When a selector fails, the error message will contain
|
|
620
|
+
When a selector fails, the error message will contain helpful context to diagnose the issue:
|
|
408
621
|
|
|
409
622
|
```typescript
|
|
410
623
|
try {
|
|
411
624
|
// This will fail if the response structure doesn't match expectations
|
|
412
|
-
const result = await
|
|
413
|
-
.
|
|
625
|
+
const result = await create
|
|
626
|
+
.get("https://api.example.com/data")
|
|
414
627
|
.getData(data => data.results.items);
|
|
415
628
|
} catch (error) {
|
|
416
629
|
console.error(error);
|
|
417
|
-
// Error message includes
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Implementing Custom Storage Providers
|
|
422
|
-
|
|
423
|
-
You can create your own storage provider by implementing the `StorageProvider` interface:
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
import { StorageProvider } from "create-request";
|
|
427
|
-
|
|
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
|
-
},
|
|
434
|
-
|
|
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
|
-
},
|
|
449
|
-
|
|
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
|
-
};
|
|
630
|
+
// Error message includes context for debugging
|
|
465
631
|
}
|
|
466
|
-
|
|
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");
|
|
475
632
|
```
|
|
476
633
|
|
|
477
634
|
## TypeScript Support
|
|
@@ -494,14 +651,12 @@ interface ApiResponse<T> {
|
|
|
494
651
|
|
|
495
652
|
// Type the full response
|
|
496
653
|
const response = await create
|
|
497
|
-
.get()
|
|
498
|
-
.sendTo("https://api.example.com/users")
|
|
654
|
+
.get("https://api.example.com/users")
|
|
499
655
|
.getJson<ApiResponse<User[]>>();
|
|
500
656
|
|
|
501
657
|
// Or use getData with type parameters
|
|
502
658
|
const users = await create
|
|
503
|
-
.get()
|
|
504
|
-
.sendTo("https://api.example.com/users")
|
|
659
|
+
.get("https://api.example.com/users")
|
|
505
660
|
.getData<ApiResponse<User[]>, User[]>(data => data.data);
|
|
506
661
|
|
|
507
662
|
// TypeScript knows the types
|
|
@@ -512,9 +667,8 @@ users.forEach(user => {
|
|
|
512
667
|
// Function with proper types
|
|
513
668
|
async function getUserById(id: number): Promise<User> {
|
|
514
669
|
return create
|
|
515
|
-
.get()
|
|
670
|
+
.get("https://api.example.com/users")
|
|
516
671
|
.withQueryParam("id", id)
|
|
517
|
-
.sendTo("https://api.example.com/users")
|
|
518
672
|
.getData<ApiResponse<User[]>, User>(data => {
|
|
519
673
|
const user = data.data[0];
|
|
520
674
|
if (!user) throw new Error(`User with ID ${id} not found`);
|
|
@@ -544,10 +698,14 @@ You can configure CSRF settings globally for all requests:
|
|
|
544
698
|
```typescript
|
|
545
699
|
// Configure CSRF settings for all requests
|
|
546
700
|
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
|
|
701
|
+
create.config.setCsrfHeaderName("X-CSRF-Token"); // Default header name for CSRF token
|
|
702
|
+
create.config.setXsrfCookieName("XSRF-TOKEN"); // Default cookie name to read from
|
|
703
|
+
create.config.setXsrfHeaderName("X-XSRF-TOKEN"); // Default header name for XSRF token from cookie
|
|
549
704
|
create.config.setEnableAntiCsrf(true); // Enable/disable X-Requested-With header
|
|
550
705
|
create.config.setEnableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
|
|
706
|
+
|
|
707
|
+
// Reset all configuration to defaults
|
|
708
|
+
create.config.reset();
|
|
551
709
|
```
|
|
552
710
|
|
|
553
711
|
### Per-Request CSRF Settings
|
|
@@ -557,21 +715,12 @@ You can also configure CSRF protection on individual requests:
|
|
|
557
715
|
```typescript
|
|
558
716
|
// Configure CSRF for a specific request
|
|
559
717
|
const request = create
|
|
560
|
-
.post()
|
|
718
|
+
.post("https://api.example.com/users")
|
|
561
719
|
.withCsrfToken("request-specific-token") // Set a specific token
|
|
562
720
|
.withAntiCsrfHeaders() // Explicitly add X-Requested-With header
|
|
563
721
|
.withoutCsrfProtection(); // Or disable all automatic CSRF protection
|
|
564
722
|
```
|
|
565
723
|
|
|
566
|
-
### Integrating with Backend Frameworks
|
|
567
|
-
|
|
568
|
-
Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
|
|
569
|
-
|
|
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
724
|
## Performance Considerations
|
|
576
725
|
|
|
577
726
|
create-request is designed to be lightweight and efficient:
|
|
@@ -580,7 +729,7 @@ create-request is designed to be lightweight and efficient:
|
|
|
580
729
|
- **Tree-Shakable**: Only import what you need
|
|
581
730
|
- **Minimal Overhead**: Thin wrapper around the native Fetch API
|
|
582
731
|
- **Memory Efficient**: Doesn't create unnecessary objects
|
|
583
|
-
- **
|
|
732
|
+
- **Clean API**: Simple and intuitive interface
|
|
584
733
|
|
|
585
734
|
## Browser Support
|
|
586
735
|
|
|
@@ -596,13 +745,12 @@ This library works with all browsers that support the Fetch API:
|
|
|
596
745
|
|
|
597
746
|
| Feature | create-request | Fetch | Axios | SuperAgent | Got | Ky | node-fetch | Redaxios |
|
|
598
747
|
| ------------------- | -------------- | ------ | ------- | ---------- | ------- | ------ | ---------- | -------- |
|
|
599
|
-
| **Size (min+gzip)** | ~5.
|
|
748
|
+
| **Size (min+gzip)** | ~5.5KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
|
|
600
749
|
| **Browser** | Modern | Modern | IE11+ | IE9+ | ❌ No | Modern | ❌ No | Modern |
|
|
601
750
|
| **Node.js** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
602
751
|
| **HTTP/2** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
603
752
|
| **Auto Retries** | ✅ | ❌ | 🛠️ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
604
753
|
| **Cancellation** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
605
|
-
| **Caching** | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
606
754
|
| **Auto JSON** | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
|
607
755
|
| **Timeout** | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
608
756
|
| **TypeScript** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
@@ -614,6 +762,8 @@ This library works with all browsers that support the Fetch API:
|
|
|
614
762
|
| **Zero Deps** | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
|
|
615
763
|
| **Chainable API** | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
616
764
|
| **CSRF Protection** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
765
|
+
| **GraphQL Support** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
766
|
+
| **Interceptors** | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
617
767
|
|
|
618
768
|
**Notes:**
|
|
619
769
|
|