@alexfalconflores/safe-fetch 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2025-04-20
4
+
5
+ ### 🛡️ The Journey Begins: safeFetch Rises
6
+
7
+ - First stable release of `safeFetch`.
8
+ - Introduces a safer and more expressive wrapper around the native `fetch` API.
9
+ - Full typing for `HeadersType`, `HttpMethod`, `ContentType`, `AuthorizationType`, and other common HTTP headers.
10
+ - Support for:
11
+ - Fully Promise-based, designed for async/await flow.
12
+ - Automatic `body` serialization in JSON format if `Content-Type` is `application/json`.
13
+ - `RequestInitExt` extension that overrides the original type `headers` to use a custom, typed one.
14
+ - `toHeaders()` function:
15
+ - Converts flat objects to valid `Headers`, ignoring undefined values.
16
+ - `Join(separator, ...args)` function:
17
+ - Auxiliary utility to concatenate strings/numbers or arrays of these.
18
+ - Types Added
19
+ - `HttpMethod`: Type support for all standard and custom HTTP methods.
20
+ - `ContentType`, `AcceptType`, `AuthorizationType`, `CacheControlType`, `UserAgentType`, among other common headers.
21
+ - `HeadersType`: Extensible object with validations for the most common HTTP request headers.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alex Stefano Falcon Flores
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 ADDED
@@ -0,0 +1,131 @@
1
+ <h1 align="center">
2
+ safeFetch
3
+ <br />
4
+ <img src="https://github.com/alexfalconflores/safe-fetch/blob/4fd0a9af158b69fa3bab5861ce13c552203845d9/logo.svg" alt="safeFetch logo" width="150"/>
5
+ </h1>
6
+
7
+ <p align="center">
8
+ <strong><code>safeFetch</code> is a typed enhancement to the JavaScript <code>fetch</code> method.</strong><br />
9
+ This function is designed to facilitate the sending of HTTP requests with greater security and typed control over headers, including automatic handling of the body if <code>Content-Type: application/json</code> is specified.
10
+ </p>
11
+
12
+ ---
13
+
14
+ ## 🚀 Installation
15
+
16
+ ```bash
17
+ npm install @alexfalconflores/safe-fetch
18
+ ```
19
+
20
+ ✨ Features
21
+
22
+ - Auto-conversion from body to JSON if Content-Type: application/json is specified
23
+ - Strong typing for HTTP methods, headers and common network values
24
+ - Accepts extended and custom headers
25
+ - Supports standard fetch (can completely replace it)
26
+
27
+ ## 📦 Examples of use
28
+
29
+ ### ⚙️ Basic Example
30
+
31
+ ```ts
32
+ import safeFetch from "@alexfalconflores/safe-fetch";
33
+
34
+ const response = await safeFetch("/api/users", {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ Authorization: "Bearer abc123",
39
+ },
40
+ body: { name: "Alex", email: "alex@correo.com" },
41
+ });
42
+
43
+ const data = await response.json();
44
+ ```
45
+
46
+ ### ⚙️ Advanced Example
47
+
48
+ ```ts
49
+ import safeFetch from "@alexfalconflores/safe-fetch";
50
+
51
+ const response = await safeFetch("/api/users", {
52
+ method: "POST",
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ Accept: "application/json",
56
+ Authorization: "Bearer 123token",
57
+ "Cache-Control": "no-cache",
58
+ },
59
+ body: { name: "Alex", email: "alex@correo.com" },
60
+ });
61
+
62
+ const data = await response.json();
63
+ ```
64
+
65
+ ## 📦 API
66
+
67
+ `safeFetch(url: string, init?: RequestInitExt): Promise<Response>`
68
+
69
+ - `url`: Destination URL of the request.
70
+ - `init`: Optional object extending RequestInit, with typed headers.
71
+
72
+ ## 🧩 Extended typing
73
+
74
+ - `RequestInitExt`
75
+
76
+ ```ts
77
+ interface RequestInitExt extends Omit<RequestInit, "headers"> {
78
+ method?: HttpMethod;
79
+ headers?: HeadersType;
80
+ }
81
+ ```
82
+
83
+ - `HttpMethod`
84
+
85
+ ```ts
86
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | ...;
87
+ ```
88
+
89
+ - `HeadersType`
90
+
91
+ ```ts
92
+ {
93
+ "Content-Type"?: ContentType;
94
+ Authorization?: AuthorizationType;
95
+ Accept?: AcceptType;
96
+ ...
97
+ }
98
+ ```
99
+
100
+ Extendable types are included for:
101
+
102
+ - `AuthorizationType` (Bearer, Basic, ApiKey, etc.)
103
+ - `ContentType` (application/json, text/html, etc.)
104
+ - `AcceptType`
105
+ - `CacheControlType`
106
+ - `AcceptLanguageType`
107
+ - `UserAgentType`, y muchos más.
108
+
109
+ ## 🛠️ Auxiliary function: `Join`
110
+ ```ts
111
+ Join("-", "2025", "04", "19"); // "2025-04-19"
112
+ ```
113
+
114
+ ## 🧩 Compatibility
115
+ Compatible with environments where fetch is available: modern browsers, Deno and Node.js (v18+ or with polyfill).
116
+
117
+ ## 👤 Autor
118
+
119
+ Alex Stefano Falcon Flores
120
+
121
+ - 🐙 GitHub: [alexstefano](https://github.com/alexfalconflores)
122
+ - 💼 LinkedIn: [alexsfalconflores](https://www.linkedin.com/in/alexfalconflores/)
123
+
124
+ ## 📄 License
125
+
126
+ This project is licensed under the MIT license. See the [LICENSE](./LICENSE) file for more details.
127
+
128
+ ## ⭐ Do you like it?
129
+
130
+ Give the repo a star to support the project!
131
+ And if you use it in your projects, I'd love to see it! 🎉
@@ -0,0 +1,53 @@
1
+ declare function safeFetch(url: string, init?: RequestInitExt): Promise<Response>;
2
+
3
+ interface RequestInitExt extends Omit<RequestInit, "headers"> {
4
+ method?: HttpMethod;
5
+ headers?: HeadersType;
6
+ }
7
+ declare function Join(separator?: string, ...args: (string | number | (string | number)[])[]): string;
8
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD" | "CONNECT" | (string & {});
9
+ type ContentType = "application/json" | "application/xml" | "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain" | "text/html" | "text/css" | "text/javascript" | "text/xml" | "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "audio/mpeg" | "audio/wav" | "video/mp4" | "application/pdf" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/octet-stream" | "application/javascript" | "application/graphql" | "application/yaml" | "application/zip" | (string & {});
10
+ type AuthorizationType = `Bearer ${string}` | `Basic ${string}` | `Digest ${string}` | `ApiKey ${string}` | `OAuth oauth_consumer_key="${string}", oauth_token="${string}", oauth_signature="${string}"` | `Hawk id="${string}", ts="${string}", nonce="${string}", mac="${string}"` | `AWS4-HMAC-SHA256 Credential=${string}, SignedHeaders=${string}, Signature=${string}` | (string & {});
11
+ type AcceptType = "application/json" | "application/xml" | "text/html" | "text/plain" | "text/css" | "text/javascript" | "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "audio/mpeg" | "audio/ogg" | "video/mp4" | "video/webm" | "multipart/form-data" | "application/x-www-form-urlencoded" | "application/pdf" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.ms-powerpoint" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/vnd.ms-word" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "*/*" | `${string}/${string}`;
12
+ type AcceptEncodingType = "gzip" | "compress" | "deflate" | "br" | "identity" | "*" | `${string}`;
13
+ type AcceptLanguageType = "en" | "en-US" | "en-GB" | "es" | "es-ES" | "es-MX" | "fr" | "fr-FR" | "de" | "de-DE" | "it" | "it-IT" | "ja" | "ja-JP" | "zh" | "zh-CN" | "zh-TW" | "ru" | "ru-RU" | "*" | `${string}`;
14
+ type CacheControlType = "no-cache" | "no-store" | "must-revalidate" | "public" | "private" | "max-age=0" | `max-age=${number}` | `s-maxage=${number}` | "proxy-revalidate" | "immutable" | "stale-while-revalidate" | "stale-if-error" | `${string}`;
15
+ type ContentEncodingType = "gzip" | "compress" | "deflate" | "br" | "identity" | "zstd" | `${string}`;
16
+ type ContentLanguageType = "en" | "es" | "fr" | "de" | "it" | "pt" | "zh" | "ja" | "ko" | "ru" | "ar" | "hi" | "nl" | "sv" | "no" | "da" | "fi" | "pl" | "tr" | "cs" | "el" | "he" | "vi" | "th" | "id" | "ms" | string;
17
+ type ETagType = `W/"${string}"` | `"${string}"` | string;
18
+ type HostType = `${string}.${string}` | `${string}.${string}:${number}` | string;
19
+ type OriginType = `${"http" | "https"}://${string}.${string}` | `${"http" | "https"}://${string}.${string}:${number}`;
20
+ type RefererType = `${"http" | "https"}://${string}`;
21
+ type UserAgentType = `Mozilla/5.0 (${string}) AppleWebKit/${string} (KHTML, like Gecko) ${string}` | `curl/${string}` | `PostmanRuntime/${string}` | `okhttp/${string}` | string;
22
+ type AccessControlAllowOriginType = "*" | "null" | `${string}`;
23
+ type AccessControlAllowMethodsType = "*" | HttpMethod | `${HttpMethod}, ${HttpMethod}` | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}` | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}` | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}` | string;
24
+ type AccessControlAllowHeadersType = string;
25
+ type HeadersType = {
26
+ "Content-Type"?: ContentType;
27
+ Authorization?: AuthorizationType;
28
+ Accept?: AcceptType;
29
+ "Accept-Encoding"?: AcceptEncodingType;
30
+ "Accept-Language"?: AcceptLanguageType;
31
+ "Cache-Control"?: CacheControlType;
32
+ Connection?: "keep-alive" | "close";
33
+ "Content-Length"?: `${number}`;
34
+ "Content-Encoding"?: ContentEncodingType;
35
+ "Content-Language"?: ContentLanguageType;
36
+ "Content-Disposition"?: string;
37
+ ETag?: ETagType;
38
+ Host?: HostType;
39
+ Origin?: OriginType;
40
+ Referer?: RefererType;
41
+ "User-Agent"?: UserAgentType;
42
+ "Access-Control-Allow-Origin"?: AccessControlAllowOriginType;
43
+ "Access-Control-Allow-Methods"?: AccessControlAllowMethodsType;
44
+ "Access-Control-Allow-Headers"?: AccessControlAllowHeadersType;
45
+ "X-Frame-Options"?: "DENY" | "SAMEORIGIN";
46
+ "X-XSS-Protection"?: "0" | "1; mode=block";
47
+ "X-Content-Type-Options"?: "nosniff";
48
+ "Content-Security-Policy"?: string;
49
+ "Strict-Transport-Security"?: string;
50
+ [key: string]: string | undefined;
51
+ };
52
+
53
+ export { type AcceptEncodingType, type AcceptLanguageType, type AcceptType, type AccessControlAllowHeadersType, type AccessControlAllowMethodsType, type AccessControlAllowOriginType, type AuthorizationType, type CacheControlType, type ContentEncodingType, type ContentLanguageType, type ContentType, type ETagType, type HeadersType, type HostType, type HttpMethod, Join, type OriginType, type RefererType, type RequestInitExt, type UserAgentType, safeFetch as default, safeFetch };
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+ var __export = (target, all) => {
33
+ for (var name in all)
34
+ __defProp(target, name, { get: all[name], enumerable: true });
35
+ };
36
+ var __copyProps = (to, from, except, desc) => {
37
+ if (from && typeof from === "object" || typeof from === "function") {
38
+ for (let key of __getOwnPropNames(from))
39
+ if (!__hasOwnProp.call(to, key) && key !== except)
40
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
41
+ }
42
+ return to;
43
+ };
44
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
45
+ var __async = (__this, __arguments, generator) => {
46
+ return new Promise((resolve, reject) => {
47
+ var fulfilled = (value) => {
48
+ try {
49
+ step(generator.next(value));
50
+ } catch (e) {
51
+ reject(e);
52
+ }
53
+ };
54
+ var rejected = (value) => {
55
+ try {
56
+ step(generator.throw(value));
57
+ } catch (e) {
58
+ reject(e);
59
+ }
60
+ };
61
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
62
+ step((generator = generator.apply(__this, __arguments)).next());
63
+ });
64
+ };
65
+
66
+ // src/index.ts
67
+ var index_exports = {};
68
+ __export(index_exports, {
69
+ Join: () => Join,
70
+ default: () => index_default,
71
+ safeFetch: () => safeFetch
72
+ });
73
+ module.exports = __toCommonJS(index_exports);
74
+ function safeFetch(url, init) {
75
+ return __async(this, null, function* () {
76
+ const _a = init || {}, { method = "GET", headers, body } = _a, props = __objRest(_a, ["method", "headers", "body"]);
77
+ const contentTypeJson = "application/json";
78
+ let newBody = body;
79
+ if (body && typeof body === "object" && (headers == null ? void 0 : headers["Content-Type"]) === contentTypeJson) {
80
+ newBody = JSON.stringify(body);
81
+ }
82
+ const response = yield fetch(url, __spreadValues({
83
+ method,
84
+ headers: toHeaders(__spreadValues({}, headers)),
85
+ body: newBody
86
+ }, props));
87
+ return response;
88
+ });
89
+ }
90
+ var index_default = safeFetch;
91
+ function Join(separator = "", ...args) {
92
+ return args.flat().join(separator);
93
+ }
94
+ var toHeaders = (headers) => {
95
+ if (headers instanceof Headers) return headers;
96
+ return new Headers(
97
+ Object.entries(headers).reduce((acc, [key, value]) => {
98
+ if (value !== void 0) {
99
+ acc.push([key, String(value)]);
100
+ }
101
+ return acc;
102
+ }, [])
103
+ );
104
+ };
105
+ // Annotate the CommonJS export names for ESM import in node:
106
+ 0 && (module.exports = {
107
+ Join,
108
+ safeFetch
109
+ });
110
+ //# sourceMappingURL=index.js.map
package/dist/index.mjs ADDED
@@ -0,0 +1,87 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ var __objRest = (source, exclude) => {
18
+ var target = {};
19
+ for (var prop in source)
20
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
21
+ target[prop] = source[prop];
22
+ if (source != null && __getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(source)) {
24
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
25
+ target[prop] = source[prop];
26
+ }
27
+ return target;
28
+ };
29
+ var __async = (__this, __arguments, generator) => {
30
+ return new Promise((resolve, reject) => {
31
+ var fulfilled = (value) => {
32
+ try {
33
+ step(generator.next(value));
34
+ } catch (e) {
35
+ reject(e);
36
+ }
37
+ };
38
+ var rejected = (value) => {
39
+ try {
40
+ step(generator.throw(value));
41
+ } catch (e) {
42
+ reject(e);
43
+ }
44
+ };
45
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
46
+ step((generator = generator.apply(__this, __arguments)).next());
47
+ });
48
+ };
49
+
50
+ // src/index.ts
51
+ function safeFetch(url, init) {
52
+ return __async(this, null, function* () {
53
+ const _a = init || {}, { method = "GET", headers, body } = _a, props = __objRest(_a, ["method", "headers", "body"]);
54
+ const contentTypeJson = "application/json";
55
+ let newBody = body;
56
+ if (body && typeof body === "object" && (headers == null ? void 0 : headers["Content-Type"]) === contentTypeJson) {
57
+ newBody = JSON.stringify(body);
58
+ }
59
+ const response = yield fetch(url, __spreadValues({
60
+ method,
61
+ headers: toHeaders(__spreadValues({}, headers)),
62
+ body: newBody
63
+ }, props));
64
+ return response;
65
+ });
66
+ }
67
+ var index_default = safeFetch;
68
+ function Join(separator = "", ...args) {
69
+ return args.flat().join(separator);
70
+ }
71
+ var toHeaders = (headers) => {
72
+ if (headers instanceof Headers) return headers;
73
+ return new Headers(
74
+ Object.entries(headers).reduce((acc, [key, value]) => {
75
+ if (value !== void 0) {
76
+ acc.push([key, String(value)]);
77
+ }
78
+ return acc;
79
+ }, [])
80
+ );
81
+ };
82
+ export {
83
+ Join,
84
+ index_default as default,
85
+ safeFetch
86
+ };
87
+ //# sourceMappingURL=index.mjs.map
package/logo.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 13C3 11.3431 4.34315 10 6 10H8L9.5 9L10.5 10H17.5L19 9L20 10H22C23.6569 10 25 11.3431 25 13V26C25 27.6569 23.6569 29 22 29H6C4.34315 29 3 27.6569 3 26V13Z" fill="#F9C23C"/>
3
+ <path d="M15.5 19.5002C16.1072 19.0441 16.5 18.3179 16.5 17.5C16.5 16.1193 15.3807 15 14 15C12.6193 15 11.5 16.1193 11.5 17.5C11.5 18.3179 11.8928 19.0441 12.5 19.5002V23C12.5 23.8284 13.1716 24.5 14 24.5C14.8284 24.5 15.5 23.8284 15.5 23V19.5002Z" fill="#433B6B"/>
4
+ <path d="M14 1C10.6863 1 8 3.68629 8 7V10H10.5V7C10.5 5.067 12.067 3.5 14 3.5C15.933 3.5 17.5 5.067 17.5 7V10H20V7C20 3.68629 17.3137 1 14 1Z" fill="#D3D3D3"/>
5
+ <path d="M30 9.5C30 11.4593 28.7478 13.1262 27 13.7439V22.5C27 23.3284 26.3284 24 25.5 24C24.6716 24 24 23.3284 24 22.5L24 20.7458C24 20.5458 24.0785 20.3558 24.2157 20.2258L24.5294 19.9158C24.8236 19.6258 24.8236 19.1558 24.5294 18.8758C24.2353 18.5958 24.2353 18.1258 24.5294 17.8458C24.8236 17.5558 24.8236 17.0858 24.5294 16.8058L24.2157 16.4958C24.0785 16.3558 24 16.1758 24 15.9758L24 13.7439C22.2522 13.1262 21 11.4593 21 9.5C21 7.01472 23.0147 5 25.5 5C27.9853 5 30 7.01472 30 9.5ZM25.5 8C26.0523 8 26.5 7.55228 26.5 7C26.5 6.44772 26.0523 6 25.5 6C24.9477 6 24.5 6.44772 24.5 7C24.5 7.55228 24.9477 8 25.5 8Z" fill="#D3D3D3"/>
6
+ </svg>
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@alexfalconflores/safe-fetch",
3
+ "version": "1.0.0",
4
+ "description": "A safe and customizable fetch wrapper for secure HTTP requests.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ "import": "./dist/index.mjs",
10
+ "require": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/alexfalconflores/safe-fetch.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/alexfalconflores/safe-fetch/issues"
19
+ },
20
+ "homepage": "https://github.com/alexfalconflores/safe-fetch#readme",
21
+ "keywords": [
22
+ "fetch",
23
+ "http",
24
+ "secure",
25
+ "api",
26
+ "http-client",
27
+ "request-wrapper"
28
+ ],
29
+ "author": "Alex Stefano Falcon Flores",
30
+ "license": "MIT",
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup src/index.ts --watch --dts",
34
+ "clean": "rm -rf dist",
35
+ "publish": "npm publish --access public"
36
+ },
37
+ "devDependencies": {
38
+ "tsup": "^8.4.0",
39
+ "typescript": "^5.8.3"
40
+ }
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,277 @@
1
+ export async function safeFetch(
2
+ url: string,
3
+ init?: RequestInitExt,
4
+ ): Promise<Response> {
5
+ const { method = "GET", headers, body, ...props } = init || {};
6
+ const contentTypeJson: ContentType = "application/json";
7
+ let newBody = body;
8
+ if (
9
+ body &&
10
+ typeof body === "object" &&
11
+ headers?.["Content-Type"] === contentTypeJson
12
+ ) {
13
+ newBody = JSON.stringify(body);
14
+ }
15
+
16
+ const response = await fetch(url, {
17
+ method,
18
+ headers: toHeaders({
19
+ ...headers,
20
+ }),
21
+ body: newBody,
22
+ ...props,
23
+ });
24
+ return response;
25
+ }
26
+
27
+ export default safeFetch;
28
+
29
+ export interface RequestInitExt extends Omit<RequestInit, "headers"> {
30
+ method?: HttpMethod;
31
+ headers?: HeadersType;
32
+ }
33
+
34
+ export function Join(
35
+ separator: string = "",
36
+ ...args: (string | number | (string | number)[])[]
37
+ ): string {
38
+ return args.flat().join(separator);
39
+ }
40
+
41
+ const toHeaders = (headers: HeadersType | Headers): Headers => {
42
+ if (headers instanceof Headers) return headers;
43
+ return new Headers(
44
+ Object.entries(headers).reduce<[string, string][]>((acc, [key, value]) => {
45
+ if (value !== undefined) {
46
+ acc.push([key, String(value)]);
47
+ }
48
+ return acc;
49
+ }, []),
50
+ );
51
+ };
52
+
53
+ export type HttpMethod =
54
+ | "GET"
55
+ | "POST"
56
+ | "PUT"
57
+ | "DELETE"
58
+ | "PATCH"
59
+ | "OPTIONS"
60
+ | "HEAD"
61
+ | "CONNECT"
62
+ | (string & {});
63
+
64
+ export type ContentType =
65
+ | "application/json"
66
+ | "application/xml"
67
+ | "application/x-www-form-urlencoded"
68
+ | "multipart/form-data"
69
+ | "text/plain"
70
+ | "text/html"
71
+ | "text/css"
72
+ | "text/javascript"
73
+ | "text/xml"
74
+ | "image/png"
75
+ | "image/jpeg"
76
+ | "image/gif"
77
+ | "image/webp"
78
+ | "audio/mpeg"
79
+ | "audio/wav"
80
+ | "video/mp4"
81
+ | "application/pdf"
82
+ | "application/msword"
83
+ | "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
84
+ | "application/vnd.ms-excel"
85
+ | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
86
+ | "application/octet-stream"
87
+ | "application/javascript"
88
+ | "application/graphql"
89
+ | "application/yaml"
90
+ | "application/zip"
91
+ | (string & {});
92
+
93
+ export type AuthorizationType =
94
+ | `Bearer ${string}` // OAuth 2.0, JWT
95
+ | `Basic ${string}` // Usuario:Contraseña en base64
96
+ | `Digest ${string}` // Digest Auth con hash
97
+ | `ApiKey ${string}` // Clave de API
98
+ | `OAuth oauth_consumer_key="${string}", oauth_token="${string}", oauth_signature="${string}"` // OAuth 1.0
99
+ | `Hawk id="${string}", ts="${string}", nonce="${string}", mac="${string}"` // Hawk Auth
100
+ | `AWS4-HMAC-SHA256 Credential=${string}, SignedHeaders=${string}, Signature=${string}` // AWS Signature
101
+ | (string & {});
102
+
103
+ export type AcceptType =
104
+ | "application/json"
105
+ | "application/xml"
106
+ | "text/html"
107
+ | "text/plain"
108
+ | "text/css"
109
+ | "text/javascript"
110
+ | "image/png"
111
+ | "image/jpeg"
112
+ | "image/gif"
113
+ | "image/webp"
114
+ | "audio/mpeg"
115
+ | "audio/ogg"
116
+ | "video/mp4"
117
+ | "video/webm"
118
+ | "multipart/form-data"
119
+ | "application/x-www-form-urlencoded"
120
+ | "application/pdf"
121
+ | "application/vnd.ms-excel"
122
+ | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
123
+ | "application/vnd.ms-powerpoint"
124
+ | "application/vnd.openxmlformats-officedocument.presentationml.presentation"
125
+ | "application/vnd.ms-word"
126
+ | "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
127
+ | "*/*" // Para aceptar cualquier tipo de contenido
128
+ | `${string}/${string}`; // Permite valores personalizados
129
+
130
+ export type AcceptEncodingType =
131
+ | "gzip"
132
+ | "compress"
133
+ | "deflate"
134
+ | "br" // Brotli, usado en HTTP/2
135
+ | "identity" // Sin compresión
136
+ | "*" // Aceptar cualquier codificación
137
+ | `${string}`; // Permite valores personalizados
138
+
139
+ export type AcceptLanguageType =
140
+ | "en" // Inglés
141
+ | "en-US"
142
+ | "en-GB"
143
+ | "es" // Español
144
+ | "es-ES"
145
+ | "es-MX"
146
+ | "fr" // Francés
147
+ | "fr-FR"
148
+ | "de" // Alemán
149
+ | "de-DE"
150
+ | "it" // Italiano
151
+ | "it-IT"
152
+ | "ja" // Japonés
153
+ | "ja-JP"
154
+ | "zh" // Chino
155
+ | "zh-CN"
156
+ | "zh-TW"
157
+ | "ru" // Ruso
158
+ | "ru-RU"
159
+ | "*" // Aceptar cualquier idioma
160
+ | `${string}`; // Permite valores personalizados como "pt-BR", "ar-SA", etc.
161
+
162
+ export type CacheControlType =
163
+ | "no-cache" // Siempre debe validar en el servidor antes de usar la caché
164
+ | "no-store" // No almacenar en caché ni en el cliente ni en el servidor
165
+ | "must-revalidate" // Debe validar en el servidor antes de usar la caché
166
+ | "public" // Puede ser almacenado en cualquier caché intermedia
167
+ | "private" // Solo puede ser almacenado en la caché del cliente
168
+ | "max-age=0" // La respuesta es inmediatamente obsoleta
169
+ | `max-age=${number}` // Se puede usar hasta cierto número de segundos
170
+ | `s-maxage=${number}` // Similar a `max-age`, pero solo para cachés compartidas (CDN)
171
+ | "proxy-revalidate" // Similar a `must-revalidate`, pero para proxies
172
+ | "immutable" // Indica que la respuesta nunca cambiará (ideal para archivos estáticos)
173
+ | "stale-while-revalidate" // Permite servir una respuesta caducada mientras se obtiene una nueva en segundo plano
174
+ | "stale-if-error" // Permite servir una respuesta caducada si hay un error en la solicitud
175
+ | `${string}`; // Permite valores personalizados.
176
+
177
+ export type ContentEncodingType =
178
+ | "gzip"
179
+ | "compress"
180
+ | "deflate"
181
+ | "br"
182
+ | "identity"
183
+ | "zstd"
184
+ | `${string}`; // Nuevo estándar para compresión
185
+
186
+ export type ContentLanguageType =
187
+ | "en" // Inglés
188
+ | "es" // Español
189
+ | "fr" // Francés
190
+ | "de" // Alemán
191
+ | "it" // Italiano
192
+ | "pt" // Portugués
193
+ | "zh" // Chino
194
+ | "ja" // Japonés
195
+ | "ko" // Coreano
196
+ | "ru" // Ruso
197
+ | "ar" // Árabe
198
+ | "hi" // Hindi
199
+ | "nl" // Neerlandés
200
+ | "sv" // Sueco
201
+ | "no" // Noruego
202
+ | "da" // Danés
203
+ | "fi" // Finés
204
+ | "pl" // Polaco
205
+ | "tr" // Turco
206
+ | "cs" // Checo
207
+ | "el" // Griego
208
+ | "he" // Hebreo
209
+ | "vi" // Vietnamita
210
+ | "th" // Tailandés
211
+ | "id" // Indonesio
212
+ | "ms" // Malayo
213
+ | string; // Permite otros idiomas personalizados
214
+
215
+ export type ETagType =
216
+ | `W/"${string}"` // Weak ETag (ejemplo: W/"123456")
217
+ | `"${string}"` // Strong ETag (ejemplo: "abcdef")
218
+ | string; // Permitir otros valores dinámicos
219
+
220
+ export type HostType =
221
+ | `${string}.${string}`
222
+ | `${string}.${string}:${number}`
223
+ | string;
224
+
225
+ export type OriginType =
226
+ | `${"http" | "https"}://${string}.${string}`
227
+ | `${"http" | "https"}://${string}.${string}:${number}`;
228
+
229
+ export type RefererType = `${"http" | "https"}://${string}`;
230
+
231
+ export type UserAgentType =
232
+ | `Mozilla/5.0 (${string}) AppleWebKit/${string} (KHTML, like Gecko) ${string}`
233
+ | `curl/${string}`
234
+ | `PostmanRuntime/${string}`
235
+ | `okhttp/${string}`
236
+ | string; // Para permitir valores personalizados
237
+
238
+ export type AccessControlAllowOriginType = "*" | "null" | `${string}`;
239
+
240
+ export type AccessControlAllowMethodsType =
241
+ | "*"
242
+ | HttpMethod
243
+ | `${HttpMethod}, ${HttpMethod}`
244
+ | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}`
245
+ | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}`
246
+ | `${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}, ${HttpMethod}`
247
+ | string; // Permite cualquier combinación personalizada
248
+
249
+ export type AccessControlAllowHeadersType = string; // Mantiene la flexibilidad y evita el error
250
+
251
+ export type HeadersType = {
252
+ "Content-Type"?: ContentType;
253
+ Authorization?: AuthorizationType;
254
+ Accept?: AcceptType; // Tipos de contenido aceptados
255
+ "Accept-Encoding"?: AcceptEncodingType; // Métodos de compresión aceptados (gzip, br)
256
+ "Accept-Language"?: AcceptLanguageType; // Idiomas aceptados (es-ES, en-US)
257
+ "Cache-Control"?: CacheControlType; // Control de caché (no-cache, max-age=3600)
258
+ Connection?: "keep-alive" | "close";
259
+ "Content-Length"?: `${number}`; // Tamaño del cuerpo en bytes
260
+ "Content-Encoding"?: ContentEncodingType; // Método de codificación (gzip, deflate, br)
261
+ "Content-Language"?: ContentLanguageType; // Idioma del contenido (es, en, fr)
262
+ "Content-Disposition"?: string; // Manejo del contenido (attachment; filename="file.pdf")
263
+ ETag?: ETagType; // Identificador único para cacheo de recursos
264
+ Host?: HostType; // Nombre del servidor al que se envía la solicitud
265
+ Origin?: OriginType; // Origen de la solicitud (https://example.com)
266
+ Referer?: RefererType; // URL de referencia
267
+ "User-Agent"?: UserAgentType; // Información del navegador o cliente
268
+ "Access-Control-Allow-Origin"?: AccessControlAllowOriginType; // Permitir acceso desde ciertos dominios (*, https://example.com)
269
+ "Access-Control-Allow-Methods"?: AccessControlAllowMethodsType; // Métodos permitidos (GET, POST, PUT)
270
+ "Access-Control-Allow-Headers"?: AccessControlAllowHeadersType; // Headers permitidos en la solicitud
271
+ "X-Frame-Options"?: "DENY" | "SAMEORIGIN"; // Protección contra Clickjacking
272
+ "X-XSS-Protection"?: "0" | "1; mode=block"; // Protección contra ataques XSS
273
+ "X-Content-Type-Options"?: "nosniff"; // Evita detección automática de MIME
274
+ "Content-Security-Policy"?: string; // Política de seguridad del contenido
275
+ "Strict-Transport-Security"?: string; // Fuerza el uso de HTTPS
276
+ [key: string]: string | undefined;
277
+ };
package/step.md ADDED
@@ -0,0 +1,4 @@
1
+ - UPDATE VERSION
2
+ - npx tsup
3
+ - npm login
4
+ - npm publish --access public
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2016" /* Compilación a JavaScript ES2016 */,
4
+ "lib": [
5
+ "ES2016",
6
+ "ESNext",
7
+ "DOM"
8
+ ] /* Usa características de ES2016 y ESNext */,
9
+ "module": "NodeNext" /* Usa módulos Node16 para compatibilidad con Node.js 16 */,
10
+ "moduleResolution": "nodenext" /* Usa la resolución de módulos de Node.js v16 */,
11
+ "esModuleInterop": true /* Habilita interoperabilidad con módulos CommonJS */,
12
+ "skipLibCheck": true /* Omitir la verificación de los archivos de declaración */,
13
+ "forceConsistentCasingInFileNames": true /* Asegurar consistencia en las mayúsculas/minúsculas en los nombres de archivo */,
14
+ "strict": true /* Habilitar todas las opciones de verificación estricta */,
15
+ "resolveJsonModule": true /* Permite importar archivos JSON */,
16
+ "allowSyntheticDefaultImports": true /* Permite importar por defecto incluso si el módulo no tiene un export por defecto */,
17
+ "skipDefaultLibCheck": true /* Omitir la comprobación de la librería predeterminada */,
18
+ "outDir": "./dist" /* Directorio de salida para los archivos compilados */,
19
+ "declaration": true /* Generar archivos .d.ts */,
20
+ "declarationMap": true /* Generar mapas de declaración */,
21
+ "sourceMap": true /* Generar mapas fuente para depuración */,
22
+ "noEmit": false /* No evitar la generación de archivos de salida */,
23
+ "resolvePackageJsonExports": true /* Usa el campo `exports` en `package.json` cuando resuelvas importaciones */,
24
+ "paths": {
25
+ "safe-fetch": ["./node_modules/safe-fetch"]
26
+ }
27
+ },
28
+ "include": ["src/**/*" /* Incluir todos los archivos en la carpeta src */],
29
+ "exclude": [
30
+ "node_modules" /* Excluir node_modules */,
31
+ "dist" /* Excluir el directorio de salida */
32
+ ]
33
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"], // Cambia esto según la ubicación de tu archivo principal
5
+ format: ["esm", "cjs"], // Formatos de salida (ESM y CommonJS)
6
+ dts: true, // Genera los archivos de declaración .d.ts
7
+ sourcemap: true, // Genera mapas de origen
8
+ clean: true, // Limpia la carpeta de salida antes de generar
9
+ outDir: "dist", // Carpeta de salida
10
+ });