create-request 1.3.0 → 1.4.0

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
@@ -1,9 +1,12 @@
1
1
  # create-request
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/create-request.svg)](https://www.npmjs.com/package/create-request)
4
3
  [![License](https://img.shields.io/npm/l/create-request.svg)](https://github.com/DanielAmenou/create-request/blob/main/LICENSE)
4
+ [![codecov](https://codecov.io/github/danielamenou/create-request/graph/badge.svg?token=OUBR6RNXZO)](https://codecov.io/github/danielamenou/create-request)
5
+ [![npm downloads](https://img.shields.io/npm/dt/create-request.svg)](https://www.npmjs.com/package/create-request)
6
+ [![npm version](https://img.shields.io/npm/v/create-request.svg)](https://www.npmjs.com/package/create-request)
5
7
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/create-request)](https://bundlephobia.com/package/create-request)
6
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-4.7%2B-blue)](https://www.typescriptlang.org/)
9
+ [![Known Vulnerabilities](https://snyk.io/test/github/DanielAmenou/create-request/badge.svg)](https://snyk.io/test/github/DanielAmenou/create-request)
7
10
 
8
11
  `create-request` is a modern TypeScript library that transforms how you make API calls. Built as an elegant wrapper around the native Fetch API, it provides a chainable, fluent interface that dramatically reduces boilerplate while adding powerful features like automatic retries, timeout handling, and comprehensive error management.
9
12
 
@@ -143,6 +146,8 @@ const request = create
143
146
 
144
147
  // Automatic retry configuration
145
148
  .withRetries(3) // Retry up to 3 times on failure
149
+ // Or use a config object with delay support:
150
+ .withRetries({ attempts: 3, delay: 1000 }) // Retry 3 times with 1 second delay between attempts
146
151
  .onRetry(({ attempt, error }) => {
147
152
  console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
148
153
  })
@@ -229,24 +234,17 @@ The library provides built-in support for GraphQL queries and mutations:
229
234
 
230
235
  ```typescript
231
236
  // GraphQL query without variables
237
+ const userQuery = "query { users { id name email } }";
232
238
  const users = await create
233
239
  .post("https://api.example.com/graphql")
234
- .withGraphQL("query { users { id name email } }")
240
+ .withGraphQL(userQuery)
235
241
  .getJson();
236
242
 
237
243
  // GraphQL query with variables
244
+ const userQuery = "query GetUser($id: ID!) { user(id: $id) { name email } }";
238
245
  const user = await create
239
246
  .post("https://api.example.com/graphql")
240
- .withGraphQL("query GetUser($id: ID!) { user(id: $id) { name email } }", { id: "123" })
241
- .getJson();
242
-
243
- // GraphQL mutation
244
- const result = await create
245
- .post("https://api.example.com/graphql")
246
- .withGraphQL(
247
- "mutation CreateUser($name: String!) { createUser(name: $name) { id name } }",
248
- { name: "John Doe" }
249
- )
247
+ .withGraphQL(userQuery, { id: "123" })
250
248
  .getJson();
251
249
  ```
252
250
 
@@ -256,14 +254,11 @@ GraphQL errors do not cause exceptions by default. Use the `throwOnError` option
256
254
 
257
255
  ```typescript
258
256
  // Throw an error if the GraphQL response contains errors
257
+ const userQuery = "query GetUser($id: ID!) { user(id: $id) { name email } }";
259
258
  try {
260
259
  const user = await create
261
260
  .post("https://api.example.com/graphql")
262
- .withGraphQL(
263
- "query GetUser($id: ID!) { user(id: $id) { name email } }",
264
- { id: "123" },
265
- { throwOnError: true }
266
- )
261
+ .withGraphQL(userQuery, { id: "123" }, { throwOnError: true })
267
262
  .getJson();
268
263
  } catch (error) {
269
264
  console.error(error.message);
@@ -421,6 +416,53 @@ try {
421
416
 
422
417
  ## Advanced Usage
423
418
 
419
+ ### Automatic Retries with Delay
420
+
421
+ The `withRetries()` method supports both simple number-based retries and object-based configuration with customizable delays:
422
+
423
+ ```typescript
424
+ // Simple number
425
+ const request1 = create.get("https://api.example.com/data").withRetries(3);
426
+
427
+ // With fixed delay between retries
428
+ const request2 = create
429
+ .get("https://api.example.com/data")
430
+ .withRetries({ attempts: 3, delay: 1000 }); // Wait 1 second between retries
431
+
432
+ // With exponential backoff function
433
+ const request3 = create.get("https://api.example.com/data").withRetries({
434
+ attempts: 5,
435
+ delay: ({ attempt }) => Math.min(1000 * Math.pow(2, attempt - 1), 10000), // Exponential backoff capped at 10s
436
+ });
437
+
438
+ // With error-aware delay (e.g., longer delay for rate limits)
439
+ const request4 = create.get("https://api.example.com/data").withRetries({
440
+ attempts: 3,
441
+ delay: ({ attempt, error }) => {
442
+ if (error.status === 429) {
443
+ return 5000; // Wait 5 seconds for rate limit errors
444
+ }
445
+ return attempt * 1000; // Linear backoff for other errors
446
+ },
447
+ });
448
+ ```
449
+
450
+ **Rate Limit Aware:**
451
+
452
+ ```typescript
453
+ .withRetries({
454
+ attempts: 3,
455
+ delay: ({ error }) => {
456
+ if (error.status === 429) {
457
+ // Check Retry-After header if available
458
+ const retryAfter = error.response?.headers.get("Retry-After");
459
+ return retryAfter ? parseInt(retryAfter) * 1000 : 5000;
460
+ }
461
+ return 1000; // Default delay
462
+ },
463
+ })
464
+ ```
465
+
424
466
  ### Interceptors
425
467
 
426
468
  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.
@@ -1,5 +1,5 @@
1
1
  import { type HttpMethod, RedirectMode, RequestMode, RequestPriority, ReferrerPolicy, CredentialsPolicy } from "./enums.js";
2
- import type { RetryCallback, CookiesRecord, CookieOptions, RequestOptions, GraphQLOptions, ErrorInterceptor, RequestInterceptor, ResponseInterceptor } from "./types.js";
2
+ import type { RetryConfig, RetryCallback, CookiesRecord, CookieOptions, RequestOptions, GraphQLOptions, ErrorInterceptor, RequestInterceptor, ResponseInterceptor } from "./types.js";
3
3
  import { ResponseWrapper } from "./ResponseWrapper.js";
4
4
  /**
5
5
  * Base class with common functionality for all request types
@@ -66,14 +66,36 @@ export declare abstract class BaseRequest {
66
66
  /**
67
67
  * Configure automatic retry behavior for failed requests
68
68
  *
69
- * @param retries - Number of retry attempts before failing
69
+ * @param retries - Number of retry attempts before failing, or a configuration object
70
70
  * @returns The request instance for chaining
71
- * @throws RequestError if retries is not a non-negative integer
71
+ * @throws RequestError if retries is not a non-negative integer or invalid config
72
72
  *
73
73
  * @example
74
+ * // Simple number (backward compatible)
74
75
  * request.withRetries(3); // Retry up to 3 times
76
+ *
77
+ * @example
78
+ * // With fixed delay
79
+ * request.withRetries({ attempts: 3, delay: 1000 }); // Retry 3 times with 1 second delay
80
+ *
81
+ * @example
82
+ * // With exponential backoff function
83
+ * request.withRetries({
84
+ * attempts: 3,
85
+ * delay: ({ attempt }) => Math.min(1000 * Math.pow(2, attempt - 1), 10000)
86
+ * });
87
+ *
88
+ * @example
89
+ * // With delay function based on error
90
+ * request.withRetries({
91
+ * attempts: 3,
92
+ * delay: ({ attempt, error }) => {
93
+ * if (error.status === 429) return 5000; // Rate limited, wait longer
94
+ * return attempt * 1000; // Exponential backoff
95
+ * }
96
+ * });
75
97
  */
76
- withRetries(retries: number): this;
98
+ withRetries(retries: number | RetryConfig): this;
77
99
  /**
78
100
  * Set a callback to be invoked before each retry attempt
79
101
  * Useful for implementing backoff strategies or logging retry attempts.
@@ -89,10 +111,26 @@ export declare abstract class BaseRequest {
89
111
  */
90
112
  onRetry(callback: RetryCallback): this;
91
113
  /**
92
- * Sets credentials policy - supports both direct call and fluent API
114
+ * Sets the credentials policy for the request, controlling whether cookies and authentication
115
+ * headers are sent with cross-origin requests.
116
+ *
117
+ * @param credentialsPolicy - The credentials policy to use:
118
+ * - `"include"` or `CredentialsPolicy.INCLUDE`: Always send credentials (cookies, authorization headers) with the request, even for cross-origin requests.
119
+ * - `"omit"` or `CredentialsPolicy.OMIT`: Never send credentials, even for same-origin requests.
120
+ * - `"same-origin"` or `CredentialsPolicy.SAME_ORIGIN`: Only send credentials for same-origin requests (default behavior in most browsers).
121
+ *
122
+ * @returns The request instance for chaining
123
+ *
93
124
  * @example
125
+ * // Using string values
94
126
  * request.withCredentials("include")
127
+ *
128
+ * @example
129
+ * // Using enum values
95
130
  * request.withCredentials(CredentialsPolicy.INCLUDE)
131
+ *
132
+ * @example
133
+ * // Using fluent API
96
134
  * request.withCredentials.INCLUDE()
97
135
  */
98
136
  get withCredentials(): ((credentialsPolicy: CredentialsPolicy | string) => BaseRequest) & {
@@ -101,22 +139,77 @@ export declare abstract class BaseRequest {
101
139
  SAME_ORIGIN: () => BaseRequest;
102
140
  };
103
141
  /**
104
- * Allows providing an external AbortController to cancel the request
105
- * @param controller - The AbortController to use for this request
106
- * @returns The instance for chaining
142
+ * Allows providing an external AbortController to cancel the request.
143
+ * This is useful when you need to cancel a request from outside the request chain,
144
+ *
145
+ * @param controller - The AbortController to use for this request. When `controller.abort()` is called,
146
+ * the request will be cancelled and throw an abort error.
147
+ *
148
+ * @returns The request instance for chaining
149
+ *
150
+ * @example
151
+ * const controller = new AbortController();
152
+ * const request = createRequest('/api/data')
153
+ * .withAbortController(controller)
154
+ * .getJson();
155
+ *
156
+ * // Later, cancel the request
157
+ * controller.abort();
158
+ *
159
+ * @example
160
+ * // Share abort controller across multiple requests
161
+ * const controller = new AbortController();
162
+ * request1.withAbortController(controller).getJson();
163
+ * request2.withAbortController(controller).getJson();
164
+ * // Aborting will cancel both requests
165
+ * controller.abort();
107
166
  */
108
167
  withAbortController(controller: AbortController): this;
109
168
  /**
110
- * Sets the referrer for the request
111
- * @param referrer - The referrer URL or policy
112
- * @returns The instance for chaining
169
+ * Sets the referrer URL for the request. The referrer is the URL of the page that initiated the request.
170
+ * This can be used to override the default referrer that the browser would normally send.
171
+ *
172
+ * @param referrer - The referrer URL to send with the request. Can be:
173
+ * - A full URL (e.g., "https://example.com/page")
174
+ * - An empty string to omit the referrer
175
+ * - A relative URL (will be resolved relative to the current page)
176
+ *
177
+ * @returns The request instance for chaining
178
+ *
179
+ * @example
180
+ * request.withReferrer("https://example.com/previous-page")
181
+ *
182
+ * @example
183
+ * // Omit referrer
184
+ * request.withReferrer("")
113
185
  */
114
186
  withReferrer(referrer: string): this;
115
187
  /**
116
- * Sets referrer policy - supports both direct call and fluent API
188
+ * Sets the referrer policy for the request, controlling how much referrer information
189
+ * is sent with the request. This helps balance privacy and functionality.
190
+ *
191
+ * @param policy - The referrer policy to use:
192
+ * - `"no-referrer"` or `ReferrerPolicy.NO_REFERRER`: Never send the referrer header.
193
+ * - `"no-referrer-when-downgrade"` or `ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE`: Send full referrer for same-origin or HTTPS→HTTPS, omit for HTTPS→HTTP (default in most browsers).
194
+ * - `"origin"` or `ReferrerPolicy.ORIGIN`: Only send the origin (scheme, host, port), not the full URL.
195
+ * - `"origin-when-cross-origin"` or `ReferrerPolicy.ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, only origin for cross-origin.
196
+ * - `"same-origin"` or `ReferrerPolicy.SAME_ORIGIN`: Send full referrer for same-origin requests only, omit for cross-origin.
197
+ * - `"strict-origin"` or `ReferrerPolicy.STRICT_ORIGIN`: Send origin for HTTPS→HTTPS or HTTP→HTTP, omit for HTTPS→HTTP.
198
+ * - `"strict-origin-when-cross-origin"` or `ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN`: Send full referrer for same-origin, origin for cross-origin HTTPS→HTTPS, omit for HTTPS→HTTP.
199
+ * - `"unsafe-url"` or `ReferrerPolicy.UNSAFE_URL`: Always send the full referrer URL (may leak sensitive information).
200
+ *
201
+ * @returns The request instance for chaining
202
+ *
117
203
  * @example
204
+ * // Using string values
118
205
  * request.withReferrerPolicy("no-referrer")
206
+ *
207
+ * @example
208
+ * // Using enum values
119
209
  * request.withReferrerPolicy(ReferrerPolicy.NO_REFERRER)
210
+ *
211
+ * @example
212
+ * // Using fluent API
120
213
  * request.withReferrerPolicy.NO_REFERRER()
121
214
  */
122
215
  get withReferrerPolicy(): ((policy: ReferrerPolicy | string) => BaseRequest) & {
@@ -130,11 +223,30 @@ export declare abstract class BaseRequest {
130
223
  STRICT_ORIGIN_WHEN_CROSS_ORIGIN: () => BaseRequest;
131
224
  };
132
225
  /**
133
- * Sets redirect mode - supports both direct call and fluent API
226
+ * Sets how the request handles HTTP redirects (3xx status codes).
227
+ *
228
+ * @param redirect - The redirect handling mode:
229
+ * - `"follow"` or `RedirectMode.FOLLOW`: Automatically follow redirects. The fetch will transparently follow redirects and return the final response (default behavior).
230
+ * - `"error"` or `RedirectMode.ERROR`: Treat redirects as errors. If a redirect occurs, the request will fail with an error.
231
+ * - `"manual"` or `RedirectMode.MANUAL`: Return the redirect response without following it. The response will have a `type` of "opaqueredirect" and you can manually handle the redirect.
232
+ *
233
+ * @returns The request instance for chaining
234
+ *
134
235
  * @example
236
+ * // Using string values
135
237
  * request.withRedirect("follow")
238
+ *
239
+ * @example
240
+ * // Using enum values
136
241
  * request.withRedirect(RedirectMode.FOLLOW)
242
+ *
243
+ * @example
244
+ * // Using fluent API
137
245
  * request.withRedirect.FOLLOW()
246
+ *
247
+ * @example
248
+ * // Fail on redirects
249
+ * request.withRedirect.ERROR()
138
250
  */
139
251
  get withRedirect(): ((redirect: RedirectMode | string) => BaseRequest) & {
140
252
  FOLLOW: () => BaseRequest;
@@ -142,17 +254,47 @@ export declare abstract class BaseRequest {
142
254
  MANUAL: () => BaseRequest;
143
255
  };
144
256
  /**
145
- * Sets the keepalive flag for the request
146
- * @param keepalive - Whether to allow the request to outlive the page
147
- * @returns The instance for chaining
257
+ * Sets the keepalive flag for the request. When enabled, the request can continue
258
+ * even after the page that initiated it is closed. This is useful for analytics,
259
+ * logging, or other background requests that should complete even if the user navigates away.
260
+ *
261
+ * @param keepalive - Whether to allow the request to outlive the page:
262
+ * - `true`: The request will continue even if the page is closed or navigated away.
263
+ * - `false`: The request will be cancelled if the page is closed (default).
264
+ *
265
+ * @returns The request instance for chaining
266
+ *
267
+ * @example
268
+ * // Send analytics event that should complete even if user navigates away
269
+ * request.withKeepAlive(true)
148
270
  */
149
271
  withKeepAlive(keepalive: boolean): this;
150
272
  /**
151
- * Sets request priority - supports both direct call and fluent API
273
+ * Sets the priority hint for the request, indicating to the browser how important
274
+ * this request is relative to other requests. This helps the browser optimize resource loading.
275
+ *
276
+ * @param priority - The request priority:
277
+ * - `"high"` or `RequestPriority.HIGH`: High priority - the browser should prioritize this request.
278
+ * - `"low"` or `RequestPriority.LOW`: Low priority - the browser can defer this request if needed.
279
+ * - `"auto"` or `RequestPriority.AUTO`: Automatic priority based on the request type (default).
280
+ *
281
+ * @returns The request instance for chaining
282
+ *
152
283
  * @example
284
+ * // Using string values
153
285
  * request.withPriority("high")
286
+ *
287
+ * @example
288
+ * // Using enum values
154
289
  * request.withPriority(RequestPriority.HIGH)
290
+ *
291
+ * @example
292
+ * // Using fluent API
155
293
  * request.withPriority.HIGH()
294
+ *
295
+ * @example
296
+ * // Low priority for non-critical requests
297
+ * request.withPriority.LOW()
156
298
  */
157
299
  get withPriority(): ((priority: RequestPriority | string) => BaseRequest) & {
158
300
  HIGH: () => BaseRequest;
@@ -160,19 +302,59 @@ export declare abstract class BaseRequest {
160
302
  AUTO: () => BaseRequest;
161
303
  };
162
304
  /**
163
- * Sets the integrity hash for subresource integrity verification
164
- * @param integrity - The integrity hash string (e.g., "sha256-...")
165
- * @returns The instance for chaining
305
+ * Sets the integrity hash for Subresource Integrity (SRI) verification.
306
+ * This allows the browser to verify that the fetched resource hasn't been tampered with
307
+ * by comparing its hash against the provided value. If the hashes don't match, the request fails.
308
+ *
309
+ * @param integrity - The integrity hash string in the format `"algorithm-hash"`:
310
+ * - Example: `"sha256-abcdef1234567890..."` (SHA-256 hash)
311
+ * - Example: `"sha384-abcdef1234567890..."` (SHA-384 hash)
312
+ * - Example: `"sha512-abcdef1234567890..."` (SHA-512 hash)
313
+ * - Multiple hashes can be separated by spaces: `"sha256-... sha384-..."`
314
+ *
315
+ * @returns The request instance for chaining
316
+ *
166
317
  * @example
167
318
  * request.withIntegrity("sha256-abcdef1234567890...")
319
+ *
320
+ * @example
321
+ * // Multiple algorithms for better compatibility
322
+ * request.withIntegrity("sha256-... sha384-...")
168
323
  */
169
324
  withIntegrity(integrity: string): this;
170
325
  /**
171
- * Sets cache mode - supports both direct call and fluent API
326
+ * Sets the cache mode for the request, controlling how the browser's HTTP cache
327
+ * is used for this request.
328
+ *
329
+ * @param cache - The cache mode:
330
+ * - `"default"` or `CacheMode.DEFAULT`: Use the browser's default cache behavior. The browser will check the cache and use it if valid, otherwise fetch from network.
331
+ * - `"no-store"` or `CacheMode.NO_STORE`: Never use the cache and don't store the response in cache. Always fetch from network.
332
+ * - `"reload"` or `CacheMode.RELOAD`: Bypass the cache but store the response. Always fetch from network, ignoring cached responses.
333
+ * - `"no-cache"` or `CacheMode.NO_CACHE`: Check the cache but revalidate with the server. Use cached response only if server confirms it's still valid.
334
+ * - `"force-cache"` or `CacheMode.FORCE_CACHE`: Use the cache if available, even if stale. Only fetch from network if not in cache.
335
+ * - `"only-if-cached"` or `CacheMode.ONLY_IF_CACHED`: Only use the cache. If not in cache, return an error. Never fetch from network.
336
+ *
337
+ * @returns The request instance for chaining
338
+ *
172
339
  * @example
340
+ * // Using string values
173
341
  * request.withCache("no-cache")
342
+ *
343
+ * @example
344
+ * // Using enum values
174
345
  * request.withCache(CacheMode.NO_CACHE)
346
+ *
347
+ * @example
348
+ * // Using fluent API
175
349
  * request.withCache.NO_CACHE()
350
+ *
351
+ * @example
352
+ * // Always fetch fresh data
353
+ * request.withCache.RELOAD()
354
+ *
355
+ * @example
356
+ * // Use cache only, fail if not cached
357
+ * request.withCache.ONLY_IF_CACHED()
176
358
  */
177
359
  get withCache(): ((cache: string) => BaseRequest) & {
178
360
  DEFAULT: () => BaseRequest;
@@ -196,11 +378,32 @@ export declare abstract class BaseRequest {
196
378
  */
197
379
  withQueryParam(key: string, value: string | string[] | number | boolean | null | undefined): this;
198
380
  /**
199
- * Sets request mode - supports both direct call and fluent API
381
+ * Sets the request mode, which determines the CORS (Cross-Origin Resource Sharing) behavior
382
+ * for the request. This controls how the browser handles cross-origin requests.
383
+ *
384
+ * @param mode - The request mode:
385
+ * - `"cors"` or `RequestMode.CORS`: Enable CORS. The browser will send CORS headers and enforce CORS rules. This is the default for most cross-origin requests.
386
+ * - `"no-cors"` or `RequestMode.NO_CORS`: Disable CORS. The request is sent as a "simple" request without CORS headers. The response will be opaque (you can't read it).
387
+ * - `"same-origin"` or `RequestMode.SAME_ORIGIN`: Only allow same-origin requests. Cross-origin requests will fail.
388
+ * - `"navigate"` or `RequestMode.NAVIGATE`: Used for navigation requests (typically only used by the browser itself).
389
+ *
390
+ * @returns The request instance for chaining
391
+ *
200
392
  * @example
393
+ * // Using string values
201
394
  * request.withMode("cors")
395
+ *
396
+ * @example
397
+ * // Using enum values
202
398
  * request.withMode(RequestMode.CORS)
399
+ *
400
+ * @example
401
+ * // Using fluent API
203
402
  * request.withMode.CORS()
403
+ *
404
+ * @example
405
+ * // Restrict to same-origin only
406
+ * request.withMode.SAME_ORIGIN()
204
407
  */
205
408
  get withMode(): ((mode: RequestMode | string) => BaseRequest) & {
206
409
  CORS: () => BaseRequest;