@rutansh0101/fetchify 1.0.0 → 1.0.3

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -1091
  3. package/fetchify.js +7 -0
  4. package/package.json +1 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rutansh Chawla
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,907 +1,191 @@
1
1
  # Fetchify - A Lightweight HTTP Client Library
2
2
 
3
- Fetchify is a modern, lightweight HTTP client library built on top of the native Fetch API. It provides a clean, axios-like interface with support for interceptors, request/response transformation, timeout handling, and more.
3
+ [![npm version](https://img.shields.io/npm/v/@rutansh0101/fetchify.svg)](https://www.npmjs.com/package/@rutansh0101/fetchify)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@rutansh0101/fetchify.svg)](https://www.npmjs.com/package/@rutansh0101/fetchify)
5
+ [![license](https://img.shields.io/npm/l/@rutansh0101/fetchify.svg)](https://github.com/Rutansh0101/Fetchify-Rutansh-Chawla/blob/master/LICENSE)
4
6
 
5
- ---
6
-
7
- ## Table of Contents
8
-
9
- 1. [Installation & Setup](#installation--setup)
10
- 2. [Basic Usage](#basic-usage)
11
- 3. [Features Overview](#features-overview)
12
- 4. [Detailed Feature Explanation](#detailed-feature-explanation)
13
- - [Instance Creation](#1-instance-creation)
14
- - [HTTP Methods](#2-http-methods)
15
- - [Configuration Management](#3-configuration-management)
16
- - [Timeout Handling](#4-timeout-handling)
17
- - [Request Interceptors](#5-request-interceptors)
18
- - [Response Interceptors](#6-response-interceptors)
19
- - [Interceptor Chain Execution](#7-interceptor-chain-execution)
20
- 5. [Architecture & Design Decisions](#architecture--design-decisions)
21
- 6. [Code Examples](#code-examples)
22
- 7. [Error Handling](#error-handling)
23
- 8. [API Reference](#api-reference)
7
+ A modern, lightweight HTTP client library built on top of the native Fetch API. It provides a clean, axios-like interface with support for interceptors, request/response transformation, and timeout handling.
24
8
 
25
9
  ---
26
10
 
27
- ## Installation & Setup
28
-
29
- ### Prerequisites
30
- - Node.js environment with ES6+ module support
31
- - Modern browser with Fetch API support
32
-
33
- ### Setup
34
- 1. Clone or download the project
35
- 2. Ensure your project supports ES6 modules (add `"type": "module"` in package.json)
36
- 3. Import Fetchify in your code:
11
+ ## Installation
37
12
 
38
- ```javascript
39
- import fetchify from "./fetchify.js";
13
+ ```bash
14
+ npm install @rutansh0101/fetchify
40
15
  ```
41
16
 
42
- ---
43
-
44
- ## Basic Usage
45
-
46
- ### Creating an Instance
17
+ ## Quick Start
47
18
 
48
19
  ```javascript
49
- import fetchify from "./fetchify.js";
20
+ import fetchify from '@rutansh0101/fetchify';
50
21
 
51
- // Create an instance with base configuration
22
+ // Create an instance
52
23
  const api = fetchify.create({
53
24
  baseURL: 'https://api.example.com',
54
25
  timeout: 5000,
55
26
  headers: {
56
- 'Content-Type': 'application/json',
57
- 'Authorization': 'Bearer YOUR_TOKEN'
27
+ 'Content-Type': 'application/json'
58
28
  }
59
29
  });
60
- ```
61
30
 
62
- ### Making Requests
63
-
64
- ```javascript
65
- // GET request
31
+ // Make requests
66
32
  const response = await api.get('/users');
67
33
  const users = await response.json();
68
-
69
- // POST request
70
- const newUser = await api.post('/users', {
71
- body: JSON.stringify({ name: 'John', email: 'john@example.com' })
72
- });
73
-
74
- // PUT request
75
- const updated = await api.put('/users/1', {
76
- body: JSON.stringify({ name: 'John Updated' })
77
- });
78
-
79
- // PATCH request
80
- const patched = await api.patch('/users/1', {
81
- body: JSON.stringify({ email: 'newemail@example.com' })
82
- });
83
-
84
- // DELETE request
85
- await api.delete('/users/1');
86
34
  ```
87
35
 
88
36
  ---
89
37
 
90
- ## Features Overview
38
+ ## Features
91
39
 
92
- ✅ **Axios-like API** - Familiar interface for developers coming from Axios
93
- ✅ **Request/Response Interceptors** - Transform requests and responses globally
40
+ ✅ **Axios-like API** - Familiar and easy to use
41
+ ✅ **Request/Response Interceptors** - Transform requests and responses
94
42
  ✅ **Timeout Support** - Abort requests after specified duration
95
43
  ✅ **Configuration Merging** - Instance, request-level, and default configs
96
- ✅ **All HTTP Methods** - GET, POST, PUT, PATCH, DELETE support
97
- ✅ **AbortController Integration** - Native request cancellation
98
- ✅ **Promise Chain Architecture** - Clean async operation handling
99
- ✅ **Error Transformation** - Custom error messages for timeouts and failures
44
+ ✅ **All HTTP Methods** - GET, POST, PUT, PATCH, DELETE
45
+ ✅ **Lightweight** - ~5KB with zero dependencies
100
46
 
101
47
  ---
102
48
 
103
- ## Detailed Feature Explanation
104
-
105
- ### 1. Instance Creation
106
-
107
- **What it does:**
108
- Creates a reusable HTTP client instance with shared configuration.
109
-
110
- **Why it's needed:**
111
- - Avoid repeating baseURL, headers, and timeout in every request
112
- - Create multiple instances for different APIs (e.g., one for auth, one for data)
113
- - Centralize common configuration
114
-
115
- **How it works:**
116
-
117
- ```javascript
118
- const create = (config) => {
119
- return new Fetchify(config);
120
- }
121
- ```
122
-
123
- The `create` method:
124
- 1. Accepts a configuration object
125
- 2. Instantiates the Fetchify class
126
- 3. Merges user config with default config
127
- 4. Returns the configured instance
128
-
129
- **Implementation Details:**
130
-
131
- ```javascript
132
- constructor(newConfig) {
133
- this.config = this.#mergeConfig(newConfig);
134
- }
135
- ```
136
-
137
- The constructor:
138
- - Calls `#mergeConfig` to combine default config with user-provided config
139
- - Stores the merged config in `this.config`
140
- - This config becomes the base for all future requests
141
-
142
- **Default Configuration:**
143
-
144
- ```javascript
145
- config = {
146
- headers: {
147
- 'Content-Type': 'application/json'
148
- },
149
- timeout: 1000 // 1 second default timeout
150
- };
151
- ```
152
-
153
- ---
154
-
155
- ### 2. HTTP Methods
156
-
157
- **What it does:**
158
- Provides convenient methods for different HTTP verbs (GET, POST, PUT, PATCH, DELETE).
159
-
160
- **Why it's needed:**
161
- - Simplifies request syntax
162
- - Makes code more readable (`api.get()` vs `api.request({ method: 'GET' })`)
163
- - Follows REST conventions
49
+ ## Basic Usage
164
50
 
165
- **How it works:**
51
+ ### Making Requests
166
52
 
167
53
  ```javascript
168
- async get(endPoint, tempConfig = {}) {
169
- return this.#request({
170
- endPoint,
171
- config: { ...tempConfig, method: 'GET' }
172
- });
173
- }
174
- ```
175
-
176
- Each method:
177
- 1. Takes an endpoint and optional temporary config
178
- 2. Merges the HTTP method into the config
179
- 3. Calls the private `#request` method
180
- 4. Returns a Promise that resolves to the fetch Response object
181
-
182
- **All Methods:**
183
-
184
- - **GET**: Retrieve data (no body allowed by HTTP spec)
185
- - **POST**: Create new resources (requires body)
186
- - **PUT**: Update entire resources (requires body)
187
- - **PATCH**: Partially update resources (requires body)
188
- - **DELETE**: Remove resources (usually no body)
189
-
190
- **Example with Body:**
54
+ // GET request
55
+ const response = await api.get('/users');
56
+ const data = await response.json();
191
57
 
192
- ```javascript
58
+ // POST request
193
59
  await api.post('/users', {
194
- body: JSON.stringify({ name: 'John' }),
195
- headers: { 'Custom-Header': 'value' }
60
+ body: JSON.stringify({ name: 'John', email: 'john@example.com' })
196
61
  });
197
- ```
198
-
199
- ---
200
-
201
- ### 3. Configuration Management
202
-
203
- **What it does:**
204
- Handles merging of configurations at three levels: default, instance, and request.
205
-
206
- **Why it's needed:**
207
- - Allow global defaults that can be overridden
208
- - Support instance-specific configs (different baseURLs)
209
- - Enable request-specific overrides (custom timeout for one request)
210
-
211
- **Configuration Priority (highest to lowest):**
212
- 1. Request-level config (passed to `get()`, `post()`, etc.)
213
- 2. Instance-level config (passed to `create()`)
214
- 3. Default config (hardcoded in the class)
215
-
216
- **How it works:**
217
-
218
- ```javascript
219
- #mergeConfig(newConfig) {
220
- return {
221
- ...this.config, // Existing config
222
- ...newConfig, // New config (overrides existing)
223
- headers: { // Deep merge for headers
224
- ...this.config.headers,
225
- ...newConfig?.headers
226
- }
227
- };
228
- }
229
- ```
230
-
231
- **Why Deep Merge for Headers?**
232
-
233
- Without deep merge:
234
- ```javascript
235
- // Instance config
236
- headers: { 'Authorization': 'Bearer token', 'Content-Type': 'application/json' }
237
-
238
- // Request config
239
- headers: { 'X-Custom': 'value' }
240
-
241
- // Result WITHOUT deep merge (WRONG):
242
- headers: { 'X-Custom': 'value' } // Lost Authorization!
243
62
 
244
- // Result WITH deep merge (CORRECT):
245
- headers: {
246
- 'Authorization': 'Bearer token',
247
- 'Content-Type': 'application/json',
248
- 'X-Custom': 'value'
249
- }
250
- ```
251
-
252
- **Two Merge Methods:**
253
-
254
- 1. `#mergeConfig(newConfig)` - Merges with instance config (`this.config`)
255
- 2. `#mergeConfigs(config1, config2)` - Merges two arbitrary configs
256
-
257
- ---
258
-
259
- ### 4. Timeout Handling
260
-
261
- **What it does:**
262
- Automatically aborts requests that take longer than specified duration.
263
-
264
- **Why it's needed:**
265
- - Prevent requests from hanging indefinitely
266
- - Improve user experience with faster feedback
267
- - Handle slow/unresponsive servers gracefully
268
- - Free up resources from stalled connections
269
-
270
- **How it works:**
271
-
272
- ```javascript
273
- // 1. Create AbortController
274
- const abortController = new AbortController();
275
- const timeout = config.timeout || this.config.timeout || 0;
276
-
277
- // 2. Set timer to abort
278
- let timeOutId;
279
- if (timeout) {
280
- timeOutId = setTimeout(() => {
281
- abortController.abort(); // Trigger abort after timeout
282
- }, timeout);
283
- }
284
-
285
- // 3. Link signal to fetch
286
- config.signal = abortController.signal;
287
-
288
- // 4. Execute fetch
289
- const response = await fetch(url, config);
290
-
291
- // 5. Clear timer in finally block
292
- finally {
293
- if (timeOutId) {
294
- clearTimeout(timeOutId); // Prevent memory leak
295
- }
296
- }
297
- ```
298
-
299
- **Why Use AbortController?**
300
-
301
- - Native browser API for cancelling fetch requests
302
- - Cleaner than old XMLHttpRequest cancellation
303
- - Works across all modern browsers
304
- - Can abort multiple operations with one controller
63
+ // PUT request
64
+ await api.put('/users/1', {
65
+ body: JSON.stringify({ name: 'Jane Doe' })
66
+ });
305
67
 
306
- **Timeout Flow:**
68
+ // PATCH request
69
+ await api.patch('/users/1', {
70
+ body: JSON.stringify({ email: 'jane@example.com' })
71
+ });
307
72
 
308
- ```
309
- Request Start
310
-
311
- Set Timeout Timer (e.g., 5000ms)
312
-
313
- Start Fetch Request
314
-
315
- ├─→ Response arrives in 2000ms → Clear Timer → Return Response ✓
316
-
317
- └─→ 5000ms passes → Timer fires → Abort Signal → Fetch throws AbortError ✗
73
+ // DELETE request
74
+ await api.delete('/users/1');
318
75
  ```
319
76
 
320
- **Why Clear Timeout?**
321
-
322
- ```javascript
323
- // Without clearing:
324
- setTimeout(() => abortController.abort(), 5000);
325
- // If request finishes in 2s, timer still exists in memory until 5s
326
- // With 1000 requests, you have 1000 timers lingering!
327
-
328
- // With clearing:
329
- finally {
330
- clearTimeout(timeOutId); // Immediately free memory when done
331
- }
332
- ```
77
+ ### Request Configuration
333
78
 
334
- **Error Handling:**
79
+ Override default settings per request:
335
80
 
336
81
  ```javascript
337
- catch (error) {
338
- if (error.name === 'AbortError') {
339
- throw new Error(`Request to ${endPoint} aborted due to timeout after ${timeout} ms`);
340
- } else {
341
- throw error;
82
+ const response = await api.get('/users', {
83
+ timeout: 10000,
84
+ headers: {
85
+ 'Authorization': 'Bearer token123'
342
86
  }
343
- }
87
+ });
344
88
  ```
345
89
 
346
- The code checks `error.name === 'AbortError'` to distinguish timeout aborts from other errors.
347
-
348
90
  ---
349
91
 
350
- ### 5. Request Interceptors
351
-
352
- **What it does:**
353
- Intercepts and modifies requests before they are sent to the server.
92
+ ## Interceptors
354
93
 
355
- **Why it's needed:**
356
- - Add authentication tokens to all requests
357
- - Log outgoing requests for debugging
358
- - Modify request data (transform, encrypt, compress)
359
- - Add timestamps or request IDs
360
- - Implement retry logic
94
+ ### Request Interceptors
361
95
 
362
- **How it works:**
96
+ Modify requests before they are sent:
363
97
 
364
98
  ```javascript
365
- // Adding an interceptor
366
99
  api.addRequestInterceptor(
367
100
  (config) => {
368
- // Success handler: runs for every request
369
- console.log('Sending request to:', config.endPoint);
370
- config.config.headers['X-Request-Time'] = Date.now();
371
- return config; // Must return config
372
- },
373
- (error) => {
374
- // Error handler: runs if previous interceptor failed
375
- console.error('Request interceptor error:', error);
376
- return Promise.reject(error);
377
- }
378
- );
379
- ```
380
-
381
- **Storage:**
382
-
383
- ```javascript
384
- requestInterceptors = []; // Array of { successHandler, errorHandler }
385
-
386
- addRequestInterceptor(successHandler, errorHandler) {
387
- this.requestInterceptors.push({ successHandler, errorHandler });
388
- }
389
- ```
390
-
391
- **Execution in Chain:**
392
-
393
- ```javascript
394
- const chain = [
395
- ...this.requestInterceptors, // All request interceptors
396
- { successHandler: this.#dispatchRequest.bind(this) }, // Actual fetch
397
- ...this.responseInterceptors // All response interceptors
398
- ];
399
- ```
400
-
401
- Request interceptors run **before** the actual fetch call.
402
-
403
- **Input/Output Format:**
404
-
405
- - **Input**: `{ endPoint: '/users', config: { method: 'GET', ... } }`
406
- - **Output**: Must return same format (modified or not)
407
-
408
- **Common Use Cases:**
409
-
410
- ```javascript
411
- // 1. Add authentication
412
- api.addRequestInterceptor((config) => {
413
- const token = localStorage.getItem('token');
414
- config.config.headers['Authorization'] = `Bearer ${token}`;
415
- return config;
416
- });
417
-
418
- // 2. Log requests
419
- api.addRequestInterceptor((config) => {
420
- console.log(`[${config.config.method}] ${config.endPoint}`);
421
- return config;
422
- });
423
-
424
- // 3. Transform request body
425
- api.addRequestInterceptor((config) => {
426
- if (config.config.body) {
427
- // Add timestamp to all POST requests
428
- const body = JSON.parse(config.config.body);
429
- body.timestamp = Date.now();
430
- config.config.body = JSON.stringify(body);
431
- }
432
- return config;
433
- });
434
- ```
435
-
436
- ---
437
-
438
- ### 6. Response Interceptors
439
-
440
- **What it does:**
441
- Intercepts and modifies responses before they reach your application code.
442
-
443
- **Why it's needed:**
444
- - Transform response data globally
445
- - Handle errors consistently (e.g., redirect on 401)
446
- - Log responses for debugging
447
- - Extract data from nested response structures
448
- - Implement global retry logic
449
-
450
- **How it works:**
451
-
452
- ```javascript
453
- // Adding an interceptor
454
- api.addResponseInterceptor(
455
- (response) => {
456
- // Success handler: runs for successful responses
457
- console.log('Response status:', response.status);
458
-
459
- if (!response.ok) {
460
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
461
- }
462
-
463
- return response; // Must return response
101
+ // Add authentication token
102
+ const token = localStorage.getItem('authToken');
103
+ config.config.headers['Authorization'] = `Bearer ${token}`;
104
+ return config;
464
105
  },
465
106
  (error) => {
466
- // Error handler: runs on fetch errors or if previous interceptor threw
467
- console.error('Response error:', error);
468
-
469
- if (error.message.includes('timeout')) {
470
- alert('Request timed out. Please try again.');
471
- }
472
-
473
107
  return Promise.reject(error);
474
108
  }
475
109
  );
476
110
  ```
477
111
 
478
- **Storage:**
479
-
480
- ```javascript
481
- responseInterceptors = []; // Array of { successHandler, errorHandler }
482
-
483
- addResponseInterceptor(successHandler, errorHandler) {
484
- this.responseInterceptors.push({ successHandler, errorHandler });
485
- }
486
- ```
487
-
488
- **Execution in Chain:**
112
+ ### Response Interceptors
489
113
 
490
- Response interceptors run **after** the fetch completes.
491
-
492
- **Input/Output Format:**
493
-
494
- - **Input**: Native fetch `Response` object
495
- - **Output**: Must return `Response` object (or modified version)
496
-
497
- **Common Use Cases:**
114
+ Handle responses globally:
498
115
 
499
116
  ```javascript
500
- // 1. Auto-logout on 401
501
117
  api.addResponseInterceptor(
502
118
  (response) => {
119
+ // Handle unauthorized responses
503
120
  if (response.status === 401) {
504
- localStorage.removeItem('token');
505
121
  window.location.href = '/login';
506
122
  }
507
123
  return response;
508
- }
509
- );
510
-
511
- // 2. Parse all responses as JSON
512
- api.addResponseInterceptor(
513
- async (response) => {
514
- const data = await response.json();
515
- // Return modified response with parsed data
516
- response.data = data;
517
- return response;
518
- }
519
- );
520
-
521
- // 3. Handle rate limiting
522
- api.addResponseInterceptor(
523
- (response) => {
524
- if (response.status === 429) {
525
- const retryAfter = response.headers.get('Retry-After');
526
- throw new Error(`Rate limited. Retry after ${retryAfter} seconds`);
527
- }
528
- return response;
529
- }
530
- );
531
-
532
- // 4. Log all responses
533
- api.addResponseInterceptor(
534
- (response) => {
535
- console.log(`[${response.status}] Response received`);
536
- return response;
537
- }
538
- );
539
- ```
540
-
541
- ---
542
-
543
- ### 7. Interceptor Chain Execution
544
-
545
- **What it does:**
546
- Executes all interceptors and the fetch request in a sequential promise chain.
547
-
548
- **Why it's needed:**
549
- - Ensure interceptors run in order (request → fetch → response)
550
- - Handle errors at any stage
551
- - Allow each interceptor to modify data for the next
552
- - Maintain clean async flow
553
-
554
- **The Chain Structure:**
555
-
556
- ```javascript
557
- const chain = [
558
- ...this.requestInterceptors, // [interceptor1, interceptor2, ...]
559
- { successHandler: this.#dispatchRequest.bind(this) }, // The actual fetch
560
- ...this.responseInterceptors // [interceptor3, interceptor4, ...]
561
- ];
562
- ```
563
-
564
- **Visual Representation:**
565
-
566
- ```
567
- Initial Promise.resolve({ endPoint, config })
568
-
569
- Request Interceptor 1 (success/error)
570
-
571
- Request Interceptor 2 (success/error)
572
-
573
- #dispatchRequest (actual fetch)
574
-
575
- Response Interceptor 1 (success/error)
576
-
577
- Response Interceptor 2 (success/error)
578
-
579
- Final Result returned to user
580
- ```
581
-
582
- **Chain Building Code:**
583
-
584
- ```javascript
585
- let promise = Promise.resolve({ endPoint, config: finalConfig });
586
-
587
- for (const { successHandler, errorHandler } of chain) {
588
- promise = promise.then(
589
- (responseOfPrevPromise) => {
590
- try {
591
- return successHandler(responseOfPrevPromise);
592
- } catch (error) {
593
- if (errorHandler) {
594
- return errorHandler(error);
595
- } else {
596
- return Promise.reject(error);
597
- }
598
- }
599
- },
600
- (error) => {
601
- if (errorHandler) {
602
- return errorHandler(error);
603
- } else {
604
- return Promise.reject(error);
605
- }
606
- }
607
- );
608
- }
609
-
610
- return promise;
611
- ```
612
-
613
- **How It Works Step-by-Step:**
614
-
615
- 1. **Initial Promise**: Starts with `{ endPoint, config }`
616
-
617
- 2. **Iteration**: For each interceptor in the chain:
618
- - Attach `.then()` to the promise
619
- - Pass output of previous step as input to next
620
-
621
- 3. **Success Path**: `then(successHandler, errorHandler)`
622
- - If previous promise resolved → `successHandler` runs
623
- - Output becomes input for next interceptor
624
-
625
- 4. **Error Path**: Second parameter of `.then()`
626
- - If previous promise rejected → `errorHandler` runs
627
- - Can recover (return value) or propagate (reject)
628
-
629
- 5. **Try-Catch in Success Handler**:
630
- - If `successHandler` throws synchronous error
631
- - Catch it and pass to `errorHandler`
632
- - This handles errors that don't return rejected promises
633
-
634
- **Example Flow:**
635
-
636
- ```javascript
637
- // User code
638
- const response = await api.get('/users');
639
-
640
- // What happens:
641
-
642
- // Step 1: Initial promise
643
- Promise.resolve({ endPoint: '/users', config: { method: 'GET' } })
644
-
645
- // Step 2: Request Interceptor 1
646
- .then(config => {
647
- config.config.headers['Auth'] = 'token';
648
- return config; // { endPoint: '/users', config: { method: 'GET', headers: {...} } }
649
- })
650
-
651
- // Step 3: Request Interceptor 2
652
- .then(config => {
653
- console.log('Logging request');
654
- return config; // Same object passed through
655
- })
656
-
657
- // Step 4: Dispatch Request (actual fetch)
658
- .then(async config => {
659
- const response = await fetch(config.config.baseURL + config.endPoint, config.config);
660
- return response; // Native Response object
661
- })
662
-
663
- // Step 5: Response Interceptor 1
664
- .then(response => {
665
- if (!response.ok) throw new Error('Bad response');
666
- return response; // Response object passed through
667
- })
668
-
669
- // Step 6: Response Interceptor 2
670
- .then(response => {
671
- console.log('Response received:', response.status);
672
- return response; // Final Response object
673
- })
674
-
675
- // Result: User gets the Response object
676
- ```
677
-
678
- **Error Handling Example:**
679
-
680
- ```javascript
681
- // If Request Interceptor throws:
682
- .then(config => {
683
- throw new Error('Invalid token'); // Sync error
684
- })
685
-
686
- // Caught by try-catch, passed to errorHandler:
687
- catch (error) {
688
- if (errorHandler) {
689
- return errorHandler(error); // Can recover
690
- } else {
691
- return Promise.reject(error); // Propagate
692
- }
693
- }
694
-
695
- // If fetch fails (network error, timeout):
696
- .then(async config => {
697
- const response = await fetch(...); // Throws on timeout
698
- return response;
699
- })
700
-
701
- // Second parameter of .then() catches it:
702
- .then(successHandler, (error) => {
703
- if (errorHandler) {
704
- return errorHandler(error);
705
- } else {
124
+ },
125
+ (error) => {
126
+ console.error('Request failed:', error);
706
127
  return Promise.reject(error);
707
128
  }
708
- })
709
- ```
710
-
711
- **Why Use This Pattern?**
712
-
713
- 1. **Sequential Execution**: Each interceptor waits for previous one
714
- 2. **Error Recovery**: Interceptors can catch and fix errors
715
- 3. **Immutable Chain**: Original promise isn't modified
716
- 4. **Type Safety**: Each step expects specific input/output format
717
- 5. **Debugging**: Easy to add console.logs at each step
718
-
719
- **Key Points:**
720
-
721
- - Interceptors must return a value (config or response)
722
- - Errors can be caught and handled at any point
723
- - The chain is built once per request
724
- - All interceptors share the same promise chain
725
- - `bind(this)` ensures `#dispatchRequest` maintains class context
726
-
727
- ---
728
-
729
- ## Architecture & Design Decisions
730
-
731
- ### Why Classes?
732
-
733
- - Encapsulation of state (config, interceptors)
734
- - Private methods (#) for internal logic
735
- - Instance creation for multiple API clients
736
- - Clear separation of concerns
737
-
738
- ### Why Private Methods (#)?
739
-
740
- ```javascript
741
- #request()
742
- #dispatchRequest()
743
- #mergeConfig()
744
- #mergeConfigs()
745
- ```
746
-
747
- - Hide implementation details
748
- - Prevent external modification
749
- - Clear API surface (only public methods exposed)
750
- - Follows encapsulation principles
751
-
752
- ### Why Async/Await in #dispatchRequest?
753
-
754
- ```javascript
755
- // Way 1: Promise chain
756
- return fetch(url, config).finally(() => clearTimeout(timeOutId));
757
-
758
- // Way 2: Async/Await (chosen)
759
- try {
760
- const response = await fetch(url, config);
761
- return response;
762
- } finally {
763
- clearTimeout(timeOutId);
764
- }
765
- ```
766
-
767
- **Chosen Way 2 because:**
768
- - More readable for error handling
769
- - Finally block guaranteed to run
770
- - Easier to debug with stack traces
771
- - Synchronous-looking async code
772
-
773
- ### Why Bind Context?
774
-
775
- ```javascript
776
- successHandler: this.#dispatchRequest.bind(this)
777
- ```
778
-
779
- Without `bind(this)`:
780
- ```javascript
781
- // Inside interceptor chain loop
782
- successHandler(config); // 'this' is undefined in #dispatchRequest!
783
- ```
784
-
785
- With `bind(this)`:
786
- ```javascript
787
- // 'this' correctly refers to Fetchify instance
788
- this.config, this.requestInterceptors, etc. are accessible
129
+ );
789
130
  ```
790
131
 
791
- ### Why Two Merge Methods?
792
-
793
- 1. **#mergeConfig(newConfig)**: Merges with `this.config`
794
- - Used in constructor and #request
795
- - Instance-specific merging
796
-
797
- 2. **#mergeConfigs(config1, config2)**: Merges two arbitrary configs
798
- - Used in #dispatchRequest
799
- - Doesn't depend on instance state
800
-
801
- Separation provides flexibility and reusability.
802
-
803
132
  ---
804
133
 
805
- ## Code Examples
134
+ ## Advanced Examples
806
135
 
807
- ### Complete Example with All Features
136
+ ### Complete Setup with Authentication
808
137
 
809
138
  ```javascript
810
- import fetchify from "./fetchify.js";
139
+ import fetchify from '@rutansh0101/fetchify';
811
140
 
812
- // 1. Create instance with base config
813
141
  const api = fetchify.create({
814
142
  baseURL: 'https://api.example.com',
815
143
  timeout: 5000,
816
144
  headers: {
817
- 'Content-Type': 'application/json',
818
- 'X-API-Version': 'v1'
145
+ 'Content-Type': 'application/json'
819
146
  }
820
147
  });
821
148
 
822
- // 2. Add request interceptor for auth
823
- api.addRequestInterceptor(
824
- (config) => {
825
- const token = localStorage.getItem('authToken');
826
- if (token) {
827
- config.config.headers['Authorization'] = `Bearer ${token}`;
828
- }
829
- console.log('→ Request:', config.config.method, config.endPoint);
830
- return config;
831
- },
832
- (error) => {
833
- console.error('Request interceptor failed:', error);
834
- return Promise.reject(error);
149
+ // Add auth token to all requests
150
+ api.addRequestInterceptor((config) => {
151
+ const token = localStorage.getItem('token');
152
+ if (token) {
153
+ config.config.headers['Authorization'] = `Bearer ${token}`;
835
154
  }
836
- );
155
+ return config;
156
+ });
837
157
 
838
- // 3. Add response interceptor for error handling
158
+ // Handle errors globally
839
159
  api.addResponseInterceptor(
840
160
  (response) => {
841
- console.log('← Response:', response.status, response.statusText);
842
-
843
161
  if (!response.ok) {
844
- if (response.status === 401) {
845
- localStorage.removeItem('authToken');
846
- window.location.href = '/login';
847
- }
848
162
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
849
163
  }
850
-
851
164
  return response;
852
165
  },
853
166
  (error) => {
854
167
  if (error.message.includes('timeout')) {
855
- alert('Request timed out. Please check your connection.');
168
+ alert('Request timed out. Please try again.');
856
169
  }
857
170
  return Promise.reject(error);
858
171
  }
859
172
  );
860
173
 
861
- // 4. Make requests
862
- async function example() {
174
+ // Use the API
175
+ async function fetchUsers() {
863
176
  try {
864
- // GET request
865
- const usersResponse = await api.get('/users');
866
- const users = await usersResponse.json();
867
- console.log('Users:', users);
868
-
869
- // POST request with custom timeout
870
- const newUser = await api.post('/users', {
871
- body: JSON.stringify({
872
- name: 'John Doe',
873
- email: 'john@example.com'
874
- }),
875
- timeout: 10000 // Override default timeout
876
- });
877
- const created = await newUser.json();
878
- console.log('Created user:', created);
879
-
880
- // PUT request
881
- const updateResponse = await api.put('/users/1', {
882
- body: JSON.stringify({ name: 'Jane Doe' })
883
- });
884
-
885
- // PATCH request
886
- const patchResponse = await api.patch('/users/1', {
887
- body: JSON.stringify({ email: 'jane@example.com' })
888
- });
889
-
890
- // DELETE request
891
- await api.delete('/users/1');
892
-
177
+ const response = await api.get('/users');
178
+ const users = await response.json();
179
+ console.log(users);
893
180
  } catch (error) {
894
- console.error('Request failed:', error.message);
181
+ console.error('Failed to fetch users:', error);
895
182
  }
896
183
  }
897
-
898
- example();
899
184
  ```
900
185
 
901
186
  ### Multiple API Instances
902
187
 
903
188
  ```javascript
904
- // Different APIs with different configs
905
189
  const mainAPI = fetchify.create({
906
190
  baseURL: 'https://api.example.com',
907
191
  timeout: 5000
@@ -912,349 +196,93 @@ const authAPI = fetchify.create({
912
196
  timeout: 10000
913
197
  });
914
198
 
915
- const analyticsAPI = fetchify.create({
916
- baseURL: 'https://analytics.example.com',
917
- timeout: 3000,
918
- headers: {
919
- 'X-Analytics-Key': 'secret123'
920
- }
921
- });
922
-
923
- // Use them independently
199
+ // Use independently
924
200
  await mainAPI.get('/users');
925
201
  await authAPI.post('/login', { body: credentials });
926
- await analyticsAPI.post('/track', { body: event });
927
202
  ```
928
203
 
929
204
  ---
930
205
 
931
206
  ## Error Handling
932
207
 
933
- ### Types of Errors
934
-
935
- 1. **Network Errors**: No internet, DNS failure, etc.
936
- 2. **Timeout Errors**: Request exceeded timeout duration
937
- 3. **HTTP Errors**: 4xx, 5xx status codes
938
- 4. **Interceptor Errors**: Thrown by custom interceptor logic
939
-
940
- ### Error Handling Pattern
941
-
942
208
  ```javascript
943
- async function safeRequest() {
944
- try {
945
- const response = await api.get('/users', { timeout: 2000 });
946
-
947
- // Check HTTP status
948
- if (!response.ok) {
949
- throw new Error(`HTTP ${response.status}`);
950
- }
951
-
952
- const data = await response.json();
953
- return data;
954
-
955
- } catch (error) {
956
- // Timeout error
957
- if (error.message.includes('timeout')) {
958
- console.error('Request timed out');
959
- return null;
960
- }
961
-
962
- // Network error
963
- if (error.message.includes('fetch')) {
964
- console.error('Network error');
965
- return null;
966
- }
967
-
968
- // Other errors
969
- console.error('Unknown error:', error);
970
- throw error;
209
+ try {
210
+ const response = await api.get('/users', { timeout: 3000 });
211
+
212
+ if (!response.ok) {
213
+ throw new Error(`HTTP ${response.status}`);
971
214
  }
972
- }
973
- ```
974
-
975
- ### Global Error Handling with Interceptors
976
-
977
- ```javascript
978
- api.addResponseInterceptor(
979
- (response) => {
980
- // Handle specific status codes globally
981
- switch(response.status) {
982
- case 401:
983
- console.error('Unauthorized - redirecting to login');
984
- window.location.href = '/login';
985
- break;
986
- case 403:
987
- console.error('Forbidden - insufficient permissions');
988
- break;
989
- case 404:
990
- console.error('Resource not found');
991
- break;
992
- case 500:
993
- console.error('Server error');
994
- break;
995
- }
996
- return response;
997
- },
998
- (error) => {
999
- // Log all errors globally
1000
- console.error('[Global Error Handler]', error);
1001
-
1002
- // Send error to monitoring service
1003
- // sendToSentry(error);
1004
-
1005
- return Promise.reject(error);
215
+
216
+ const data = await response.json();
217
+ return data;
218
+
219
+ } catch (error) {
220
+ if (error.message.includes('timeout')) {
221
+ console.error('Request timed out');
222
+ } else {
223
+ console.error('Request failed:', error);
1006
224
  }
1007
- );
225
+ }
1008
226
  ```
1009
227
 
1010
228
  ---
1011
229
 
1012
230
  ## API Reference
1013
231
 
1014
- ### fetchify.create(config)
232
+ ### `fetchify.create(config)`
1015
233
 
1016
234
  Creates a new Fetchify instance.
1017
235
 
1018
- **Parameters:**
1019
- - `config` (Object): Configuration object
1020
- - `baseURL` (String): Base URL for all requests
1021
- - `timeout` (Number): Default timeout in milliseconds
1022
- - `headers` (Object): Default headers for all requests
1023
-
1024
- **Returns:** Fetchify instance
1025
-
1026
- **Example:**
1027
- ```javascript
1028
- const api = fetchify.create({
1029
- baseURL: 'https://api.example.com',
1030
- timeout: 5000,
1031
- headers: { 'Authorization': 'Bearer token' }
1032
- });
1033
- ```
1034
-
1035
- ---
236
+ **Config Options:**
237
+ - `baseURL` (String) - Base URL for all requests
238
+ - `timeout` (Number) - Default timeout in milliseconds (default: 1000)
239
+ - `headers` (Object) - Default headers
1036
240
 
1037
- ### instance.get(endpoint, config)
241
+ ### HTTP Methods
1038
242
 
1039
- Performs a GET request.
243
+ All methods return a Promise that resolves to the fetch Response object.
1040
244
 
1041
- **Parameters:**
1042
- - `endpoint` (String): API endpoint (appended to baseURL)
1043
- - `config` (Object, optional): Request-specific config
1044
- - `timeout` (Number): Override default timeout
1045
- - `headers` (Object): Additional headers
1046
- - Any other fetch options
245
+ - `instance.get(endpoint, config)` - GET request
246
+ - `instance.post(endpoint, config)` - POST request
247
+ - `instance.put(endpoint, config)` - PUT request
248
+ - `instance.patch(endpoint, config)` - PATCH request
249
+ - `instance.delete(endpoint, config)` - DELETE request
1047
250
 
1048
- **Returns:** Promise<Response>
251
+ ### Interceptors
1049
252
 
1050
- **Example:**
1051
- ```javascript
1052
- const response = await api.get('/users', { timeout: 3000 });
1053
- const users = await response.json();
1054
- ```
253
+ - `instance.addRequestInterceptor(successHandler, errorHandler)` - Intercept requests
254
+ - `instance.addResponseInterceptor(successHandler, errorHandler)` - Intercept responses
1055
255
 
1056
256
  ---
1057
257
 
1058
- ### instance.post(endpoint, config)
1059
-
1060
- Performs a POST request.
1061
-
1062
- **Parameters:**
1063
- - `endpoint` (String): API endpoint
1064
- - `config` (Object, optional): Request-specific config
1065
- - `body` (String): Request body (usually JSON.stringify)
1066
- - `timeout` (Number): Override default timeout
1067
- - `headers` (Object): Additional headers
1068
-
1069
- **Returns:** Promise<Response>
258
+ ## Quick Reference
1070
259
 
1071
- **Example:**
1072
260
  ```javascript
1073
- const response = await api.post('/users', {
1074
- body: JSON.stringify({ name: 'John' }),
1075
- timeout: 10000
1076
- });
1077
- ```
1078
-
1079
- ---
1080
-
1081
- ### instance.put(endpoint, config)
1082
-
1083
- Performs a PUT request (full update).
1084
-
1085
- **Parameters:** Same as POST
1086
-
1087
- **Example:**
1088
- ```javascript
1089
- await api.put('/users/1', {
1090
- body: JSON.stringify({ name: 'John', email: 'john@example.com' })
1091
- });
1092
- ```
1093
-
1094
- ---
1095
-
1096
- ### instance.patch(endpoint, config)
1097
-
1098
- Performs a PATCH request (partial update).
1099
-
1100
- **Parameters:** Same as POST
1101
-
1102
- **Example:**
1103
- ```javascript
1104
- await api.patch('/users/1', {
1105
- body: JSON.stringify({ email: 'newemail@example.com' })
1106
- });
1107
- ```
1108
-
1109
- ---
1110
-
1111
- ### instance.delete(endpoint, config)
1112
-
1113
- Performs a DELETE request.
1114
-
1115
- **Parameters:**
1116
- - `endpoint` (String): API endpoint
1117
- - `config` (Object, optional): Request-specific config
1118
-
1119
- **Returns:** Promise<Response>
1120
-
1121
- **Example:**
1122
- ```javascript
1123
- await api.delete('/users/1');
1124
- ```
1125
-
1126
- ---
1127
-
1128
- ### instance.addRequestInterceptor(successHandler, errorHandler)
1129
-
1130
- Adds a request interceptor.
1131
-
1132
- **Parameters:**
1133
- - `successHandler` (Function): Called before request
1134
- - Receives: `{ endPoint, config }`
1135
- - Must return: Modified `{ endPoint, config }`
1136
- - `errorHandler` (Function, optional): Called if previous interceptor failed
1137
- - Receives: Error object
1138
- - Must return: Rejected promise or recovery value
1139
-
1140
- **Example:**
1141
- ```javascript
1142
- api.addRequestInterceptor(
1143
- (config) => {
1144
- config.config.headers['X-Timestamp'] = Date.now();
1145
- return config;
1146
- },
1147
- (error) => {
1148
- console.error('Request error:', error);
1149
- return Promise.reject(error);
1150
- }
1151
- );
1152
- ```
1153
-
1154
- ---
1155
-
1156
- ### instance.addResponseInterceptor(successHandler, errorHandler)
1157
-
1158
- Adds a response interceptor.
261
+ // Create
262
+ const api = fetchify.create({ baseURL: '...', timeout: 5000 });
1159
263
 
1160
- **Parameters:**
1161
- - `successHandler` (Function): Called after response received
1162
- - Receives: Response object
1163
- - Must return: Response object (modified or not)
1164
- - `errorHandler` (Function, optional): Called on request failure
1165
- - Receives: Error object
1166
- - Must return: Rejected promise or recovery value
264
+ // Requests
265
+ await api.get('/path');
266
+ await api.post('/path', { body: JSON.stringify(data) });
267
+ await api.put('/path', { body: JSON.stringify(data) });
268
+ await api.patch('/path', { body: JSON.stringify(data) });
269
+ await api.delete('/path');
1167
270
 
1168
- **Example:**
1169
- ```javascript
1170
- api.addResponseInterceptor(
1171
- (response) => {
1172
- console.log('Status:', response.status);
1173
- return response;
1174
- },
1175
- (error) => {
1176
- if (error.message.includes('timeout')) {
1177
- alert('Request timed out');
1178
- }
1179
- return Promise.reject(error);
1180
- }
1181
- );
271
+ // Interceptors
272
+ api.addRequestInterceptor((config) => { return config; });
273
+ api.addResponseInterceptor((response) => { return response; });
1182
274
  ```
1183
275
 
1184
276
  ---
1185
277
 
1186
- ## Comparison with Axios
1187
-
1188
- | Feature | Fetchify | Axios |
1189
- |---------|----------|-------|
1190
- | Size | ~5KB | ~20KB |
1191
- | Dependencies | None (native fetch) | Standalone library |
1192
- | Browser Support | Modern browsers | All browsers (polyfills) |
1193
- | Interceptors | ✅ | ✅ |
1194
- | Timeout | ✅ | ✅ |
1195
- | Request Cancellation | ✅ (AbortController) | ✅ (CancelToken) |
1196
- | Automatic JSON Transform | ❌ (manual) | ✅ |
1197
- | Progress Events | ❌ | ✅ |
1198
- | TypeScript | ❌ | ✅ |
1199
-
1200
- ---
1201
-
1202
- ## Future Enhancements
1203
-
1204
- Potential features to add:
1205
- - Retry logic with exponential backoff
1206
- - Request caching
1207
- - Automatic JSON parsing
1208
- - Progress events for uploads
1209
- - TypeScript definitions
1210
- - Request/response transformation helpers
1211
- - CSRF token handling
1212
- - File upload helpers
1213
-
1214
- ---
1215
-
1216
278
  ## License
1217
279
 
1218
280
  MIT License - Free to use and modify
1219
281
 
1220
- ---
1221
-
1222
- ## Contributing
282
+ ## Author
1223
283
 
1224
- Feel free to submit issues and pull requests for improvements!
284
+ Rutansh Chawla
1225
285
 
1226
286
  ---
1227
287
 
1228
- **Last Updated:** January 2026
1229
288
  **Version:** 1.0.0
1230
- **Author:** Rutansh Chawla
1231
-
1232
- ---
1233
-
1234
- ## Quick Reference Card
1235
-
1236
- ```javascript
1237
- // Create instance
1238
- const api = fetchify.create({ baseURL: '...', timeout: 5000 });
1239
-
1240
- // Requests
1241
- await api.get('/path', { timeout: 3000 });
1242
- await api.post('/path', { body: JSON.stringify(data) });
1243
- await api.put('/path', { body: JSON.stringify(data) });
1244
- await api.patch('/path', { body: JSON.stringify(data) });
1245
- await api.delete('/path');
1246
-
1247
- // Interceptors
1248
- api.addRequestInterceptor((config) => { /* modify */ return config; });
1249
- api.addResponseInterceptor((response) => { /* handle */ return response; });
1250
-
1251
- // Error handling
1252
- try {
1253
- const res = await api.get('/path');
1254
- const data = await res.json();
1255
- } catch (error) {
1256
- console.error(error.message);
1257
- }
1258
- ```
1259
-
1260
- ---
package/fetchify.js CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Fetchify - A Lightweight HTTP Client Library
3
+ * @author Rutansh Chawla
4
+ * @license MIT
5
+ * @version 1.0.0
6
+ */
7
+
1
8
  class Fetchify {
2
9
  // Implementation of Fetchify class
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rutansh0101/fetchify",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "A modern, lightweight HTTP client library built on top of the native Fetch API with axios-like interface, interceptors, and timeout support",
5
5
  "license": "MIT",
6
6
  "author": "Rutansh Chawla",