@alexfalconflores/safe-fetch 2.0.0 → 2.0.1

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 (2) hide show
  1. package/README.md +468 -94
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  <h1 align="center">
2
- safeFetch
2
+ safeFetch
3
3
  <br />
4
4
  <img src="https://github.com/alexfalconflores/safe-fetch/blob/4fd0a9af158b69fa3bab5861ce13c552203845d9/logo.svg" alt="safeFetch logo" width="150"/>
5
5
  </h1>
6
6
 
7
7
  <p align="center">
8
- <strong>The production-ready, typed HTTP client for modern TypeScript apps.</strong><br />
9
- A lightweight, zero-dependency wrapper around <code>fetch</code> with built-in retries, timeouts, interceptors, and strong typing.
8
+ <strong>Typed, productionready HTTP client for modern TypeScript apps.</strong><br />
9
+ A lightweight, zerodependency wrapper around <code>fetch</code> with retries, timeouts, interceptors, abortAll and strong typing.
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -25,15 +25,26 @@
25
25
 
26
26
  ## 🚀 Why safeFetch?
27
27
 
28
- Native `fetch` is great, but it lacks features needed for real-world apps. **safeFetch** solves this without the bloat of Axios.
28
+ Native ‎`fetch` is great, but in real apps we always end up writing the same things:
29
29
 
30
- - **Smart & Typed:** Generics support `<T>` for return types.
31
- - 🔄 **Auto-Retries:** Automatically retries on network errors or 5xx responses.
32
- - ⏱️ **Timeouts:** Native timeout support that plays nice with manual cancellation.
33
- - 🛑 **AbortAll:** Cancel all pending requests at once (perfect for React `useEffect` cleanup).
34
- - 🪝 **Interceptors:** Hooks for `onRequest`, `onResponse`, and `onResponseError` (great for token refresh).
35
- - 🔍 **Debug Mode:** Generates copy-pasteable **cURL** commands when requests fail.
36
- - 🍬 **Sugar Syntax:** `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()`.
30
+ - Repetitive error handling.
31
+ - Converting responses to JSON.
32
+ - Adding common headers.
33
+ - Dealing with timeouts, retries, AbortController, etc.
34
+
35
+ safeFetch solves all of this without the heaviness of Axios.
36
+
37
+ - ✨ Strong typing with ‎`<T>` generics on responses.
38
+ - 🔄 Automatic retries on network errors or 5xx responses.
39
+ - ⏱️ Per‑request configurable timeouts.
40
+ - 🛑 ‎`abortAll()` to cancel all pending requests for an instance.
41
+ - 🪝 Interceptors (‎`onRequest`, ‎`onResponse`, ‎`onResponseError`).
42
+ - 🧠 HTTP status handlers (on200, on401, on500, etc).
43
+ - 🔍 Debug mode with ready‑to‑paste cURL commands.
44
+ - 🍬 Sugar syntax: ‎`.get()`, ‎`.post()`, ‎`.put()`, ‎`.delete()`, ‎`.patch()`.
45
+ - 📦 Typed headers for common cases (‎`Content-Type`, ‎`Authorization`, ‎`Accept`, etc).
46
+
47
+ ---
37
48
 
38
49
  ## 📦 Installation
39
50
 
@@ -43,11 +54,13 @@ npm install @alexfalconflores/safe-fetch
43
54
  bun add @alexfalconflores/safe-fetch
44
55
  ```
45
56
 
46
- ## 💻 Usage
57
+ ---
58
+
59
+ ## 💻 Basic usage
47
60
 
48
- ### 1. Quick Start (Singleton)
61
+ ### 1. Default singleton
49
62
 
50
- Use the default instance for quick requests.
63
+ For simple cases, use the default ‎`safeFetch` instance:
51
64
 
52
65
  ```ts
53
66
  import safeFetch from "@alexfalconflores/safe-fetch";
@@ -57,161 +70,522 @@ interface User {
57
70
  name: string;
58
71
  }
59
72
 
60
- // Automatic JSON parsing + Typing
73
+ /**
74
+ * 1) Sugar syntax (get/post)
75
+ * Great for most day‑to‑day use cases.
76
+ */
77
+
78
+ // Typed GET + automatic JSON parsing
61
79
  const users = await safeFetch.get<User[]>("/api/users");
62
80
 
63
- // Automatic Body serialization
81
+ // POST with body automatically serialized to JSON
64
82
  await safeFetch.post("/api/users", { name: "Alex" });
83
+
84
+ /**
85
+ * 2) Core usage (no sugar)
86
+ * Same instance, but calling the underlying request function directly.
87
+ * Useful if you want full control over method, body and options.
88
+ */
89
+
90
+ // Equivalent GET without .get()
91
+ const usersCore = await safeFetch("/api/users", {
92
+ method: "GET",
93
+ timeout: 5000, // 5 segundos
94
+ // You can also add headers, params, timeout, etc. here
95
+ });
96
+
97
+ // Equivalent POST without .post()
98
+ await safeFetch("/api/users", {
99
+ method: "POST",
100
+ body: { name: "Alex" }, // Will be JSON‑stringified automatically
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ },
104
+ });
65
105
  ```
66
106
 
67
- ### 2. The Factory Pattern (Recommended)
107
+ Under the hood:
108
+
109
+ - If the method is not ‎`GET`/‎`HEAD` and the body is an object, it will be serialized as JSON.
110
+ - If you don’t define ‎`Content-Type`, ‎`"application/json"` is used by default.
111
+
112
+ ---
113
+
114
+ ## 🏭 Recommended pattern: ‎`createSafeFetch
68
115
 
69
- Create isolated instances for different APIs (e.g., Backend, ThirdParty).
116
+ The ideal setup is to create **isolated instances per API** (backend, third‑party services, etc).
70
117
 
71
118
  ```ts
72
119
  import { createSafeFetch } from "@alexfalconflores/safe-fetch";
73
120
 
74
- const api = createSafeFetch({
75
- baseUrl: "[https://api.myapp.com/v1](https://api.myapp.com/v1)",
121
+ export const api = createSafeFetch({
122
+ debug: true, // Imprime logs y cURL cuando algo falla
123
+ baseUrl: "https://api.myapp.com/v1",
76
124
  headers: {
77
- "Authorization": "Bearer initial-token",
78
- "Content-Type": "application/json" // Default
125
+ "Content-Type": "application/json",
126
+ },
127
+ onRequest: async (url, config) => {
128
+ const session = await getSession();
129
+ config.headers = {
130
+ ...config.headers,
131
+ Authorization: `Bearer ${session?.backend_token}`,
132
+ ...(await buildAuditHeaders()),
133
+ };
134
+ return config;
135
+ },
136
+ on401: async () => {
137
+ console.error("Unauthorized: Redirecting to login page.");
79
138
  },
80
- debug: true, // Prints cURL on error
81
139
  });
82
140
 
83
- // Usage
84
- const data = await api.get<MyData>("/dashboard");
141
+ // Ejemplo de uso
142
+ interface DashboardResponse {
143
+ stats: {
144
+ users: number;
145
+ sales: number;
146
+ };
147
+ }
148
+
149
+ // Sin Azucar
150
+ const res = await api("/dashboard", {
151
+ method: "GET",
152
+ timeout: TIMEOUT,
153
+ next: { tags: [GET_BRANDS_TAG], revalidate: CRUD_REVALIDATE_SECONDS }, // Si usas Next.js
154
+ });
155
+
156
+ const data = await api.get<DashboardResponse>("/dashboard");
157
+ console.log(data.stats.users);
85
158
  ```
86
159
 
87
- ### 3. Advanced Features
160
+ ### Update configuration on the fly
88
161
 
89
- 🔄 Retries & Timeouts
162
+ You can update ‎`baseUrl`, ‎`headers` and ‎`handlers` without creating a new instance.
90
163
 
91
- Configure per-request or globally.
164
+ A common pattern (especially in frameworks like Next.js) is to configure ‎`safeFetch` once and then simply import it wherever you need it.
92
165
 
93
166
  ```ts
94
- const data = await api.get("/flaky-endpoint", {
95
- timeout: 5000, // Abort after 5s
96
- retries: 3, // Retry 3 times on 5xx or Network Error
97
- retryDelay: 1000, // Wait 1s between retries
167
+ // config.ts
168
+ // Global configuration for the default safeFetch instance
169
+
170
+ import safeFetch from "@alexfalconflores/safe-fetch";
171
+ import { getSession } from "./auth";
172
+ import { buildAuditHeaders } from "./audit";
173
+
174
+ safeFetch.configure({
175
+ baseUrl: "http://localhost:8000/api/v1",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ },
179
+ onRequest: async (url, config) => {
180
+ const session = await getSession();
181
+ config.headers = {
182
+ ...config.headers,
183
+ Authorization: session?.backend_token
184
+ ? `Bearer ${session.backend_token}`
185
+ : undefined,
186
+ ...(await buildAuditHeaders()),
187
+ };
188
+ return config;
189
+ },
190
+ on401: async () => {
191
+ console.error("Unauthorized: Redirecting to login page.");
192
+ // redirectToLogin(); // your logic here
193
+ },
194
+ });
195
+ ```
196
+
197
+ Then import this configuration once at the top level of your app (so it runs before any requests):
198
+
199
+ > `app/layout.tsx` (or your main entry file)
200
+
201
+ ```ts
202
+ import "./config.ts";
203
+ ```
204
+
205
+ From that point on, you can use the already configured ‎`safeFetch` anywhere in your code:
206
+
207
+ ```ts
208
+ import safeFetch from "@alexfalconflores/safe-fetch";
209
+
210
+ const usersCore = await safeFetch("/api/users", {
211
+ method: "GET",
212
+ timeout: 5000, // 5 segundos
213
+ // You can also add headers, params, timeout, etc. here
98
214
  });
99
215
  ```
100
216
 
101
- 🪝 Interceptors (Middleware)
217
+ ---
102
218
 
103
- Powerful hooks for authentication, logging, or error recovery.
219
+ If you create a custom instance with ‎`createSafeFetch`, you can also re‑configure it:
104
220
 
105
221
  ```ts
222
+ import { createSafeFetch } from "@alexfalconflores/safe-fetch";
223
+
106
224
  const api = createSafeFetch({
107
- // 1. Inject Token before request
108
- onRequest: async (url, config) => {
225
+ baseUrl: "https://api.myapp.com",
226
+ });
227
+
228
+ api.configure({
229
+ headers: {
230
+ Authorization: `Bearer ${token}`,
231
+ },
232
+ });
233
+ ```
234
+
235
+ > New configuration is merged with the existing one (headers are merged, not fully replaced).
236
+
237
+ ---
238
+
239
+ ## 🪝 Interceptores y manejo global
240
+
241
+ ### `onRequest`: inject token, tweak URL, etc.
242
+
243
+ ```ts
244
+ const api = createSafeFetch({
245
+ baseUrl: "https://api.myapp.com",
246
+ async onRequest(url, config) {
109
247
  const token = localStorage.getItem("token");
110
- if (token) {
111
- config.headers = { ...config.headers, Authorization: `Bearer ${token}` };
112
- }
113
- return config;
248
+ return {
249
+ ...config,
250
+ headers: {
251
+ ...config.headers,
252
+ Authorization: token ? `Bearer ${token}` : undefined,
253
+ },
254
+ };
114
255
  },
256
+ });
257
+ ```
115
258
 
116
- // 2. Global Error Handling
117
- onError: (err) => console.error("Network died:", err),
259
+ ### `onResponse`: global logging
118
260
 
119
- // 3. Auto-Recovery (e.g., Refresh Token on 401)
120
- onResponseError: async (response, attempt) => {
261
+ ```ts
262
+ const api = createSafeFetch({
263
+ baseUrl: "https://api.myapp.com",
264
+ onResponse(response) {
265
+ console.log("[HTTP]", response.status, response.url);
266
+ },
267
+ });
268
+ ```
269
+
270
+ ### `onResponseError`: refresh token / transparent retry
271
+
272
+ ```ts
273
+ const api = createSafeFetch({
274
+ baseUrl: "https://api.myapp.com",
275
+ async onResponseError(response, attempt) {
276
+ // First 401 ➜ try to refresh token and retry
121
277
  if (response.status === 401 && attempt === 0) {
122
- await refreshToken(); // Your logic
123
- // Retry the request transparently
124
- return api.request(response.url, { ...response });
278
+ await refreshToken(); // Tu lógica
279
+ // Retry the same URL with the new configuration
280
+ return api.request(response.url, {
281
+ method: response.request?.method ?? "GET",
282
+ // You can pass the original init here if you store it yourself
283
+ responseType: "response",
284
+ });
125
285
  }
126
- }
286
+ },
127
287
  });
128
288
  ```
129
289
 
130
- 🛑 Cancellation (Abort All)
290
+ > Note: in a real app, it’s usually better to store the original ‎`init` so you can reuse it when retrying.
291
+
292
+ ---
131
293
 
132
- Stop all pending requests when a component unmounts.
294
+ ## 🧠 HTTP status handlers
295
+
296
+ safeFetch lets you attach callbacks to specific HTTP status codes.
297
+
298
+ Instead of doing ‎`console.log`, it’s more realistic to delegate to shared services (auth, notifications, monitoring, etc.).
299
+
300
+ Example: centralize expired session handling, notifications and reporting.
301
+
302
+ Assume you have three simple utilities:
133
303
 
134
304
  ```ts
135
- // React Example
136
- useEffect(() => {
137
- api.get("/heavy-data").then(setData);
305
+ // auth.ts
306
+ export function forceLogout() {
307
+ // Clear tokens, global state, etc.
308
+ localStorage.removeItem("token");
309
+ // You can use your router here
310
+ window.location.href = "/login";
311
+ }
138
312
 
139
- return () => {
140
- api.abortAll(); // Cancels pending requests immediately
141
- };
142
- }, []);
313
+ // notifications.ts
314
+ export function notifyError(message: string) {
315
+ // Here you could integrate Sonner, Toastify, Radix, etc.
316
+ // Por ejemplo:
317
+ // toast.error(message);
318
+ console.error("[UI ERROR]", message);
319
+ }
320
+
321
+ // monitoring.ts
322
+ export function reportError(error: unknown) {
323
+ // In a real project: send to Sentry, Datadog, LogRocket, etc.
324
+ console.error("[REPORT ERROR]", error);
325
+ }
326
+ ```
327
+
328
+ Ahora conectas todo desde ‎`createSafeFetch`:
329
+
330
+ ```ts
331
+ import { createSafeFetch } from "@alexfalconflores/safe-fetch";
332
+ import { forceLogout } from "./auth";
333
+ import { notifyError } from "./notifications";
334
+ import { reportError } from "./monitoring";
335
+
336
+ const api = createSafeFetch({
337
+ baseUrl: "https://api.myapp.com",
338
+ // 4xx: errores de cliente
339
+ async on400(response) {
340
+ // Example: extract validation errors and show them in the UI
341
+ try {
342
+ const data = await response.json();
343
+ const message =
344
+ data?.message ?? "Tu solicitud contiene datos inválidos (400).";
345
+ notifyError(message);
346
+ } catch {
347
+ notifyError("Tu solicitud contiene datos inválidos (400).");
348
+ }
349
+ },
350
+ async on401() {
351
+ // Session expired ➜ force logout and redirect to login
352
+ notifyError("Tu sesión ha expirado. Ingresa nuevamente.");
353
+ forceLogout();
354
+ },
355
+ on403() {
356
+ // No permissions ➜ show generic message
357
+ notifyError("No tienes permisos para realizar esta acción (403).");
358
+ },
359
+ on404() {
360
+ // Resource not found ➜ you may update a routing store, etc.
361
+ notifyError("Recurso no encontrado (404).");
362
+ },
363
+ // 5xx: server errors
364
+ async on500(response) {
365
+ notifyError("Estamos teniendo problemas en el servidor. Inténtalo más tarde.");
366
+ reportError({
367
+ status: response.status,
368
+ url: response.url,
369
+ });
370
+ },
371
+ // Network error (no HTTP response)
372
+ onError(error) {
373
+ notifyError("Parece que no tienes conexión a Internet.");
374
+ reportError(error);
375
+ },
376
+ });
377
+ ```
378
+
379
+ In your business code you just use ‎`api` normally:
380
+ ```ts
381
+ // Example in a domain service / React hook
382
+ export async function fetchCurrentUser() {
383
+ const user = await api.get<User>("/me");
384
+ return user;
385
+ }
386
+ ```
387
+ >💡 This keeps all network logic in one place (safeFetch + handlers)
388
+ and your UI only talks to utilities like ‎`notifyError`, ‎`forceLogout`, etc.
389
+
390
+ Besides specific handlers (‎`on200`, ‎`on404`, ‎`on503`, etc.), you also have:
391
+
392
+ - ‎`onError(error)`: when the network fails (no Internet, DNS, CORS, timeout, abort).
393
+ - ‎`onResponse(response)`: runs whenever there is a response (2xx, 3xx, 4xx, 5xx).
394
+
395
+ ---
396
+
397
+ ## ⏱️ Timeouts and retries
398
+
399
+ ### Per‑request timeout
400
+
401
+ ```ts
402
+ const user = await api.get<User>("/users/1", {
403
+ timeout: 5000, // 5 segundos
404
+ });
405
+ ```
406
+
407
+ If the timeout is exceeded:
408
+
409
+ - The request is aborted.
410
+ - An error is thrown with the message ‎`Request timeout after 5000ms`.
411
+
412
+ ### Retries on network / 5xx errors
413
+
414
+ ```ts
415
+ const data = await api.get("/flaky-endpoint", {
416
+ retries: 3, // 3 retries
417
+ retryDelay: 1000, // 1s between retries
418
+ timeout: 4000, // Max 4s per attempt
419
+ });
420
+ ```
421
+
422
+ Execution flow:
423
+
424
+ 1. Attempt 1 → fails (network or 5xx) → wait 1s.
425
+ 2. Attempt 2 → fails → wait 1s.
426
+ 3. Attempt 3 → fails → throw the last captured error.
427
+
428
+ ---
429
+
430
+ ## 🛑 Cancel all requests (`abortAll`)
431
+
432
+ Each ‎`safeFetch` instance keeps its own set of ‎`AbortController`s.
433
+
434
+ This is perfect for React when unmounting components:
435
+
436
+ ```ts
437
+ import { useEffect, useState } from "react";
438
+ import { createSafeFetch } from "@alexfalconflores/safe-fetch";
439
+
440
+ const api = createSafeFetch({
441
+ baseUrl: "https://api.myapp.com",
442
+ });
443
+
444
+ function HeavyComponent() {
445
+ const [data, setData] = useState<any>(null);
446
+
447
+ useEffect(() => {
448
+ api
449
+ .get("/heavy-data")
450
+ .then(setData)
451
+ .catch((err) => {
452
+ if (err.message === "Request aborted by safeFetch.abortAll()") return;
453
+ console.error(err);
454
+ });
455
+
456
+ return () => {
457
+ api.abortAll(); // Cancels ALL pending requests for this instance
458
+ };
459
+ }, []);
460
+
461
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
462
+ }
143
463
  ```
144
464
 
145
- 🔍 Query Params
465
+ ---
466
+
467
+ ## 🔍 Easy query params
146
468
 
147
- Forget about URLSearchParams. Pass a simple object.
469
+ Forget about building ‎`URLSearchParams` manually:
148
470
 
149
471
  ```ts
150
- // GET /search?q=books&page=1&tags=a&tags=b
472
+ // Genera: GET /search?q=books&page=2&tags=a&tags=b
151
473
  await api.get("/search", {
152
474
  params: {
153
475
  q: "books",
154
- page: 1,
155
- tags: ["a", "b"]
156
- }
476
+ page: 2,
477
+ tags: ["a", "b"],
478
+ },
157
479
  });
158
480
  ```
159
481
 
160
- 📦 API Reference
482
+ - ‎`undefined` and ‎`null` are ignored.
483
+ - Arrays become multiple query params.
484
+
485
+ ---
486
+
487
+ ## 📥 Response types (‎`responseType`)
488
+
489
+ By default, safeFetch tries to parse the response as JSON (‎`responseType: "json"`).
490
+ If the backend does not return valid JSON, it falls back to returning the raw ‎`text`.
491
+
492
+ ```ts
493
+ // Plain text (HTML, CSV, etc.)
494
+ const html = await api.get<string>("/page", {
495
+ responseType: "text",
496
+ });
497
+
498
+ // Blob (archivos, imágenes, PDFs)
499
+ const file = await api.get<Blob>("/report.pdf", {
500
+ responseType: "blob",
501
+ });
161
502
 
162
- `safeFetch` and `createSafeFetch` accept a configuration object:
503
+ // ArrayBuffer (binary data for low‑level processing)
504
+ const buffer = await api.get<ArrayBuffer>("/binary", {
505
+ responseType: "arrayBuffer",
506
+ });
163
507
 
164
- |Opción |Tipo |Valor por defecto|Descripción |
165
- |---------------|-----------|-----------------|--------------------------------------------|
166
- |baseUrl |string |`""` |Base URL que se antepone a rutas relativas. |
167
- |headers |HeadersType|`{}` |Headers globales combinados en cada request.|
168
- |debug |boolean |`false` |Registra comandos cURL cuando hay error. |
169
- |onRequest |function |`undefined` |Hook previo para modificar la configuración.|
170
- |onResponse |function |`undefined` |Hook posterior para logging/analytics. |
171
- |onResponseError|function |`undefined` |Hook de recuperación para errores 4xx/5xx. |
508
+ // Raw native Response (you handle parsing yourself)
509
+ const response = await api.get<Response>("/raw", {
510
+ responseType: "response",
511
+ });
512
+ ```
172
513
 
173
- Request Options (RequestInitExt)
174
- Extends standard RequestInit with:
514
+ ---
175
515
 
176
- - body: Accepts object (auto-stringified), FormData, Blob, etc.
177
- - params: Object for query string generation.
178
- - timeout: Number in ms.
179
- - retries: Number of retry attempts.
180
- - responseType: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'response'.
516
+ ## 🧱 Typed headers (quality‑of‑life)
181
517
 
182
- 🛠️ Utilities
183
- Join(...parts)
184
- Helper to join path segments or strings safely.
518
+ ‎`HeadersType` gives you autocomplete and type safety for most common HTTP headers:
519
+
520
+ ```ts
521
+ import {
522
+ createSafeFetch,
523
+ type HeadersType,
524
+ } from "@alexfalconflores/safe-fetch";
525
+
526
+ const defaultHeaders: HeadersType = {
527
+ "Content-Type": "application/json",
528
+ Authorization: `Bearer ${myToken}`,
529
+ Accept: "application/json",
530
+ "Cache-Control": "no-cache",
531
+ };
532
+
533
+ const api = createSafeFetch({
534
+ baseUrl: "https://api.myapp.com",
535
+ headers: {
536
+ "Content-Type": "application/json",
537
+ // Headers custom siguen siendo válidos
538
+ "X-Request-Source": "dashboard-web",
539
+ },
540
+ });
541
+ ```
542
+
543
+ You still keep full flexibility for custom headers, while getting strong typing and autocomplete for the standard ones.
544
+
545
+ ---
546
+
547
+ ## 🛠 Utility: `Join`
548
+
549
+ Small helper to join strings or arrays:
185
550
 
186
551
  ```ts
187
552
  import { Join } from "@alexfalconflores/safe-fetch";
188
- Join("-", "2025", "04", "19"); // "2025-04-19"
553
+
554
+ const date = Join("-", "2025", "04", "19"); // "2025-04-19"
555
+ const classes = Join(" ", "btn", ["btn-primary", "btn-lg"]); // "btn btn-primary btn-lg"
189
556
  ```
190
557
 
558
+ ---
559
+
191
560
  ## 🧩 Compatibility
192
561
 
193
562
  Works in all modern runtimes:
194
563
 
195
564
  - ✅ Browser (Chrome, Firefox, Safari, Edge)
196
- - ✅ Node.js (v18+ or with polyfill)
565
+ - ✅ Node.js (v18+ or with a ‎`fetch` polyfill)
197
566
  - ✅ Bun
198
567
  - ✅ Deno
199
568
 
200
- ## 👤 Autor
569
+ ---
570
+
571
+ ## 👤 Author
201
572
 
202
573
  Alex Stefano Falcon Flores
203
574
 
204
- - 🐙 GitHub: [alexstefano](https://github.com/alexfalconflores)
205
- - 💼 LinkedIn: [alexsfalconflores](https://www.linkedin.com/in/alexfalconflores/)
575
+ - 🐙 GitHub: [alexfalconflores](https://github.com/alexfalconflores)
576
+ - 💼 LinkedIn: [alexfalconflores](https://www.linkedin.com/in/alexfalconflores/)
577
+ - 🌐 Website: [alexfalconflores](https://www.alexfalconflores.com/)
206
578
 
207
- ## 📄 License
579
+ ---
208
580
 
209
- This project is licensed under the MIT license. See the [LICENSE](./LICENSE) file for more details.
581
+ ## 📄 Licencia
582
+
583
+ This project is licensed under the MIT license. See the LICENSE ↗ file for more details.
210
584
 
211
585
  <p align="center">
212
- ⭐ <strong>Love it?</strong> Give it a star on GitHub!
586
+
587
+ ⭐ <strong>Find it useful?</strong> Give it a star on GitHub.<br />
588
+
213
589
  Built with ❤️ in Peru 🇵🇪
214
- </p>
215
590
 
216
- Give the repo a star to support the project!
217
- And if you use it in your projects, I'd love to see it! 🎉
591
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexfalconflores/safe-fetch",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "The production-ready, typed HTTP client with built-in retries, timeouts, interceptors, and cancellation.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",