@rutansh0101/fetchify 1.0.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.
Files changed (3) hide show
  1. package/README.md +1260 -0
  2. package/fetchify.js +227 -0
  3. package/package.json +47 -0
package/fetchify.js ADDED
@@ -0,0 +1,227 @@
1
+ class Fetchify {
2
+ // Implementation of Fetchify class
3
+
4
+ // Default configuration of Fetchify
5
+ config = {
6
+ headers: {
7
+ 'Content-Type': 'application/json'
8
+ },
9
+ timeout: 1000
10
+ };
11
+
12
+ // Storting interceptors:
13
+ requestInterceptors = []; // { successHandler, errorHandler } -> 1 interceptor.
14
+ responseInterceptors = []; // { successHandler, errorHandler } -> 1 interceptor.
15
+
16
+ // Constructor to initialize Fetchify with user-defined config
17
+ constructor(newConfig) {
18
+ this.config = this.#mergeConfig(newConfig);
19
+ }
20
+
21
+
22
+
23
+
24
+ // private methods (not accessible outside the class):
25
+
26
+
27
+ // Private method to handle the complete request process: start -> request_Interceptor -> dispatchRequest -> response_Interceptor -> end
28
+ async #request({ endPoint, config }) {
29
+ // Interceptors are used to modify requests or responses before they are handled.
30
+ // Request Interceptor: Modify the request config before sending the request.
31
+ // Example: You can add authentication tokens, log requests, etc.
32
+ // Response Interceptor: Modify the response before returning it to the caller.
33
+ // Example: You can handle global errors, log responses, etc.
34
+
35
+ const finalConfig = this.#mergeConfig(config);
36
+
37
+
38
+ const chain = [
39
+ ...this.requestInterceptors,
40
+ { // this syntax is used to add the dispatchRequest method after request interceptors.
41
+ // we are not giving parameters like endPoint and config here, we will do that while calling the whole chain. as previous interceptor's output will be this interceptor's input. they are returning an object containing endPoint and config.
42
+ successHandler: this.#dispatchRequest.bind(this), // bind 'this' to maintain context
43
+ },
44
+ ...this.responseInterceptors
45
+ ]
46
+
47
+
48
+ let promise = Promise.resolve({ endPoint, config: finalConfig });
49
+
50
+ // iterate over the chain of interceptors
51
+ for (const { successHandler, errorHandler } of chain) {
52
+ promise = promise.then((reponseOfPrevPromise) => {
53
+ try{ // this is the success function of then, it will be called if previous promise is resolved.
54
+ return successHandler(reponseOfPrevPromise);
55
+ }
56
+ catch (error) { // this catch block will handle errors thrown in successHandler
57
+ if(errorHandler) {
58
+ return errorHandler(error);
59
+ }
60
+ else {
61
+ return Promise.reject(error);
62
+ }
63
+ }
64
+ }, (Error) => { // this error function will only be called if previous promise is rejected.
65
+ if(errorHandler) {
66
+ return errorHandler(Error);
67
+ }
68
+ else {
69
+ return Promise.reject(Error);
70
+ }
71
+ }); // we pass 2 parameters to then, first for success case, second for error case.
72
+ }
73
+ return promise; // final promise after all interceptors have been applied.
74
+ }
75
+
76
+
77
+ // Private method to dispatch the request, i made this function to reduce redundancy and code reusability.
78
+ async #dispatchRequest({ endPoint, config }) {
79
+
80
+ // abort controller is used to abort the request after some time given by the user(timeout).
81
+ const abortController = new AbortController();
82
+ const timeout = config.timeout || this.config.timeout || 0; // get timeout from temp config or default config or set to 0(no timeout).
83
+
84
+ // we set a timer to abort the request after the specified timeout duration.
85
+ let timeOutId; // to hold the timeout ID so we can clear it later if needed. If we don't clear it, it may lead to unexpected behavior or memory leaks.
86
+ if (timeout) {
87
+ timeOutId = setTimeout(() => {
88
+ abortController.abort();
89
+ }, timeout);
90
+ }
91
+
92
+ // To make this abort functionality work, we need to add the signal from the abort controller to the fetch config. which will tell fetch to listen for abort signals.
93
+ config.signal = abortController.signal; // now fetch will listen for abort signals.
94
+
95
+ // Merge temporary config with existing config
96
+ const newConfig = this.#mergeConfigs(this.config, config);
97
+ // console.log(newConfig);
98
+
99
+
100
+ // fetch is asynchronous, but try catch is synchronous, so we can't use try catch here directly.
101
+ // we used .finally() to clear the timeout after the fetch is complete.
102
+ // but we can use try catch block if we use async await with fetch.
103
+ // Note: Error handling for fetch should be done where this method is called, as fetch returns a promise.
104
+
105
+
106
+
107
+ // promise returned by fetch (Way 1):
108
+ // return fetch(newConfig.baseURL + endPoint, newConfig)
109
+ // .catch((error) => {
110
+ // // Handle fetch errors, including abort errors
111
+ // if (error.name === 'AbortError') {
112
+ // throw new Error(`Request to ${endPoint} aborted due to timeout after ${timeout} ms`);
113
+ // } else {
114
+ // throw error; // rethrow other errors to be handled where this method is called.
115
+ // }
116
+ // })
117
+ // .finally(() => {
118
+ // // Clear the timeout if the request completes before the timeout duration.
119
+ // if (timeOutId) {
120
+ // clearTimeout(timeOutId);
121
+ // }
122
+ // });
123
+
124
+
125
+ // promise returned by fetch (Way 2: using async await):
126
+ try {
127
+ const response = await fetch(newConfig.baseURL + endPoint, newConfig); // wait for the fetch to complete due to await.
128
+ return response; // return the response to be handled where this method is called.
129
+ }
130
+ catch (error) {
131
+ // Handle fetch errors, including abort errors
132
+ if (error.name === 'AbortError') {
133
+ throw new Error(`Request to ${endPoint} aborted due to timeout after ${timeout} ms`);
134
+ } else {
135
+ throw error; // rethrow other errors to be handled where this method is called.
136
+ }
137
+ }
138
+ finally {
139
+ // Clear the timeout if the request completes before the timeout duration.
140
+ if (timeOutId) {
141
+ clearTimeout(timeOutId);
142
+ }
143
+ }
144
+ }
145
+
146
+
147
+ // Private method to merge user-defined config with default config
148
+ #mergeConfig(newConfig) {
149
+ return {
150
+ ...this.config, // copy of existing config
151
+ ...newConfig, // copy of user-defined config
152
+ headers: { // merge headers specifically
153
+ ...this.config.headers,
154
+ ...newConfig.headers
155
+ }
156
+ };
157
+ }
158
+
159
+
160
+ // Private method to merge two config objects
161
+ #mergeConfigs(config1, config2) {
162
+ return {
163
+ ...config1,
164
+ ...config2,
165
+ headers: {
166
+ ...config1?.headers,
167
+ ...config2?.headers
168
+ }
169
+ };
170
+ }
171
+
172
+
173
+
174
+
175
+ // public methods(user can access these methods):
176
+
177
+
178
+ // Method to perform GET request
179
+ async get(endPoint, tempConfig = {}) {
180
+ // Call request with method set to GET, it will go through the whole chain of interceptors.:
181
+ return this.#request({ endPoint, config: { ...tempConfig, method: 'GET' } });
182
+ }
183
+
184
+ // Method to perform POST request
185
+ async post(endPoint, tempConfig = {}) {
186
+ // Call request with method set to POST, it will go through the whole chain of interceptors.:
187
+ return this.#request({ endPoint, config: { ...tempConfig, method: 'POST' } });
188
+ }
189
+
190
+ // Method to perform PUT request
191
+ async put(endPoint, tempConfig = {}) {
192
+ // Call request with method set to PUT, it will go through the whole chain of interceptors.:
193
+ return this.#request({ endPoint, config: { ...tempConfig, method: 'PUT' } });
194
+ }
195
+
196
+ // Method to perform DELETE request
197
+ async delete(endPoint, tempConfig = {}) {
198
+ // Call request with method set to DELETE, it will go through the whole chain of interceptors.:
199
+ return this.#request({ endPoint, config: { ...tempConfig, method: 'DELETE' } });
200
+ }
201
+
202
+ // Method to perform PATCH request
203
+ async patch(endPoint, tempConfig = {}) {
204
+ // Call request with method set to PATCH, it will go through the whole chain of interceptors.:
205
+ return this.#request({ endPoint, config: { ...tempConfig, method: 'PATCH' } });
206
+ }
207
+
208
+ // Method to add request interceptor
209
+ addRequestInterceptor(successHandler, errorHandler) {
210
+ this.requestInterceptors.push({ successHandler, errorHandler });
211
+ }
212
+
213
+ // Method to add response interceptor
214
+ addResponseInterceptor(successHandler, errorHandler) {
215
+ this.responseInterceptors.push({ successHandler, errorHandler });
216
+ }
217
+ }
218
+
219
+
220
+ const create = (config) => {
221
+ // Implementation of fetchify's create method
222
+ return new Fetchify(config);
223
+ }
224
+
225
+ export default {
226
+ create
227
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@rutansh0101/fetchify",
3
+ "version": "1.0.0",
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
+ "license": "MIT",
6
+ "author": "Rutansh Chawla",
7
+ "type": "module",
8
+ "main": "fetchify.js",
9
+ "module": "fetchify.js",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./fetchify.js",
13
+ "require": "./fetchify.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "fetchify.js",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "fetch",
22
+ "http",
23
+ "ajax",
24
+ "request",
25
+ "client",
26
+ "axios",
27
+ "interceptors",
28
+ "promise",
29
+ "frontend",
30
+ "react",
31
+ "api"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/Rutansh0101/Fetchify-Rutansh-Chawla"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/Rutansh0101/Fetchify-Rutansh-Chawla/issues"
39
+ },
40
+ "homepage": "https://github.com/Rutansh0101/Fetchify-Rutansh-Chawla#readme",
41
+ "engines": {
42
+ "node": ">=14.0.0"
43
+ },
44
+ "scripts": {
45
+ "test": "echo \"Error: no test specified\" && exit 1"
46
+ }
47
+ }