@fgrzl/fetch 1.1.0 → 1.3.0-alpha.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.
- package/README.md +41 -0
- package/dist/cjs/index.js +105 -9
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +1 -1
- package/dist/cjs/index.min.js.map +1 -1
- package/dist/index.d.ts +162 -1
- package/dist/index.js +103 -9
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,26 @@ npm install @fgrzl/fetch
|
|
|
28
28
|
```ts
|
|
29
29
|
import api from "@fgrzl/fetch";
|
|
30
30
|
|
|
31
|
+
// It just works! 🎉
|
|
32
|
+
const users = await api.get("https://api.example.com/api/users");
|
|
33
|
+
const newUser = await api.post("https://api.example.com/api/users", {
|
|
34
|
+
name: "John",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Built-in error handling
|
|
38
|
+
if (users.ok) {
|
|
39
|
+
console.log("Users:", users.data);
|
|
40
|
+
} else {
|
|
41
|
+
console.error("Error:", users.error?.message);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import api from "@fgrzl/fetch";
|
|
47
|
+
|
|
48
|
+
// Set the base url to keep things simple
|
|
49
|
+
api.setBaseUrl("https://api.example.com");
|
|
50
|
+
|
|
31
51
|
// It just works! 🎉
|
|
32
52
|
const users = await api.get("/api/users");
|
|
33
53
|
const newUser = await api.post("/api/users", { name: "John" });
|
|
@@ -48,7 +68,28 @@ import { FetchClient, useAuthentication } from "@fgrzl/fetch";
|
|
|
48
68
|
const authClient = useAuthentication(new FetchClient(), {
|
|
49
69
|
tokenProvider: () => localStorage.getItem("token") || "",
|
|
50
70
|
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Working with APIs?** Use a base URL:
|
|
51
74
|
|
|
75
|
+
```ts
|
|
76
|
+
import { FetchClient } from "@fgrzl/fetch";
|
|
77
|
+
|
|
78
|
+
const apiClient = new FetchClient({
|
|
79
|
+
baseUrl: "https://api.example.com",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// All relative URLs are prefixed with the base URL
|
|
83
|
+
await apiClient.get("/users"); // → GET https://api.example.com/users
|
|
84
|
+
await apiClient.post("/users", data); // → POST https://api.example.com/users
|
|
85
|
+
|
|
86
|
+
// Absolute URLs work as expected
|
|
87
|
+
await apiClient.get("https://other-api.com/data"); // → GET https://other-api.com/data
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Advanced usage** with middleware stacks:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
52
93
|
// Smart defaults - just works
|
|
53
94
|
useCSRF(client);
|
|
54
95
|
useAuthorization(client); // Redirects to /login with return URL
|
package/dist/cjs/index.js
CHANGED
|
@@ -24,6 +24,8 @@ __export(index_exports, {
|
|
|
24
24
|
FetchError: () => FetchError,
|
|
25
25
|
HttpError: () => HttpError,
|
|
26
26
|
NetworkError: () => NetworkError,
|
|
27
|
+
appendQueryParams: () => appendQueryParams,
|
|
28
|
+
buildQueryParams: () => buildQueryParams,
|
|
27
29
|
createAuthenticationMiddleware: () => createAuthenticationMiddleware,
|
|
28
30
|
createAuthorizationMiddleware: () => createAuthorizationMiddleware,
|
|
29
31
|
createCacheMiddleware: () => createCacheMiddleware,
|
|
@@ -49,16 +51,46 @@ var FetchClient = class {
|
|
|
49
51
|
constructor(config = {}) {
|
|
50
52
|
this.middlewares = [];
|
|
51
53
|
this.credentials = config.credentials ?? "same-origin";
|
|
54
|
+
this.baseUrl = config.baseUrl;
|
|
52
55
|
}
|
|
53
56
|
use(middleware) {
|
|
54
57
|
this.middlewares.push(middleware);
|
|
55
58
|
return this;
|
|
56
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Set or update the base URL for this client instance.
|
|
62
|
+
*
|
|
63
|
+
* When a base URL is set, relative URLs will be resolved against it.
|
|
64
|
+
* Absolute URLs will continue to work unchanged.
|
|
65
|
+
*
|
|
66
|
+
* @param baseUrl - The base URL to set, or undefined to clear it
|
|
67
|
+
* @returns The client instance for method chaining
|
|
68
|
+
*
|
|
69
|
+
* @example Set base URL:
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const client = new FetchClient();
|
|
72
|
+
* client.setBaseUrl('https://api.example.com');
|
|
73
|
+
*
|
|
74
|
+
* // Now relative URLs work
|
|
75
|
+
* await client.get('/users'); // → GET https://api.example.com/users
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @example Chain with middleware:
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const client = useProductionStack(new FetchClient())
|
|
81
|
+
* .setBaseUrl(process.env.API_BASE_URL);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
setBaseUrl(baseUrl) {
|
|
85
|
+
this.baseUrl = baseUrl;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
57
88
|
async request(url, init = {}) {
|
|
89
|
+
const resolvedUrl = this.resolveUrl(url);
|
|
58
90
|
let index = 0;
|
|
59
91
|
const execute = async (request) => {
|
|
60
|
-
const currentRequest = request || { ...init, url };
|
|
61
|
-
const currentUrl = currentRequest.url ||
|
|
92
|
+
const currentRequest = request || { ...init, url: resolvedUrl };
|
|
93
|
+
const currentUrl = currentRequest.url || resolvedUrl;
|
|
62
94
|
if (index >= this.middlewares.length) {
|
|
63
95
|
const { url: _, ...requestInit } = currentRequest;
|
|
64
96
|
return this.coreFetch(requestInit, currentUrl);
|
|
@@ -142,20 +174,48 @@ var FetchClient = class {
|
|
|
142
174
|
if (!params) {
|
|
143
175
|
return url;
|
|
144
176
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
const resolvedUrl = this.resolveUrl(url);
|
|
178
|
+
if (!resolvedUrl.startsWith("http://") && !resolvedUrl.startsWith("https://") && !resolvedUrl.startsWith("//")) {
|
|
179
|
+
const searchParams = new URLSearchParams();
|
|
180
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
181
|
+
if (value !== void 0 && value !== null) {
|
|
182
|
+
searchParams.set(key, String(value));
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
const queryString = searchParams.toString();
|
|
186
|
+
return queryString ? `${resolvedUrl}?${queryString}` : resolvedUrl;
|
|
187
|
+
}
|
|
188
|
+
const urlObj = new URL(resolvedUrl);
|
|
149
189
|
Object.entries(params).forEach(([key, value]) => {
|
|
150
190
|
if (value !== void 0 && value !== null) {
|
|
151
191
|
urlObj.searchParams.set(key, String(value));
|
|
152
192
|
}
|
|
153
193
|
});
|
|
154
|
-
if (!url.startsWith("http")) {
|
|
155
|
-
return urlObj.pathname + urlObj.search;
|
|
156
|
-
}
|
|
157
194
|
return urlObj.toString();
|
|
158
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolves a URL with the base URL if it's relative and base URL is configured
|
|
198
|
+
* @param url - The URL to resolve
|
|
199
|
+
* @returns The resolved URL
|
|
200
|
+
* @private
|
|
201
|
+
*/
|
|
202
|
+
resolveUrl(url) {
|
|
203
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
|
|
204
|
+
return url;
|
|
205
|
+
}
|
|
206
|
+
if (!this.baseUrl) {
|
|
207
|
+
return url;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const baseUrl = new URL(this.baseUrl);
|
|
211
|
+
const resolvedUrl = new URL(url, baseUrl);
|
|
212
|
+
return resolvedUrl.toString();
|
|
213
|
+
} catch {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Invalid URL: Unable to resolve "${url}" with baseUrl "${this.baseUrl}"`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
159
219
|
// 🎯 PIT OF SUCCESS: Convenience methods with smart defaults
|
|
160
220
|
/**
|
|
161
221
|
* HEAD request with query parameter support.
|
|
@@ -1068,6 +1128,40 @@ var NetworkError = class extends FetchError {
|
|
|
1068
1128
|
}
|
|
1069
1129
|
};
|
|
1070
1130
|
|
|
1131
|
+
// src/client/query.ts
|
|
1132
|
+
function buildQueryParams(query) {
|
|
1133
|
+
const params = new URLSearchParams();
|
|
1134
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1135
|
+
if (value !== void 0) {
|
|
1136
|
+
if (Array.isArray(value)) {
|
|
1137
|
+
value.forEach((item) => {
|
|
1138
|
+
if (item !== void 0) {
|
|
1139
|
+
params.append(key, String(item));
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
params.set(key, String(value));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return params.toString();
|
|
1148
|
+
}
|
|
1149
|
+
function appendQueryParams(baseUrl, query) {
|
|
1150
|
+
const queryString = buildQueryParams(query);
|
|
1151
|
+
if (!queryString) {
|
|
1152
|
+
return baseUrl;
|
|
1153
|
+
}
|
|
1154
|
+
const fragmentIndex = baseUrl.indexOf("#");
|
|
1155
|
+
if (fragmentIndex !== -1) {
|
|
1156
|
+
const urlPart = baseUrl.substring(0, fragmentIndex);
|
|
1157
|
+
const fragmentPart = baseUrl.substring(fragmentIndex);
|
|
1158
|
+
const separator2 = urlPart.includes("?") ? "&" : "?";
|
|
1159
|
+
return `${urlPart}${separator2}${queryString}${fragmentPart}`;
|
|
1160
|
+
}
|
|
1161
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
1162
|
+
return `${baseUrl}${separator}${queryString}`;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1071
1165
|
// src/index.ts
|
|
1072
1166
|
var api = useProductionStack(
|
|
1073
1167
|
new FetchClient({
|
|
@@ -1103,6 +1197,8 @@ var index_default = api;
|
|
|
1103
1197
|
FetchError,
|
|
1104
1198
|
HttpError,
|
|
1105
1199
|
NetworkError,
|
|
1200
|
+
appendQueryParams,
|
|
1201
|
+
buildQueryParams,
|
|
1106
1202
|
createAuthenticationMiddleware,
|
|
1107
1203
|
createAuthorizationMiddleware,
|
|
1108
1204
|
createCacheMiddleware,
|