create-request 0.0.1-beta.7 → 0.0.1-beta.8

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
@@ -8,28 +8,58 @@
8
8
 
9
9
  ## Core Features
10
10
 
11
- - ⛓️ **Chainable API** - Build and execute requests with a fluent interface
12
11
  - ⏱️ **Timeout Support** - Set timeouts for any request
13
- - 🔁 **Automatic Retries** - Retry failed requests with customizable settings
14
- - 🔐 **Auth Helpers** - Simple methods for common authentication patterns
15
- - 📦 **Typed Responses** - Full TypeScript support for response data
12
+ - 🚀 **Performance** - Tiny bundle size with zero dependencies
16
13
  - 🚧 **Error Handling** - Detailed error info with custom error class
14
+ - 🔐 **Auth Helpers** - Simple methods for common authentication patterns
15
+ - ⛓️ **Chainable API** - Build and execute requests with a fluent interface
16
+ - 📉 **Reduced Boilerplate** - Write 60% less code for common API operations
17
+ - 🔁 **Automatic Retries** - Retry failed requests with customizable settings
18
+ - 🛡️ **Type Safety** - Full TypeScript support with intelligent type inference
17
19
  - 📝 **Response Caching** - Flexible caching system with multiple storage options
18
20
 
19
21
  ## Why create-request?
20
22
 
21
- **You've probably been here before:** writing boilerplate fetch code, handling retries manually, struggling with timeouts, or wrestling with TypeScript types for your API responses. `create-request` solves all of that with an elegant API that separates request building from execution:
23
+ **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:
22
24
 
23
25
  ```typescript
24
- // Create a request
25
- const usersRequest = create.get()
26
- .withTimeout(5000)
27
- .withRetries(3)
28
- .withBearerToken('your-token');
26
+ // Before: Regular fetch
27
+ async function createUser(userData) {
28
+ try {
29
+ const response = await fetch('https://api.example.com/users', {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ 'Authorization': 'Basic ' + btoa('username:password')
34
+ },
35
+ body: JSON.stringify(userData)
36
+ });
37
+
38
+ if (!response.ok) {
39
+ throw new Error(`HTTP error! status: ${response.status}`);
40
+ }
41
+ const data = await response.json();
42
+ return data;
43
+ } catch (error) {
44
+ console.error('Fetch error:', error);
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ // After: With create-request
50
+ import create from 'create-request';
29
51
 
30
- // Execute the request
31
- const users = await usersRequest.sendTo('https://api.example.com/users')
32
- .getJson<User[]>();
52
+ function createUser(userData) {
53
+ return create.post()
54
+ .withBasicAuth('username', 'password')
55
+ .withBody(userData)
56
+ .sendTo('https://api.example.com/users')
57
+ .getData<User>()
58
+ .catch(error => {
59
+ console.error('Fetch error:', error);
60
+ throw error;
61
+ });
62
+ }
33
63
  ```
34
64
 
35
65
  ## Installation
@@ -44,67 +74,6 @@ Or with yarn:
44
74
  yarn add create-request
45
75
  ```
46
76
 
47
- ## 🔄 Fetch API Comparison
48
-
49
- ### Fetch API
50
-
51
- ```typescript
52
- // Basic GET request with fetch
53
- try {
54
- const response = await fetch('https://api.example.com/users');
55
- if (!response.ok) {
56
- throw new Error(`HTTP error ${response.status}`);
57
- }
58
- const users = await response.json();
59
- console.log(users);
60
- } catch (error) {
61
- console.error('Error fetching users:', error);
62
- }
63
-
64
- // POST request with fetch
65
- try {
66
- const response = await fetch('https://api.example.com/users', {
67
- method: 'POST',
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- 'Authorization': 'Bearer token123'
71
- },
72
- body: JSON.stringify({ name: 'John Doe', email: 'john@example.com' })
73
- });
74
- if (!response.ok) {
75
- throw new Error(`HTTP error ${response.status}`);
76
- }
77
- const result = await response.json();
78
- console.log(result);
79
- } catch (error) {
80
- console.error('Error creating user:', error);
81
- }
82
- ```
83
-
84
- ### create-request API
85
-
86
- ```typescript
87
- // Basic GET request with create-request
88
- try {
89
- const users = await create.get().sendTo('https://api.example.com/users').getJson();
90
- console.log(users);
91
- } catch (error) {
92
- console.error('Error fetching users:', error.message);
93
- }
94
-
95
- // POST request with create-request
96
- const createUserRequest = create.post()
97
- .withBody({ name: 'John Doe', email: 'john@example.com' })
98
- .withBearerToken('token123');
99
-
100
- try {
101
- const result = await createUserRequest.sendTo('https://api.example.com/users').getJson();
102
- console.log(result);
103
- } catch (error) {
104
- console.error('Error creating user:', error.message);
105
- }
106
- ```
107
-
108
77
  ## Basic Usage
109
78
 
110
79
  ### Creating Requests
@@ -124,30 +93,78 @@ const optionsRequest = create.options();
124
93
 
125
94
  ### Request Configuration
126
95
 
96
+ The library provides a comprehensive set of configuration methods that can be chained together to customize your requests:
97
+
127
98
  ```typescript
128
- import { RequestPriority, CredentialsPolicy } from 'create-request';
99
+ import create, { RequestPriority, CredentialsPolicy, RequestMode, CachePolicy, RedirectMode, ReferrerPolicy } from 'create-request';
129
100
 
130
101
  // Configure request options
131
102
  const request = create.get()
132
- // Headers
133
- .withHeaders({ 'X-API-Key': 'abc123' })
134
-
135
- // Timeout and retries
136
- .withTimeout(5000)
137
- .withRetries(3)
138
- .onRetry(({ attempt, error }) => {
139
- console.log(`Attempt ${attempt} failed: ${error.message}`);
103
+ // Basic headers
104
+ .withHeaders({ 'X-API-Key': 'abc123', 'Accept-Language': 'en-US' })
105
+ .withHeader('Custom-Header', 'value') // Add a single header
106
+
107
+ // Timeout settings
108
+ .withTimeout(5000) // Request will abort after 5 seconds
109
+
110
+ // Automatic retry configuration
111
+ .withRetries(3) // Retry up to 3 times on failure
112
+ .withRetryDelay(1000) // Wait 1 second between retries
113
+ .withExponentialBackoff(true) // Use exponential backoff (increases wait time between retries)
114
+ .onRetry(({ attempt, error, delay }) => {
115
+ console.log(`Attempt ${attempt} failed: ${error.message}. Retrying in ${delay}ms`);
140
116
  })
141
117
 
142
- // Authentication
143
- .withBearerToken('your-token')
118
+ // Authentication methods
119
+ .withBearerToken('your-token') // Adds Authorization: Bearer your-token
120
+ .withBasicAuth('username', 'password') // HTTP Basic Authentication
121
+
122
+ // URL parameters
123
+ .withQueryParams({ search: 'term', page: 1, limit: 20 })
124
+ .withQueryParam('filter', 'active') // Add a single query parameter
125
+
126
+ // Request body configuration (for POST/PUT/PATCH)
127
+ .withContentType('application/json') // Set specific content type
128
+
129
+ // Fetch API options
130
+ .withCredentials(CredentialsPolicy.INCLUDE) // Includes cookies with cross-origin requests
131
+ .withMode(RequestMode.CORS) // Controls CORS behavior
132
+ .withCache(CachePolicy.NO_STORE) // Controls how request interacts with browser cache
133
+ .withRedirect(RedirectMode.FOLLOW) // Controls redirect behavior (follow, error, manual)
134
+ .withReferrer('https://example.com') // Sets request referrer
135
+ .withReferrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) // Controls referrer policy
136
+ .withPriority(RequestPriority.HIGH) // Sets request priority
137
+ ```
144
138
 
145
- // Query parameters
146
- .withQueryParams({ search: 'term', page: 1 })
139
+ 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:
147
140
 
148
- // Advanced options
149
- .withCredentials(CredentialsPolicy.INCLUDE)
150
- .withPriority(RequestPriority.HIGH);
141
+ ```typescript
142
+ // Simple example with just what's needed
143
+ const searchUsers = create.get()
144
+ .withBearerToken(userToken)
145
+ .withQueryParams({ q: searchTerm, limit: 20 })
146
+ .withTimeout(3000);
147
+
148
+ // Now execute the request
149
+ const users = await searchUsers.sendTo('https://api.example.com/users').getData();
150
+ ```
151
+
152
+ You can also create reusable base requests with common settings:
153
+
154
+ ```typescript
155
+ // Create base authenticated request
156
+ const apiBase = create.get()
157
+ .withHeaders({
158
+ 'X-API-Version': '1.2',
159
+ 'Accept-Language': 'en-US'
160
+ })
161
+ .withBearerToken(authToken)
162
+ .withTimeout(5000)
163
+ .withRetries(2);
164
+
165
+ // Use the base request for different endpoints
166
+ const users = await apiBase.sendTo('https://api.example.com/users').getData();
167
+ const products = await apiBase.sendTo('https://api.example.com/products').getData();
151
168
  ```
152
169
 
153
170
  ### Request Bodies (POST/PUT/PATCH)
@@ -182,6 +199,58 @@ const textData = await request.sendTo('https://api.example.com/endpoint').getTex
182
199
  const blobData = await request.sendTo('https://api.example.com/endpoint').getBlob();
183
200
  const bodyStream = await request.sendTo('https://api.example.com/endpoint').getBody();
184
201
  const arrayBuffer = await request.sendTo('https://api.example.com/endpoint').getArrayBuffer();
202
+
203
+ // Using the data selector API to extract specific data
204
+ const userData = await request.sendTo('https://api.example.com/users')
205
+ .getData(data => data.results.users);
206
+
207
+ // Using the data selector without a selector function just returns the full JSON response
208
+ const fullData = await request.sendTo('https://api.example.com/data').getData();
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
+ // "Data selector failed: Cannot read properties of undefined (reading 'items').
252
+ // Original data: { "status": "error", "message": "Invalid request" }"
253
+ }
185
254
  ```
186
255
 
187
256
  ### Error Handling
@@ -285,12 +354,33 @@ interface User {
285
354
  email: string;
286
355
  }
287
356
 
357
+ interface ApiResponse {
358
+ users: User[];
359
+ pagination: {
360
+ total: number;
361
+ page: number;
362
+ }
363
+ }
364
+
288
365
  const userRequest = create.get();
289
- const user = await userRequest.sendTo('https://api.example.com/user/123')
290
- .getJson<User>();
291
366
 
292
- // TypeScript knows the type
293
- console.log(user.name);
367
+ // Type the full response
368
+ const response = await userRequest
369
+ .sendTo('https://api.example.com/users')
370
+ .getJson<ApiResponse>();
371
+
372
+ // Or use getData with type parameters
373
+ const users = await userRequest
374
+ .sendTo('https://api.example.com/users')
375
+ .getData<ApiResponse, User[]>(data => data.users);
376
+
377
+ // Or just get the full typed response
378
+ const fullData = await userRequest
379
+ .sendTo('https://api.example.com/users')
380
+ .getData<ApiResponse>();
381
+
382
+ // TypeScript knows the types
383
+ console.log(users[0].name);
294
384
  ```
295
385
 
296
386
  ### Promise Chaining
@@ -322,15 +412,6 @@ const request = create.get()
322
412
  storage: createMemoryStorage(),
323
413
  keyGenerator: (url, method, headers) => {
324
414
  // Include user ID from authorization header in the cache key
325
- const authHeader = headers?.['Authorization'] || '';
326
- const userId = extractUserIdFromToken(authHeader);
327
- return `${method}:${url}:${userId}`;
328
- }
329
- });
330
-
331
- // Simple custom storage provider example
332
- const customStorage: StorageProvider = {
333
- get(key: string): string | null {
334
415
  // Get from your custom storage
335
416
  return localStorage.getItem(`my-app:${key}`);
336
417
  },
@@ -424,24 +505,54 @@ With custom storage providers, you can integrate with any storage system, such a
424
505
 
425
506
  ## CSRF Protection
426
507
 
508
+ Cross-Site Request Forgery (CSRF) is a type of security vulnerability where unauthorized commands are executed on behalf of an authenticated user. `create-request` provides built-in protection mechanisms to help prevent CSRF attacks.
509
+
510
+ ### How CSRF Protection Works
511
+
512
+ The library employs multiple strategies to protect against CSRF attacks:
513
+
514
+ 1. **Automatic X-Requested-With Header**: By default, all requests include the `X-Requested-With: XMLHttpRequest` header, which helps servers identify legitimate AJAX requests.
515
+
516
+ 2. **CSRF Token Support**: The library can automatically include CSRF tokens in request headers, which servers can validate to ensure the request came from your application.
517
+
518
+ 3. **XSRF Cookie Reading**: For frameworks that use the double-submit cookie pattern (like Laravel, Rails, or Django), the library can automatically read XSRF tokens from cookies and include them in request headers.
519
+
427
520
  ### Global CSRF Configuration
428
521
 
522
+ You can configure CSRF settings globally for all requests:
523
+
429
524
  ```typescript
430
525
  // Configure CSRF settings for all requests
431
526
  create.config.setCsrfToken('your-csrf-token');
432
- create.config.setCsrfHeaderName('X-CSRF-Token');
433
- create.config.setXsrfCookieName('XSRF-TOKEN');
527
+ create.config.setCsrfHeaderName('X-CSRF-Token'); // Default header name
528
+ create.config.setXsrfCookieName('XSRF-TOKEN'); // Default cookie name
529
+ create.config.enableAntiCsrf(true); // Enable/disable X-Requested-With header
530
+ create.config.enableAutoXsrf(true); // Enable/disable automatic cookie-to-header token
434
531
  ```
435
532
 
436
533
  ### Per-Request CSRF Settings
437
534
 
535
+ You can also configure CSRF protection on individual requests:
536
+
438
537
  ```typescript
439
538
  // Configure CSRF for a specific request
440
539
  const request = create.post()
441
- .withCsrfToken('request-specific-token')
442
- .withoutCsrfProtection(); // Disable automatic CSRF protection
540
+ .withCsrfToken('request-specific-token') // Set a specific token
541
+ .withAntiCsrfHeaders() // Explicitly add X-Requested-With header
542
+ .withoutCsrfProtection(); // Or disable all automatic CSRF protection
443
543
  ```
444
544
 
545
+ ### Integrating with Backend Frameworks
546
+
547
+ Most modern frameworks support CSRF protection out of the box. The library works seamlessly with:
548
+
549
+ - **Laravel**: Automatically reads XSRF-TOKEN cookie and sends X-XSRF-TOKEN header
550
+ - **Rails**: Works with the Rails CSRF token system
551
+ - **Django**: Compatible with Django's CSRF middleware
552
+ - **Express.js + csurf**: Works with the csurf middleware token pattern
553
+
554
+ When your server sends a CSRF token in a cookie or response header, `create-request` can automatically extract and include it in subsequent requests.
555
+
445
556
  ## Browser Support
446
557
 
447
558
  This library works with all browsers that support the Fetch API:
@@ -34,6 +34,12 @@ export declare abstract class BaseRequest {
34
34
  * @returns The instance for chaining
35
35
  */
36
36
  withReferrer(referrer: string): this;
37
+ /**
38
+ * Sets the referrer policy for the request
39
+ * @param policy - The referrer policy to use
40
+ * @returns The instance for chaining
41
+ */
42
+ withReferrerPolicy(policy: ReferrerPolicy): this;
37
43
  /**
38
44
  * Sets the redirect policy for the request
39
45
  * @param redirect - Can be 'follow', 'error', or 'manual'
@@ -29,4 +29,5 @@ export interface ResponsePromise<T = ResponseWrapper> extends Promise<T> {
29
29
  getBlob(): Promise<Blob>;
30
30
  getArrayBuffer(): Promise<ArrayBuffer>;
31
31
  getBody(): Promise<ReadableStream<Uint8Array> | null>;
32
+ getData<T = unknown, R = T>(selector?: (data: T) => R): Promise<T | R>;
32
33
  }
@@ -70,3 +70,16 @@ export declare enum BodyType {
70
70
  STRING = "string",
71
71
  BINARY = "binary"
72
72
  }
73
+ /**
74
+ * Referrer policies for fetch requests
75
+ */
76
+ export declare enum ReferrerPolicy {
77
+ ORIGIN = "origin",
78
+ UNSAFE_URL = "unsafe-url",
79
+ SAME_ORIGIN = "same-origin",
80
+ NO_REFERRER = "no-referrer",
81
+ STRICT_ORIGIN = "strict-origin",
82
+ ORIGIN_WHEN_CROSS_ORIGIN = "origin-when-cross-origin",
83
+ NO_REFERRER_WHEN_DOWNGRADE = "no-referrer-when-downgrade",
84
+ STRICT_ORIGIN_WHEN_CROSS_ORIGIN = "strict-origin-when-cross-origin"
85
+ }
@@ -78,6 +78,20 @@ var BodyType;
78
78
  BodyType["STRING"] = "string";
79
79
  BodyType["BINARY"] = "binary";
80
80
  })(BodyType || (BodyType = {}));
81
+ /**
82
+ * Referrer policies for fetch requests
83
+ */
84
+ var ReferrerPolicy;
85
+ (function (ReferrerPolicy) {
86
+ ReferrerPolicy["ORIGIN"] = "origin";
87
+ ReferrerPolicy["UNSAFE_URL"] = "unsafe-url";
88
+ ReferrerPolicy["SAME_ORIGIN"] = "same-origin";
89
+ ReferrerPolicy["NO_REFERRER"] = "no-referrer";
90
+ ReferrerPolicy["STRICT_ORIGIN"] = "strict-origin";
91
+ ReferrerPolicy["ORIGIN_WHEN_CROSS_ORIGIN"] = "origin-when-cross-origin";
92
+ ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
93
+ ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
94
+ })(ReferrerPolicy || (ReferrerPolicy = {}));
81
95
 
82
96
  class RequestError extends Error {
83
97
  constructor(message, url, method, options = {}) {
@@ -823,6 +837,15 @@ class BaseRequest {
823
837
  this.requestOptions.referrer = referrer;
824
838
  return this;
825
839
  }
840
+ /**
841
+ * Sets the referrer policy for the request
842
+ * @param policy - The referrer policy to use
843
+ * @returns The instance for chaining
844
+ */
845
+ withReferrerPolicy(policy) {
846
+ this.requestOptions.referrerPolicy = policy;
847
+ return this;
848
+ }
826
849
  /**
827
850
  * Sets the redirect policy for the request
828
851
  * @param redirect - Can be 'follow', 'error', or 'manual'
@@ -929,6 +952,12 @@ class BaseRequest {
929
952
  * Works in both browser and Node.js environments
930
953
  */
931
954
  encodeBase64(str) {
955
+ // Modern approach using TextEncoder (available in both modern browsers and Node.js)
956
+ if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
957
+ const encoder = new TextEncoder();
958
+ const bytes = encoder.encode(str);
959
+ return btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
960
+ }
932
961
  // Browser environment
933
962
  if (typeof btoa === "function")
934
963
  return btoa(str);
@@ -1119,6 +1148,25 @@ class BaseRequest {
1119
1148
  responsePromise.getBody = async () => {
1120
1149
  return basePromise.then(response => response.getBody());
1121
1150
  };
1151
+ responsePromise.getData = async (selector) => {
1152
+ try {
1153
+ const data = await basePromise.then(response => response.getJson());
1154
+ // If no selector is provided, return the raw JSON data
1155
+ if (!selector)
1156
+ return data;
1157
+ // Apply the selector if provided
1158
+ return selector(data);
1159
+ }
1160
+ catch (error) {
1161
+ if (error instanceof Error) {
1162
+ // If selector fails, enhance the error with the original data
1163
+ const enhancedError = new Error(`Data selector failed: ${error.message}. Original data: ${JSON.stringify(await basePromise.then(response => response.getJson()), null, 2)}`);
1164
+ enhancedError.stack = error.stack;
1165
+ throw enhancedError;
1166
+ }
1167
+ throw error;
1168
+ }
1169
+ };
1122
1170
  return responsePromise;
1123
1171
  }
1124
1172
  /**
@@ -84,6 +84,20 @@
84
84
  BodyType["STRING"] = "string";
85
85
  BodyType["BINARY"] = "binary";
86
86
  })(BodyType || (BodyType = {}));
87
+ /**
88
+ * Referrer policies for fetch requests
89
+ */
90
+ var ReferrerPolicy;
91
+ (function (ReferrerPolicy) {
92
+ ReferrerPolicy["ORIGIN"] = "origin";
93
+ ReferrerPolicy["UNSAFE_URL"] = "unsafe-url";
94
+ ReferrerPolicy["SAME_ORIGIN"] = "same-origin";
95
+ ReferrerPolicy["NO_REFERRER"] = "no-referrer";
96
+ ReferrerPolicy["STRICT_ORIGIN"] = "strict-origin";
97
+ ReferrerPolicy["ORIGIN_WHEN_CROSS_ORIGIN"] = "origin-when-cross-origin";
98
+ ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
99
+ ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
100
+ })(ReferrerPolicy || (ReferrerPolicy = {}));
87
101
 
88
102
  class RequestError extends Error {
89
103
  constructor(message, url, method, options = {}) {
@@ -829,6 +843,15 @@
829
843
  this.requestOptions.referrer = referrer;
830
844
  return this;
831
845
  }
846
+ /**
847
+ * Sets the referrer policy for the request
848
+ * @param policy - The referrer policy to use
849
+ * @returns The instance for chaining
850
+ */
851
+ withReferrerPolicy(policy) {
852
+ this.requestOptions.referrerPolicy = policy;
853
+ return this;
854
+ }
832
855
  /**
833
856
  * Sets the redirect policy for the request
834
857
  * @param redirect - Can be 'follow', 'error', or 'manual'
@@ -935,6 +958,12 @@
935
958
  * Works in both browser and Node.js environments
936
959
  */
937
960
  encodeBase64(str) {
961
+ // Modern approach using TextEncoder (available in both modern browsers and Node.js)
962
+ if (typeof TextEncoder !== "undefined" && typeof btoa === "function") {
963
+ const encoder = new TextEncoder();
964
+ const bytes = encoder.encode(str);
965
+ return btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
966
+ }
938
967
  // Browser environment
939
968
  if (typeof btoa === "function")
940
969
  return btoa(str);
@@ -1125,6 +1154,25 @@
1125
1154
  responsePromise.getBody = async () => {
1126
1155
  return basePromise.then(response => response.getBody());
1127
1156
  };
1157
+ responsePromise.getData = async (selector) => {
1158
+ try {
1159
+ const data = await basePromise.then(response => response.getJson());
1160
+ // If no selector is provided, return the raw JSON data
1161
+ if (!selector)
1162
+ return data;
1163
+ // Apply the selector if provided
1164
+ return selector(data);
1165
+ }
1166
+ catch (error) {
1167
+ if (error instanceof Error) {
1168
+ // If selector fails, enhance the error with the original data
1169
+ const enhancedError = new Error(`Data selector failed: ${error.message}. Original data: ${JSON.stringify(await basePromise.then(response => response.getJson()), null, 2)}`);
1170
+ enhancedError.stack = error.stack;
1171
+ throw enhancedError;
1172
+ }
1173
+ throw error;
1174
+ }
1175
+ };
1128
1176
  return responsePromise;
1129
1177
  }
1130
1178
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-request",
3
- "version": "0.0.1-beta.7",
3
+ "version": "0.0.1-beta.8",
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",