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 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
- .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
+ // 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(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
175
- .withPriority(RequestPriority.HIGH) // Sets request priority
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 searchUsers = create
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.post().withBody({ name: "John", age: 30 });
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.post().withBody("Plain text content");
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
- // Simple execution
240
- const response = await request.sendTo("https://api.example.com/endpoint");
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 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();
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 request
251
- .sendTo("https://api.example.com/users")
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 request.sendTo("https://api.example.com/data").getData();
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 request.sendTo("https://api.example.com/data").getJson();
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.timeoutError); // Whether it was a timeout
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
- ### Caching Requests
359
+ ## Advanced Usage
282
360
 
283
- ```typescript
284
- import create, {
285
- createMemoryStorage,
286
- createLocalStorageStorage,
287
- createSessionStorageStorage,
288
- } from "create-request";
361
+ ### GraphQL Support
289
362
 
290
- // Simple in-memory caching
291
- const request = create
292
- .get()
293
- .withCache({
294
- storage: createMemoryStorage(),
295
- ttl: 60000, // 1 minute in milliseconds
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
- .sendTo("https://api.example.com/data");
404
+ .getJson();
405
+ ```
298
406
 
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",
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
- .sendTo("https://api.example.com/users");
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
- // 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"],
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
- .sendTo("https://api.example.com/data");
481
+ .getJson();
319
482
  ```
320
483
 
321
- ## Advanced Usage
484
+ #### Interceptor Execution Order
485
+
486
+ Interceptors execute in a specific order:
322
487
 
323
- ### Request Reuse
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
- // Create a base authenticated request
327
- const authRequest = create.get().withBearerToken("token123").withTimeout(5000).withRetries(2);
328
-
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();
332
-
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);
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
- function getProducts() {
350
- return apiBase().sendTo("https://api.example.com/products").getData();
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.get().withTimeout(10000).withAbortController(controller);
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.sendTo("https://api.example.com/slow-endpoint").getJson();
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.timeoutError) {
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 request
384
- .sendTo("https://api.example.com/feed")
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 request
389
- .sendTo("https://api.example.com/users")
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 request
394
- .sendTo("https://api.example.com/users")
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 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
- }));
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 the original response data to help diagnose the issue:
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 request
413
- .sendTo("https://api.example.com/data")
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 the original response data for debugging
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
- - **Cache Management**: Configurable caching to reduce network requests
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.1KB | Native | ~13.6KB | ~17.8KB | ~17.8KB | ~3.4KB | ~7.7KB | ~1KB |
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