@fgrzl/fetch 1.1.0-alpha.8 → 1.1.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 +50 -0
- package/CONTRIBUTING.md +327 -0
- package/README.md +50 -21
- package/dist/cjs/index.js +1110 -105
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.min.js +2 -0
- package/dist/cjs/index.min.js.map +1 -0
- package/dist/index.d.ts +1381 -10
- package/dist/index.js +1066 -91
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +1 -0
- package/package.json +14 -5
- package/dist/cjs/client/fetch-client.d.ts +0 -189
- package/dist/cjs/client/fetch-client.d.ts.map +0 -1
- package/dist/cjs/client/fetch-client.js +0 -339
- package/dist/cjs/client/fetch-client.js.map +0 -1
- package/dist/cjs/client/index.d.ts +0 -11
- package/dist/cjs/client/index.d.ts.map +0 -1
- package/dist/cjs/client/index.js +0 -14
- package/dist/cjs/client/index.js.map +0 -1
- package/dist/cjs/client/types.d.ts +0 -63
- package/dist/cjs/client/types.d.ts.map +0 -1
- package/dist/cjs/client/types.js +0 -9
- package/dist/cjs/client/types.js.map +0 -1
- package/dist/cjs/errors/index.d.ts +0 -64
- package/dist/cjs/errors/index.d.ts.map +0 -1
- package/dist/cjs/errors/index.js +0 -79
- package/dist/cjs/errors/index.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -65
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/middleware/authentication/authentication.d.ts +0 -31
- package/dist/cjs/middleware/authentication/authentication.d.ts.map +0 -1
- package/dist/cjs/middleware/authentication/authentication.js +0 -93
- package/dist/cjs/middleware/authentication/authentication.js.map +0 -1
- package/dist/cjs/middleware/authentication/index.d.ts +0 -37
- package/dist/cjs/middleware/authentication/index.d.ts.map +0 -1
- package/dist/cjs/middleware/authentication/index.js +0 -42
- package/dist/cjs/middleware/authentication/index.js.map +0 -1
- package/dist/cjs/middleware/authentication/types.d.ts +0 -73
- package/dist/cjs/middleware/authentication/types.d.ts.map +0 -1
- package/dist/cjs/middleware/authentication/types.js +0 -6
- package/dist/cjs/middleware/authentication/types.js.map +0 -1
- package/dist/cjs/middleware/authorization/authorization.d.ts +0 -30
- package/dist/cjs/middleware/authorization/authorization.d.ts.map +0 -1
- package/dist/cjs/middleware/authorization/authorization.js +0 -82
- package/dist/cjs/middleware/authorization/authorization.js.map +0 -1
- package/dist/cjs/middleware/authorization/index.d.ts +0 -36
- package/dist/cjs/middleware/authorization/index.d.ts.map +0 -1
- package/dist/cjs/middleware/authorization/index.js +0 -41
- package/dist/cjs/middleware/authorization/index.js.map +0 -1
- package/dist/cjs/middleware/authorization/types.d.ts +0 -67
- package/dist/cjs/middleware/authorization/types.d.ts.map +0 -1
- package/dist/cjs/middleware/authorization/types.js +0 -6
- package/dist/cjs/middleware/authorization/types.js.map +0 -1
- package/dist/cjs/middleware/cache/cache.d.ts +0 -41
- package/dist/cjs/middleware/cache/cache.d.ts.map +0 -1
- package/dist/cjs/middleware/cache/cache.js +0 -191
- package/dist/cjs/middleware/cache/cache.js.map +0 -1
- package/dist/cjs/middleware/cache/index.d.ts +0 -44
- package/dist/cjs/middleware/cache/index.d.ts.map +0 -1
- package/dist/cjs/middleware/cache/index.js +0 -50
- package/dist/cjs/middleware/cache/index.js.map +0 -1
- package/dist/cjs/middleware/cache/types.d.ts +0 -89
- package/dist/cjs/middleware/cache/types.d.ts.map +0 -1
- package/dist/cjs/middleware/cache/types.js +0 -6
- package/dist/cjs/middleware/cache/types.js.map +0 -1
- package/dist/cjs/middleware/csrf/csrf.d.ts +0 -34
- package/dist/cjs/middleware/csrf/csrf.d.ts.map +0 -1
- package/dist/cjs/middleware/csrf/csrf.js +0 -94
- package/dist/cjs/middleware/csrf/csrf.js.map +0 -1
- package/dist/cjs/middleware/csrf/index.d.ts +0 -57
- package/dist/cjs/middleware/csrf/index.d.ts.map +0 -1
- package/dist/cjs/middleware/csrf/index.js +0 -62
- package/dist/cjs/middleware/csrf/index.js.map +0 -1
- package/dist/cjs/middleware/csrf/types.d.ts +0 -57
- package/dist/cjs/middleware/csrf/types.d.ts.map +0 -1
- package/dist/cjs/middleware/csrf/types.js +0 -6
- package/dist/cjs/middleware/csrf/types.js.map +0 -1
- package/dist/cjs/middleware/index.d.ts +0 -115
- package/dist/cjs/middleware/index.d.ts.map +0 -1
- package/dist/cjs/middleware/index.js +0 -153
- package/dist/cjs/middleware/index.js.map +0 -1
- package/dist/cjs/middleware/logging/index.d.ts +0 -42
- package/dist/cjs/middleware/logging/index.d.ts.map +0 -1
- package/dist/cjs/middleware/logging/index.js +0 -47
- package/dist/cjs/middleware/logging/index.js.map +0 -1
- package/dist/cjs/middleware/logging/logging.d.ts +0 -29
- package/dist/cjs/middleware/logging/logging.d.ts.map +0 -1
- package/dist/cjs/middleware/logging/logging.js +0 -171
- package/dist/cjs/middleware/logging/logging.js.map +0 -1
- package/dist/cjs/middleware/logging/types.d.ts +0 -90
- package/dist/cjs/middleware/logging/types.d.ts.map +0 -1
- package/dist/cjs/middleware/logging/types.js +0 -6
- package/dist/cjs/middleware/logging/types.js.map +0 -1
- package/dist/cjs/middleware/rate-limit/index.d.ts +0 -16
- package/dist/cjs/middleware/rate-limit/index.d.ts.map +0 -1
- package/dist/cjs/middleware/rate-limit/index.js +0 -21
- package/dist/cjs/middleware/rate-limit/index.js.map +0 -1
- package/dist/cjs/middleware/rate-limit/rate-limit.d.ts +0 -14
- package/dist/cjs/middleware/rate-limit/rate-limit.d.ts.map +0 -1
- package/dist/cjs/middleware/rate-limit/rate-limit.js +0 -87
- package/dist/cjs/middleware/rate-limit/rate-limit.js.map +0 -1
- package/dist/cjs/middleware/rate-limit/types.d.ts +0 -97
- package/dist/cjs/middleware/rate-limit/types.d.ts.map +0 -1
- package/dist/cjs/middleware/rate-limit/types.js +0 -6
- package/dist/cjs/middleware/rate-limit/types.js.map +0 -1
- package/dist/cjs/middleware/retry/index.d.ts +0 -6
- package/dist/cjs/middleware/retry/index.d.ts.map +0 -1
- package/dist/cjs/middleware/retry/index.js +0 -11
- package/dist/cjs/middleware/retry/index.js.map +0 -1
- package/dist/cjs/middleware/retry/retry.d.ts +0 -39
- package/dist/cjs/middleware/retry/retry.d.ts.map +0 -1
- package/dist/cjs/middleware/retry/retry.js +0 -144
- package/dist/cjs/middleware/retry/retry.js.map +0 -1
- package/dist/cjs/middleware/retry/types.d.ts +0 -61
- package/dist/cjs/middleware/retry/types.d.ts.map +0 -1
- package/dist/cjs/middleware/retry/types.js +0 -6
- package/dist/cjs/middleware/retry/types.js.map +0 -1
- package/dist/client/fetch-client.d.ts +0 -189
- package/dist/client/fetch-client.d.ts.map +0 -1
- package/dist/client/fetch-client.js +0 -335
- package/dist/client/fetch-client.js.map +0 -1
- package/dist/client/index.d.ts +0 -11
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -10
- package/dist/client/index.js.map +0 -1
- package/dist/client/types.d.ts +0 -63
- package/dist/client/types.d.ts.map +0 -1
- package/dist/client/types.js +0 -8
- package/dist/client/types.js.map +0 -1
- package/dist/errors/index.d.ts +0 -64
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js +0 -73
- package/dist/errors/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/middleware/authentication/authentication.d.ts +0 -31
- package/dist/middleware/authentication/authentication.d.ts.map +0 -1
- package/dist/middleware/authentication/authentication.js +0 -90
- package/dist/middleware/authentication/authentication.js.map +0 -1
- package/dist/middleware/authentication/index.d.ts +0 -37
- package/dist/middleware/authentication/index.d.ts.map +0 -1
- package/dist/middleware/authentication/index.js +0 -37
- package/dist/middleware/authentication/index.js.map +0 -1
- package/dist/middleware/authentication/types.d.ts +0 -73
- package/dist/middleware/authentication/types.d.ts.map +0 -1
- package/dist/middleware/authentication/types.js +0 -5
- package/dist/middleware/authentication/types.js.map +0 -1
- package/dist/middleware/authorization/authorization.d.ts +0 -30
- package/dist/middleware/authorization/authorization.d.ts.map +0 -1
- package/dist/middleware/authorization/authorization.js +0 -79
- package/dist/middleware/authorization/authorization.js.map +0 -1
- package/dist/middleware/authorization/index.d.ts +0 -36
- package/dist/middleware/authorization/index.d.ts.map +0 -1
- package/dist/middleware/authorization/index.js +0 -36
- package/dist/middleware/authorization/index.js.map +0 -1
- package/dist/middleware/authorization/types.d.ts +0 -67
- package/dist/middleware/authorization/types.d.ts.map +0 -1
- package/dist/middleware/authorization/types.js +0 -5
- package/dist/middleware/authorization/types.js.map +0 -1
- package/dist/middleware/cache/cache.d.ts +0 -41
- package/dist/middleware/cache/cache.d.ts.map +0 -1
- package/dist/middleware/cache/cache.js +0 -186
- package/dist/middleware/cache/cache.js.map +0 -1
- package/dist/middleware/cache/index.d.ts +0 -44
- package/dist/middleware/cache/index.d.ts.map +0 -1
- package/dist/middleware/cache/index.js +0 -44
- package/dist/middleware/cache/index.js.map +0 -1
- package/dist/middleware/cache/types.d.ts +0 -89
- package/dist/middleware/cache/types.d.ts.map +0 -1
- package/dist/middleware/cache/types.js +0 -5
- package/dist/middleware/cache/types.js.map +0 -1
- package/dist/middleware/csrf/csrf.d.ts +0 -34
- package/dist/middleware/csrf/csrf.d.ts.map +0 -1
- package/dist/middleware/csrf/csrf.js +0 -91
- package/dist/middleware/csrf/csrf.js.map +0 -1
- package/dist/middleware/csrf/index.d.ts +0 -57
- package/dist/middleware/csrf/index.d.ts.map +0 -1
- package/dist/middleware/csrf/index.js +0 -57
- package/dist/middleware/csrf/index.js.map +0 -1
- package/dist/middleware/csrf/types.d.ts +0 -57
- package/dist/middleware/csrf/types.d.ts.map +0 -1
- package/dist/middleware/csrf/types.js +0 -5
- package/dist/middleware/csrf/types.js.map +0 -1
- package/dist/middleware/index.d.ts +0 -115
- package/dist/middleware/index.d.ts.map +0 -1
- package/dist/middleware/index.js +0 -134
- package/dist/middleware/index.js.map +0 -1
- package/dist/middleware/logging/index.d.ts +0 -42
- package/dist/middleware/logging/index.d.ts.map +0 -1
- package/dist/middleware/logging/index.js +0 -42
- package/dist/middleware/logging/index.js.map +0 -1
- package/dist/middleware/logging/logging.d.ts +0 -29
- package/dist/middleware/logging/logging.d.ts.map +0 -1
- package/dist/middleware/logging/logging.js +0 -168
- package/dist/middleware/logging/logging.js.map +0 -1
- package/dist/middleware/logging/types.d.ts +0 -90
- package/dist/middleware/logging/types.d.ts.map +0 -1
- package/dist/middleware/logging/types.js +0 -5
- package/dist/middleware/logging/types.js.map +0 -1
- package/dist/middleware/rate-limit/index.d.ts +0 -16
- package/dist/middleware/rate-limit/index.d.ts.map +0 -1
- package/dist/middleware/rate-limit/index.js +0 -16
- package/dist/middleware/rate-limit/index.js.map +0 -1
- package/dist/middleware/rate-limit/rate-limit.d.ts +0 -14
- package/dist/middleware/rate-limit/rate-limit.d.ts.map +0 -1
- package/dist/middleware/rate-limit/rate-limit.js +0 -84
- package/dist/middleware/rate-limit/rate-limit.js.map +0 -1
- package/dist/middleware/rate-limit/types.d.ts +0 -97
- package/dist/middleware/rate-limit/types.d.ts.map +0 -1
- package/dist/middleware/rate-limit/types.js +0 -5
- package/dist/middleware/rate-limit/types.js.map +0 -1
- package/dist/middleware/retry/index.d.ts +0 -6
- package/dist/middleware/retry/index.d.ts.map +0 -1
- package/dist/middleware/retry/index.js +0 -6
- package/dist/middleware/retry/index.js.map +0 -1
- package/dist/middleware/retry/retry.d.ts +0 -39
- package/dist/middleware/retry/retry.d.ts.map +0 -1
- package/dist/middleware/retry/retry.js +0 -141
- package/dist/middleware/retry/retry.js.map +0 -1
- package/dist/middleware/retry/types.d.ts +0 -61
- package/dist/middleware/retry/types.d.ts.map +0 -1
- package/dist/middleware/retry/types.js +0 -5
- package/dist/middleware/retry/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,102 +1,1077 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
// src/client/fetch-client.ts
|
|
2
|
+
var FetchClient = class {
|
|
3
|
+
constructor(config = {}) {
|
|
4
|
+
this.middlewares = [];
|
|
5
|
+
this.credentials = config.credentials ?? "same-origin";
|
|
6
|
+
}
|
|
7
|
+
use(middleware) {
|
|
8
|
+
this.middlewares.push(middleware);
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
async request(url, init = {}) {
|
|
12
|
+
let index = 0;
|
|
13
|
+
const execute = async (request) => {
|
|
14
|
+
const currentRequest = request || { ...init, url };
|
|
15
|
+
const currentUrl = currentRequest.url || url;
|
|
16
|
+
if (index >= this.middlewares.length) {
|
|
17
|
+
const { url: _, ...requestInit } = currentRequest;
|
|
18
|
+
return this.coreFetch(requestInit, currentUrl);
|
|
19
|
+
}
|
|
20
|
+
const middleware = this.middlewares[index++];
|
|
21
|
+
if (!middleware) {
|
|
22
|
+
const { url: _, ...requestInit } = currentRequest;
|
|
23
|
+
return this.coreFetch(requestInit, currentUrl);
|
|
24
|
+
}
|
|
25
|
+
return middleware(currentRequest, execute);
|
|
26
|
+
};
|
|
27
|
+
const result = await execute();
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
async coreFetch(request, url) {
|
|
31
|
+
try {
|
|
32
|
+
const finalInit = {
|
|
33
|
+
credentials: this.credentials,
|
|
34
|
+
...request
|
|
35
|
+
};
|
|
36
|
+
if (finalInit.headers instanceof Headers) {
|
|
37
|
+
const headersObj = {};
|
|
38
|
+
finalInit.headers.forEach((value, key) => {
|
|
39
|
+
headersObj[key] = value;
|
|
40
|
+
});
|
|
41
|
+
finalInit.headers = headersObj;
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(url, finalInit);
|
|
44
|
+
const data = await this.parseResponse(response);
|
|
45
|
+
return {
|
|
46
|
+
data: response.ok ? data : null,
|
|
47
|
+
status: response.status,
|
|
48
|
+
statusText: response.statusText,
|
|
49
|
+
headers: response.headers,
|
|
50
|
+
url: response.url,
|
|
51
|
+
ok: response.ok,
|
|
52
|
+
...response.ok ? {} : {
|
|
53
|
+
error: {
|
|
54
|
+
message: response.statusText,
|
|
55
|
+
body: data
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
61
|
+
return {
|
|
62
|
+
data: null,
|
|
63
|
+
status: 0,
|
|
64
|
+
statusText: "Network Error",
|
|
65
|
+
headers: new Headers(),
|
|
66
|
+
url,
|
|
67
|
+
ok: false,
|
|
68
|
+
error: {
|
|
69
|
+
message: "Failed to fetch",
|
|
70
|
+
body: error
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async parseResponse(res) {
|
|
78
|
+
const contentType = res.headers.get("content-type") || "";
|
|
79
|
+
if (contentType.includes("application/json")) {
|
|
80
|
+
return res.json();
|
|
81
|
+
}
|
|
82
|
+
if (contentType.includes("text/")) {
|
|
83
|
+
return res.text();
|
|
84
|
+
}
|
|
85
|
+
if (contentType.includes("application/octet-stream") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/")) {
|
|
86
|
+
return res.blob();
|
|
87
|
+
}
|
|
88
|
+
if (res.body) {
|
|
89
|
+
const text = await res.text();
|
|
90
|
+
return text || null;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
// Helper method to build URL with query parameters
|
|
95
|
+
buildUrlWithParams(url, params) {
|
|
96
|
+
if (!params) {
|
|
97
|
+
return url;
|
|
98
|
+
}
|
|
99
|
+
const urlObj = new URL(
|
|
100
|
+
url,
|
|
101
|
+
url.startsWith("http") ? void 0 : "http://localhost"
|
|
102
|
+
);
|
|
103
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
104
|
+
if (value !== void 0 && value !== null) {
|
|
105
|
+
urlObj.searchParams.set(key, String(value));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (!url.startsWith("http")) {
|
|
109
|
+
return urlObj.pathname + urlObj.search;
|
|
110
|
+
}
|
|
111
|
+
return urlObj.toString();
|
|
112
|
+
}
|
|
113
|
+
// 🎯 PIT OF SUCCESS: Convenience methods with smart defaults
|
|
114
|
+
/**
|
|
115
|
+
* HEAD request with query parameter support.
|
|
116
|
+
*
|
|
117
|
+
* HEAD requests are used to retrieve metadata about a resource without downloading
|
|
118
|
+
* the response body. Useful for checking if a resource exists, getting content length,
|
|
119
|
+
* last modified date, etc.
|
|
120
|
+
*
|
|
121
|
+
* @template T - Expected response data type (will be null for HEAD requests)
|
|
122
|
+
* @param url - Request URL
|
|
123
|
+
* @param params - Query parameters to append to URL
|
|
124
|
+
* @returns Promise resolving to typed response (data will always be null)
|
|
125
|
+
*
|
|
126
|
+
* @example Check if resource exists:
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const headResponse = await client.head('/api/large-file.zip');
|
|
129
|
+
* if (headResponse.ok) {
|
|
130
|
+
* const contentLength = headResponse.headers.get('content-length');
|
|
131
|
+
* const lastModified = headResponse.headers.get('last-modified');
|
|
132
|
+
* console.log(`File size: ${contentLength} bytes`);
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example Check with query parameters:
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const exists = await client.head('/api/users', { id: 123 });
|
|
139
|
+
* if (exists.status === 404) {
|
|
140
|
+
* console.log('User not found');
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
head(url, params) {
|
|
145
|
+
const finalUrl = this.buildUrlWithParams(url, params);
|
|
146
|
+
return this.request(finalUrl, { method: "HEAD" });
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* HEAD request that returns useful metadata about a resource.
|
|
150
|
+
*
|
|
151
|
+
* This is a convenience method that extracts common metadata from HEAD responses
|
|
152
|
+
* for easier consumption.
|
|
153
|
+
*
|
|
154
|
+
* @param url - Request URL
|
|
155
|
+
* @param params - Query parameters to append to URL
|
|
156
|
+
* @returns Promise resolving to response with extracted metadata
|
|
157
|
+
*
|
|
158
|
+
* @example Get resource metadata:
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const metadata = await client.headMetadata('/api/large-file.zip');
|
|
161
|
+
* if (metadata.ok) {
|
|
162
|
+
* console.log('File exists:', metadata.exists);
|
|
163
|
+
* console.log('Content type:', metadata.contentType);
|
|
164
|
+
* console.log('Size:', metadata.contentLength, 'bytes');
|
|
165
|
+
* console.log('Last modified:', metadata.lastModified);
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
async headMetadata(url, params) {
|
|
170
|
+
const response = await this.head(url, params);
|
|
171
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
172
|
+
const lastModifiedHeader = response.headers.get("last-modified");
|
|
173
|
+
return {
|
|
174
|
+
...response,
|
|
175
|
+
exists: response.ok,
|
|
176
|
+
contentType: response.headers.get("content-type") || void 0,
|
|
177
|
+
contentLength: contentLengthHeader ? parseInt(contentLengthHeader, 10) : void 0,
|
|
178
|
+
lastModified: lastModifiedHeader ? new Date(lastModifiedHeader) : void 0,
|
|
179
|
+
etag: response.headers.get("etag") || void 0,
|
|
180
|
+
cacheControl: response.headers.get("cache-control") || void 0
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* GET request with query parameter support.
|
|
185
|
+
*
|
|
186
|
+
* @template T - Expected response data type
|
|
187
|
+
* @param url - Request URL
|
|
188
|
+
* @param params - Query parameters to append to URL
|
|
189
|
+
* @returns Promise resolving to typed response
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const users = await client.get<User[]>('/api/users');
|
|
194
|
+
* const filteredUsers = await client.get<User[]>('/api/users', { status: 'active', limit: 10 });
|
|
195
|
+
* if (users.ok) console.log(users.data);
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
get(url, params) {
|
|
199
|
+
const finalUrl = this.buildUrlWithParams(url, params);
|
|
200
|
+
return this.request(finalUrl, { method: "GET" });
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* POST request with automatic JSON serialization.
|
|
204
|
+
*
|
|
205
|
+
* @template T - Expected response data type
|
|
206
|
+
* @param url - Request URL
|
|
207
|
+
* @param body - Request body (auto-serialized to JSON)
|
|
208
|
+
* @param headers - Additional headers (Content-Type: application/json is default)
|
|
209
|
+
* @returns Promise resolving to typed response
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* const result = await client.post<User>('/api/users', { name: 'John' });
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
post(url, body, headers) {
|
|
217
|
+
const requestHeaders = {
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
...headers ?? {}
|
|
220
|
+
};
|
|
221
|
+
return this.request(url, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: requestHeaders,
|
|
224
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* PUT request with automatic JSON serialization.
|
|
229
|
+
*
|
|
230
|
+
* @template T - Expected response data type
|
|
231
|
+
* @param url - Request URL
|
|
232
|
+
* @param body - Request body (auto-serialized to JSON)
|
|
233
|
+
* @param headers - Additional headers (Content-Type: application/json is default)
|
|
234
|
+
* @returns Promise resolving to typed response
|
|
235
|
+
*/
|
|
236
|
+
put(url, body, headers) {
|
|
237
|
+
const requestHeaders = {
|
|
238
|
+
"Content-Type": "application/json",
|
|
239
|
+
...headers ?? {}
|
|
240
|
+
};
|
|
241
|
+
return this.request(url, {
|
|
242
|
+
method: "PUT",
|
|
243
|
+
headers: requestHeaders,
|
|
244
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* PATCH request with automatic JSON serialization.
|
|
249
|
+
*
|
|
250
|
+
* @template T - Expected response data type
|
|
251
|
+
* @param url - Request URL
|
|
252
|
+
* @param body - Request body (auto-serialized to JSON)
|
|
253
|
+
* @param headers - Additional headers (Content-Type: application/json is default)
|
|
254
|
+
* @returns Promise resolving to typed response
|
|
255
|
+
*/
|
|
256
|
+
patch(url, body, headers) {
|
|
257
|
+
const requestHeaders = {
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
...headers ?? {}
|
|
260
|
+
};
|
|
261
|
+
return this.request(url, {
|
|
262
|
+
method: "PATCH",
|
|
263
|
+
headers: requestHeaders,
|
|
264
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* DELETE request with query parameter support.
|
|
269
|
+
*
|
|
270
|
+
* @template T - Expected response data type
|
|
271
|
+
* @param url - Request URL
|
|
272
|
+
* @param params - Query parameters to append to URL
|
|
273
|
+
* @returns Promise resolving to typed response
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const result = await client.del('/api/users/123');
|
|
278
|
+
* const bulkResult = await client.del('/api/users', { status: 'inactive', force: true });
|
|
279
|
+
* if (result.ok) console.log('Deleted successfully');
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
del(url, params) {
|
|
283
|
+
const finalUrl = this.buildUrlWithParams(url, params);
|
|
284
|
+
return this.request(finalUrl, { method: "DELETE" });
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/middleware/authentication/authentication.ts
|
|
289
|
+
function shouldSkipAuth(url, skipPatterns = []) {
|
|
290
|
+
return skipPatterns.some((pattern) => {
|
|
291
|
+
if (typeof pattern === "string") {
|
|
292
|
+
return url.includes(pattern);
|
|
293
|
+
}
|
|
294
|
+
return pattern.test(url);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function shouldIncludeAuth(url, includePatterns) {
|
|
298
|
+
if (!includePatterns || includePatterns.length === 0) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return includePatterns.some((pattern) => {
|
|
302
|
+
if (typeof pattern === "string") {
|
|
303
|
+
return url.includes(pattern);
|
|
304
|
+
}
|
|
305
|
+
return pattern.test(url);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function createAuthenticationMiddleware(options) {
|
|
309
|
+
const {
|
|
310
|
+
tokenProvider,
|
|
311
|
+
headerName = "Authorization",
|
|
312
|
+
tokenType = "Bearer",
|
|
313
|
+
skipPatterns = [],
|
|
314
|
+
includePatterns
|
|
315
|
+
} = options;
|
|
316
|
+
return async (request, next) => {
|
|
317
|
+
const url = request.url || "";
|
|
318
|
+
const parsedUrl = new URL(url);
|
|
319
|
+
const pathname = parsedUrl.pathname;
|
|
320
|
+
if (shouldSkipAuth(pathname, skipPatterns) || !shouldIncludeAuth(pathname, includePatterns)) {
|
|
321
|
+
return next(request);
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const token = await tokenProvider();
|
|
325
|
+
if (!token) {
|
|
326
|
+
return next(request);
|
|
327
|
+
}
|
|
328
|
+
const headers = new Headers(request.headers);
|
|
329
|
+
headers.set(headerName, `${tokenType} ${token}`);
|
|
330
|
+
const modifiedRequest = {
|
|
331
|
+
...request,
|
|
332
|
+
headers
|
|
333
|
+
};
|
|
334
|
+
return next(modifiedRequest);
|
|
335
|
+
} catch {
|
|
336
|
+
return next(request);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/middleware/authentication/index.ts
|
|
342
|
+
function useAuthentication(client, options) {
|
|
343
|
+
return client.use(createAuthenticationMiddleware(options));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/middleware/authorization/authorization.ts
|
|
347
|
+
function createRedirectHandler(config = {}) {
|
|
348
|
+
const {
|
|
349
|
+
redirectPath = "/login",
|
|
350
|
+
returnUrlParam = "return_url",
|
|
351
|
+
includeReturnUrl = true
|
|
352
|
+
} = config;
|
|
353
|
+
return () => {
|
|
354
|
+
let redirectUrl = redirectPath;
|
|
355
|
+
if (includeReturnUrl && typeof window !== "undefined") {
|
|
356
|
+
const currentUrl = encodeURIComponent(window.location.href);
|
|
357
|
+
const separator = redirectPath.includes("?") ? "&" : "?";
|
|
358
|
+
redirectUrl = `${redirectPath}${separator}${returnUrlParam}=${currentUrl}`;
|
|
359
|
+
}
|
|
360
|
+
if (typeof window !== "undefined") {
|
|
361
|
+
window.location.href = redirectUrl;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function selectUnauthorizedHandler(providedHandler, redirectConfig) {
|
|
366
|
+
if (providedHandler) {
|
|
367
|
+
return providedHandler;
|
|
368
|
+
}
|
|
369
|
+
return createRedirectHandler(redirectConfig);
|
|
370
|
+
}
|
|
371
|
+
function shouldSkipAuth2(url, skipPatterns = []) {
|
|
372
|
+
let pathname;
|
|
373
|
+
try {
|
|
374
|
+
pathname = new URL(url).pathname;
|
|
375
|
+
} catch {
|
|
376
|
+
pathname = url;
|
|
377
|
+
}
|
|
378
|
+
return skipPatterns.some((pattern) => {
|
|
379
|
+
if (typeof pattern === "string") {
|
|
380
|
+
return pathname.includes(pattern);
|
|
381
|
+
}
|
|
382
|
+
return pattern.test(pathname);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
function createAuthorizationMiddleware(options = {}) {
|
|
386
|
+
const {
|
|
387
|
+
onUnauthorized: providedOnUnauthorized,
|
|
388
|
+
redirectConfig,
|
|
389
|
+
onForbidden,
|
|
390
|
+
skipPatterns = [],
|
|
391
|
+
statusCodes = [401]
|
|
392
|
+
} = options;
|
|
393
|
+
const onUnauthorized = selectUnauthorizedHandler(
|
|
394
|
+
providedOnUnauthorized,
|
|
395
|
+
redirectConfig
|
|
396
|
+
);
|
|
397
|
+
return async (request, next) => {
|
|
398
|
+
const url = request.url || "";
|
|
399
|
+
if (shouldSkipAuth2(url, skipPatterns)) {
|
|
400
|
+
return next(request);
|
|
401
|
+
}
|
|
402
|
+
const response = await next(request);
|
|
403
|
+
if (statusCodes.includes(response.status)) {
|
|
404
|
+
try {
|
|
405
|
+
if (response.status === 401 && onUnauthorized) {
|
|
406
|
+
await onUnauthorized(response, request);
|
|
407
|
+
} else if (response.status === 403 && onForbidden) {
|
|
408
|
+
await onForbidden(response, request);
|
|
409
|
+
} else if (onUnauthorized) {
|
|
410
|
+
await onUnauthorized(response, request);
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.warn("Authorization handler failed:", error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return response;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/middleware/authorization/index.ts
|
|
421
|
+
function useAuthorization(client, options = {}) {
|
|
422
|
+
return client.use(createAuthorizationMiddleware(options));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/middleware/cache/cache.ts
|
|
426
|
+
var MemoryStorage = class {
|
|
427
|
+
constructor() {
|
|
428
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
429
|
+
}
|
|
430
|
+
async get(key) {
|
|
431
|
+
const entry = this.cache.get(key);
|
|
432
|
+
if (!entry) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
if (Date.now() > entry.expiresAt) {
|
|
436
|
+
this.cache.delete(key);
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
return entry;
|
|
440
|
+
}
|
|
441
|
+
async getWithExpiry(key) {
|
|
442
|
+
const entry = this.cache.get(key);
|
|
443
|
+
if (!entry) {
|
|
444
|
+
return { entry: null, isExpired: false };
|
|
445
|
+
}
|
|
446
|
+
const isExpired = Date.now() > entry.expiresAt;
|
|
447
|
+
return { entry, isExpired };
|
|
448
|
+
}
|
|
449
|
+
async set(key, entry) {
|
|
450
|
+
this.cache.set(key, entry);
|
|
451
|
+
}
|
|
452
|
+
async delete(key) {
|
|
453
|
+
this.cache.delete(key);
|
|
454
|
+
}
|
|
455
|
+
async clear() {
|
|
456
|
+
this.cache.clear();
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var defaultKeyGenerator = (request) => {
|
|
460
|
+
const url = request.url || "";
|
|
461
|
+
const method = request.method || "GET";
|
|
462
|
+
const headers = request.headers ? JSON.stringify(request.headers) : "";
|
|
463
|
+
return `${method}:${url}:${headers}`;
|
|
464
|
+
};
|
|
465
|
+
function shouldSkipCache(url, skipPatterns = []) {
|
|
466
|
+
return skipPatterns.some((pattern) => {
|
|
467
|
+
if (typeof pattern === "string") {
|
|
468
|
+
return url.includes(pattern);
|
|
469
|
+
}
|
|
470
|
+
return pattern.test(url);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function createCacheMiddleware(options = {}) {
|
|
474
|
+
const {
|
|
475
|
+
ttl = 5 * 60 * 1e3,
|
|
476
|
+
// 5 minutes
|
|
477
|
+
methods = ["GET"],
|
|
478
|
+
storage = new MemoryStorage(),
|
|
479
|
+
keyGenerator = defaultKeyGenerator,
|
|
480
|
+
skipPatterns = [],
|
|
481
|
+
staleWhileRevalidate = false
|
|
482
|
+
} = options;
|
|
483
|
+
return async (request, next) => {
|
|
484
|
+
const method = (request.method || "GET").toUpperCase();
|
|
485
|
+
const url = request.url || "";
|
|
486
|
+
if (!methods.includes(method) || shouldSkipCache(url, skipPatterns)) {
|
|
487
|
+
return next(request);
|
|
488
|
+
}
|
|
489
|
+
const cacheKey = keyGenerator(request);
|
|
490
|
+
try {
|
|
491
|
+
const { entry: cached, isExpired } = storage.getWithExpiry ? await storage.getWithExpiry(cacheKey) : await (async () => {
|
|
492
|
+
const entry = await storage.get(cacheKey);
|
|
493
|
+
return { entry, isExpired: false };
|
|
494
|
+
})();
|
|
495
|
+
if (cached && !isExpired) {
|
|
496
|
+
return {
|
|
497
|
+
...cached.response,
|
|
498
|
+
headers: new Headers(cached.response.headers),
|
|
499
|
+
data: cached.response.data
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (cached && staleWhileRevalidate) {
|
|
503
|
+
const cachedResponse = {
|
|
504
|
+
...cached.response,
|
|
505
|
+
headers: new Headers(cached.response.headers),
|
|
506
|
+
data: cached.response.data
|
|
507
|
+
};
|
|
508
|
+
if (isExpired) {
|
|
509
|
+
next(request).then(async (freshResponse) => {
|
|
510
|
+
const headersObj = {};
|
|
511
|
+
freshResponse.headers.forEach((value, key) => {
|
|
512
|
+
headersObj[key] = value;
|
|
513
|
+
});
|
|
514
|
+
const cacheEntry = {
|
|
515
|
+
response: {
|
|
516
|
+
status: freshResponse.status,
|
|
517
|
+
statusText: freshResponse.statusText,
|
|
518
|
+
headers: headersObj,
|
|
519
|
+
data: freshResponse.data
|
|
520
|
+
},
|
|
521
|
+
timestamp: Date.now(),
|
|
522
|
+
expiresAt: Date.now() + ttl
|
|
523
|
+
};
|
|
524
|
+
await storage.set(cacheKey, cacheEntry);
|
|
525
|
+
}).catch(() => {
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return cachedResponse;
|
|
529
|
+
}
|
|
530
|
+
const response = await next(request);
|
|
531
|
+
if (response.ok) {
|
|
532
|
+
try {
|
|
533
|
+
const headersObj = {};
|
|
534
|
+
response.headers.forEach((value, key) => {
|
|
535
|
+
headersObj[key] = value;
|
|
536
|
+
});
|
|
537
|
+
const cacheEntry = {
|
|
538
|
+
response: {
|
|
539
|
+
status: response.status,
|
|
540
|
+
statusText: response.statusText,
|
|
541
|
+
headers: headersObj,
|
|
542
|
+
data: response.data
|
|
543
|
+
},
|
|
544
|
+
timestamp: Date.now(),
|
|
545
|
+
expiresAt: Date.now() + ttl
|
|
546
|
+
};
|
|
547
|
+
await storage.set(cacheKey, cacheEntry);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return response;
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
554
|
+
const errorMessage = error.message;
|
|
555
|
+
if (errorMessage.includes("Network") || errorMessage.includes("fetch")) {
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return next(request);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/middleware/cache/index.ts
|
|
565
|
+
function useCache(client, options = {}) {
|
|
566
|
+
return client.use(createCacheMiddleware(options));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/middleware/csrf/csrf.ts
|
|
570
|
+
function getTokenFromCookie(cookieName = "XSRF-TOKEN") {
|
|
571
|
+
if (typeof document === "undefined") {
|
|
572
|
+
return "";
|
|
573
|
+
}
|
|
574
|
+
const name = `${cookieName}=`;
|
|
575
|
+
const decodedCookie = decodeURIComponent(document.cookie);
|
|
576
|
+
const cookies = decodedCookie.split(";");
|
|
577
|
+
for (const cookie of cookies) {
|
|
578
|
+
const c = cookie.trim();
|
|
579
|
+
if (c.indexOf(name) === 0) {
|
|
580
|
+
return c.substring(name.length);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return "";
|
|
584
|
+
}
|
|
585
|
+
function shouldSkipCSRF(url, skipPatterns = []) {
|
|
586
|
+
return skipPatterns.some((pattern) => {
|
|
587
|
+
if (typeof pattern === "string") {
|
|
588
|
+
return url.includes(pattern);
|
|
589
|
+
}
|
|
590
|
+
return pattern.test(url);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function createCSRFMiddleware(options = {}) {
|
|
594
|
+
const {
|
|
595
|
+
headerName = "X-XSRF-TOKEN",
|
|
596
|
+
cookieName = "XSRF-TOKEN",
|
|
597
|
+
protectedMethods = ["POST", "PUT", "PATCH", "DELETE"],
|
|
598
|
+
skipPatterns = [],
|
|
599
|
+
tokenProvider = () => getTokenFromCookie(cookieName)
|
|
600
|
+
} = options;
|
|
601
|
+
return async (request, next) => {
|
|
602
|
+
const method = (request.method || "GET").toUpperCase();
|
|
603
|
+
const url = request.url || "";
|
|
604
|
+
if (!protectedMethods.includes(method) || shouldSkipCSRF(url, skipPatterns)) {
|
|
605
|
+
return next(request);
|
|
606
|
+
}
|
|
607
|
+
const token = tokenProvider();
|
|
608
|
+
if (!token) {
|
|
609
|
+
return next(request);
|
|
610
|
+
}
|
|
611
|
+
const headers = new Headers(request.headers);
|
|
612
|
+
headers.set(headerName, token);
|
|
613
|
+
const modifiedRequest = {
|
|
614
|
+
...request,
|
|
615
|
+
headers
|
|
616
|
+
};
|
|
617
|
+
return next(modifiedRequest);
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/middleware/csrf/index.ts
|
|
622
|
+
function useCSRF(client, options = {}) {
|
|
623
|
+
return client.use(createCSRFMiddleware(options));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/middleware/logging/logging.ts
|
|
627
|
+
var defaultLogger = {
|
|
628
|
+
// eslint-disable-next-line no-console -- allow console.debug in logger implementation
|
|
629
|
+
debug: (message, data) => console.debug(message, data),
|
|
630
|
+
// eslint-disable-next-line no-console -- allow console.info in logger implementation
|
|
631
|
+
info: (message, data) => console.info(message, data),
|
|
632
|
+
// eslint-disable-next-line no-console -- allow console.warn in logger implementation
|
|
633
|
+
warn: (message, data) => console.warn(message, data),
|
|
634
|
+
// eslint-disable-next-line no-console -- allow console.error in logger implementation
|
|
635
|
+
error: (message, data) => console.error(message, data)
|
|
636
|
+
};
|
|
637
|
+
var LOG_LEVELS = {
|
|
638
|
+
debug: 0,
|
|
639
|
+
info: 1,
|
|
640
|
+
warn: 2,
|
|
641
|
+
error: 3
|
|
642
|
+
};
|
|
643
|
+
var defaultFormatter = (entry) => {
|
|
644
|
+
const { method, url, status, duration } = entry;
|
|
645
|
+
let message = `${method} ${url}`;
|
|
646
|
+
if (status) {
|
|
647
|
+
message += ` \u2192 ${status}`;
|
|
648
|
+
}
|
|
649
|
+
if (duration) {
|
|
650
|
+
message += ` (${duration}ms)`;
|
|
651
|
+
}
|
|
652
|
+
return message;
|
|
653
|
+
};
|
|
654
|
+
function shouldSkipLogging(url, skipPatterns = []) {
|
|
655
|
+
return skipPatterns.some((pattern) => {
|
|
656
|
+
if (typeof pattern === "string") {
|
|
657
|
+
return url.includes(pattern);
|
|
658
|
+
}
|
|
659
|
+
return pattern.test(url);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function createLoggingMiddleware(options = {}) {
|
|
663
|
+
const {
|
|
664
|
+
level = "info",
|
|
665
|
+
logger = defaultLogger,
|
|
666
|
+
includeRequestHeaders = false,
|
|
667
|
+
includeResponseHeaders = false,
|
|
668
|
+
includeRequestBody = false,
|
|
669
|
+
includeResponseBody = false,
|
|
670
|
+
skipPatterns = [],
|
|
671
|
+
formatter = defaultFormatter
|
|
672
|
+
} = options;
|
|
673
|
+
const minLevel = LOG_LEVELS[level];
|
|
674
|
+
return async (request, next) => {
|
|
675
|
+
const url = request.url || "";
|
|
676
|
+
const method = (request.method || "GET").toUpperCase();
|
|
677
|
+
if (shouldSkipLogging(url, skipPatterns)) {
|
|
678
|
+
return next(request);
|
|
679
|
+
}
|
|
680
|
+
const startTime = Date.now();
|
|
681
|
+
if (LOG_LEVELS.debug >= minLevel) {
|
|
682
|
+
const requestHeaders = includeRequestHeaders ? getHeadersObject(
|
|
683
|
+
request.headers
|
|
684
|
+
) : void 0;
|
|
685
|
+
const requestBody = includeRequestBody ? request.body : void 0;
|
|
686
|
+
const requestEntry = {
|
|
687
|
+
level: "debug",
|
|
688
|
+
timestamp: startTime,
|
|
689
|
+
method,
|
|
690
|
+
url,
|
|
691
|
+
...requestHeaders && { requestHeaders },
|
|
692
|
+
...requestBody && { requestBody }
|
|
693
|
+
};
|
|
694
|
+
logger.debug(`\u2192 ${formatter(requestEntry)}`, requestEntry);
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
const response = await next(request);
|
|
698
|
+
const duration = Date.now() - startTime;
|
|
699
|
+
const logLevel = response.status >= 400 ? "error" : "info";
|
|
700
|
+
if (LOG_LEVELS[logLevel] >= minLevel) {
|
|
701
|
+
const responseHeaders = includeResponseHeaders ? getHeadersObject(response.headers) : void 0;
|
|
702
|
+
const responseBody = includeResponseBody ? response.data : void 0;
|
|
703
|
+
const responseEntry = {
|
|
704
|
+
level: logLevel,
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
method,
|
|
707
|
+
url,
|
|
708
|
+
status: response.status,
|
|
709
|
+
duration,
|
|
710
|
+
...responseHeaders ? { responseHeaders } : {},
|
|
711
|
+
...responseBody !== void 0 ? { responseBody } : {}
|
|
712
|
+
};
|
|
713
|
+
const logMessage = `\u2190 ${formatter(responseEntry)}`;
|
|
714
|
+
if (logLevel === "error") {
|
|
715
|
+
logger.error(logMessage, responseEntry);
|
|
716
|
+
} else {
|
|
717
|
+
logger.info(logMessage, responseEntry);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return response;
|
|
721
|
+
} catch (error) {
|
|
722
|
+
const duration = Date.now() - startTime;
|
|
723
|
+
if (LOG_LEVELS.error >= minLevel) {
|
|
724
|
+
const errorEntry = {
|
|
725
|
+
level: "error",
|
|
726
|
+
timestamp: Date.now(),
|
|
727
|
+
method,
|
|
728
|
+
url,
|
|
729
|
+
duration,
|
|
730
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
731
|
+
};
|
|
732
|
+
logger.error(`\u2717 ${formatter(errorEntry)}`, errorEntry);
|
|
733
|
+
}
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function getHeadersObject(headers) {
|
|
739
|
+
if (!headers) {
|
|
740
|
+
return void 0;
|
|
741
|
+
}
|
|
742
|
+
const obj = {};
|
|
743
|
+
if (headers instanceof Headers) {
|
|
744
|
+
headers.forEach((value, key) => {
|
|
745
|
+
obj[key] = value;
|
|
746
|
+
});
|
|
747
|
+
return obj;
|
|
748
|
+
} else {
|
|
749
|
+
return headers;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/middleware/logging/index.ts
|
|
754
|
+
function useLogging(client, options = {}) {
|
|
755
|
+
return client.use(createLoggingMiddleware(options));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/middleware/rate-limit/rate-limit.ts
|
|
759
|
+
var TokenBucket = class {
|
|
760
|
+
constructor(maxTokens, refillRate, timeProvider = () => Date.now()) {
|
|
761
|
+
this.maxTokens = maxTokens;
|
|
762
|
+
this.refillRate = refillRate;
|
|
763
|
+
this.timeProvider = timeProvider;
|
|
764
|
+
this.tokens = maxTokens;
|
|
765
|
+
this.lastRefill = this.timeProvider();
|
|
766
|
+
}
|
|
767
|
+
tryConsume() {
|
|
768
|
+
this.refill();
|
|
769
|
+
if (this.tokens >= 1) {
|
|
770
|
+
this.tokens--;
|
|
771
|
+
return { allowed: true };
|
|
772
|
+
}
|
|
773
|
+
const retryAfter = (1 - this.tokens) / this.refillRate;
|
|
774
|
+
return { allowed: false, retryAfter: Math.ceil(retryAfter) };
|
|
775
|
+
}
|
|
776
|
+
refill() {
|
|
777
|
+
const now = this.timeProvider();
|
|
778
|
+
const timePassed = now - this.lastRefill;
|
|
779
|
+
const tokensToAdd = timePassed * this.refillRate;
|
|
780
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
|
|
781
|
+
this.lastRefill = now;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
function createRateLimitMiddleware(options = {}) {
|
|
785
|
+
const {
|
|
786
|
+
maxRequests = 60,
|
|
787
|
+
windowMs = 6e4,
|
|
788
|
+
keyGenerator = () => "default",
|
|
789
|
+
skipPatterns = [],
|
|
790
|
+
onRateLimitExceeded
|
|
791
|
+
} = options;
|
|
792
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
793
|
+
const refillRate = maxRequests / windowMs;
|
|
794
|
+
return async (request, next) => {
|
|
795
|
+
const url = request.url || "";
|
|
796
|
+
if (skipPatterns.some(
|
|
797
|
+
(pattern) => typeof pattern === "string" ? url.includes(pattern) : pattern.test(url)
|
|
798
|
+
)) {
|
|
799
|
+
return next(request);
|
|
800
|
+
}
|
|
801
|
+
const key = keyGenerator(request);
|
|
802
|
+
if (!buckets.has(key)) {
|
|
803
|
+
buckets.set(key, new TokenBucket(maxRequests, refillRate));
|
|
804
|
+
}
|
|
805
|
+
const bucket = buckets.get(key);
|
|
806
|
+
const result = bucket.tryConsume();
|
|
807
|
+
if (!result.allowed) {
|
|
808
|
+
if (onRateLimitExceeded) {
|
|
809
|
+
const customResponse = await onRateLimitExceeded(
|
|
810
|
+
result.retryAfter || 0,
|
|
811
|
+
request
|
|
812
|
+
);
|
|
813
|
+
if (customResponse) {
|
|
814
|
+
return customResponse;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
data: null,
|
|
819
|
+
status: 429,
|
|
820
|
+
statusText: "Too Many Requests",
|
|
821
|
+
headers: new Headers({
|
|
822
|
+
"Retry-After": Math.ceil((result.retryAfter || 0) / 1e3).toString()
|
|
823
|
+
}),
|
|
824
|
+
url: request.url || "",
|
|
825
|
+
ok: false,
|
|
826
|
+
error: {
|
|
827
|
+
message: `Rate limit exceeded. Retry after ${result.retryAfter}ms`,
|
|
828
|
+
body: { retryAfter: result.retryAfter }
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
return next(request);
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// src/middleware/rate-limit/index.ts
|
|
837
|
+
function useRateLimit(client, options = {}) {
|
|
838
|
+
return client.use(createRateLimitMiddleware(options));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/middleware/retry/retry.ts
|
|
842
|
+
var defaultShouldRetry = (response) => {
|
|
843
|
+
return response.status === 0 || response.status >= 500 && response.status < 600;
|
|
844
|
+
};
|
|
845
|
+
var calculateDelay = (attempt, baseDelay, backoff, maxDelay) => {
|
|
846
|
+
let delay;
|
|
847
|
+
switch (backoff) {
|
|
848
|
+
case "exponential":
|
|
849
|
+
delay = baseDelay * Math.pow(2, attempt - 1);
|
|
850
|
+
break;
|
|
851
|
+
case "linear":
|
|
852
|
+
delay = baseDelay * attempt;
|
|
853
|
+
break;
|
|
854
|
+
case "fixed":
|
|
855
|
+
default:
|
|
856
|
+
delay = baseDelay;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
return Math.min(delay, maxDelay);
|
|
860
|
+
};
|
|
861
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
862
|
+
function createRetryMiddleware(options = {}) {
|
|
863
|
+
const {
|
|
864
|
+
maxRetries = 3,
|
|
865
|
+
delay = 1e3,
|
|
866
|
+
backoff = "exponential",
|
|
867
|
+
maxDelay = 3e4,
|
|
868
|
+
shouldRetry = defaultShouldRetry,
|
|
869
|
+
onRetry
|
|
870
|
+
} = options;
|
|
871
|
+
return async (request, next) => {
|
|
872
|
+
let lastResponse;
|
|
873
|
+
let attempt = 0;
|
|
874
|
+
while (attempt <= maxRetries) {
|
|
875
|
+
try {
|
|
876
|
+
const response = await next(request);
|
|
877
|
+
if (response.ok) {
|
|
878
|
+
return response;
|
|
879
|
+
}
|
|
880
|
+
if (!shouldRetry(
|
|
881
|
+
{ status: response.status, ok: response.ok },
|
|
882
|
+
attempt + 1
|
|
883
|
+
)) {
|
|
884
|
+
return response;
|
|
885
|
+
}
|
|
886
|
+
if (attempt >= maxRetries) {
|
|
887
|
+
return response;
|
|
888
|
+
}
|
|
889
|
+
lastResponse = response;
|
|
890
|
+
attempt++;
|
|
891
|
+
const retryDelay = calculateDelay(attempt, delay, backoff, maxDelay);
|
|
892
|
+
if (onRetry) {
|
|
893
|
+
onRetry(attempt, retryDelay, {
|
|
894
|
+
status: response.status,
|
|
895
|
+
statusText: response.statusText
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
await sleep(retryDelay);
|
|
899
|
+
} catch (error) {
|
|
900
|
+
const errorResponse = {
|
|
901
|
+
data: null,
|
|
902
|
+
status: 0,
|
|
903
|
+
statusText: "Network Error",
|
|
904
|
+
headers: new Headers(),
|
|
905
|
+
url: request.url || "",
|
|
906
|
+
ok: false,
|
|
907
|
+
error: {
|
|
908
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
909
|
+
body: error
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
if (!shouldRetry(errorResponse, attempt + 1)) {
|
|
913
|
+
return errorResponse;
|
|
914
|
+
}
|
|
915
|
+
if (attempt >= maxRetries) {
|
|
916
|
+
return errorResponse;
|
|
917
|
+
}
|
|
918
|
+
lastResponse = errorResponse;
|
|
919
|
+
attempt++;
|
|
920
|
+
const retryDelay = calculateDelay(attempt, delay, backoff, maxDelay);
|
|
921
|
+
if (onRetry) {
|
|
922
|
+
onRetry(attempt, retryDelay, {
|
|
923
|
+
status: errorResponse.status,
|
|
924
|
+
statusText: errorResponse.statusText
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
await sleep(retryDelay);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return lastResponse;
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/middleware/retry/index.ts
|
|
935
|
+
function useRetry(client, options = {}) {
|
|
936
|
+
return client.use(createRetryMiddleware(options));
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/middleware/index.ts
|
|
940
|
+
function useProductionStack(client, config = {}) {
|
|
941
|
+
let enhanced = client;
|
|
942
|
+
if (config.auth) {
|
|
943
|
+
enhanced = useAuthentication(enhanced, config.auth);
|
|
944
|
+
}
|
|
945
|
+
if (config.cache !== void 0) {
|
|
946
|
+
enhanced = useCache(enhanced, config.cache);
|
|
947
|
+
}
|
|
948
|
+
if (config.retry !== void 0) {
|
|
949
|
+
enhanced = useRetry(enhanced, config.retry);
|
|
950
|
+
}
|
|
951
|
+
if (config.rateLimit !== void 0) {
|
|
952
|
+
enhanced = useRateLimit(enhanced, config.rateLimit);
|
|
953
|
+
}
|
|
954
|
+
if (config.logging !== void 0) {
|
|
955
|
+
enhanced = useLogging(enhanced, config.logging);
|
|
956
|
+
}
|
|
957
|
+
return enhanced;
|
|
958
|
+
}
|
|
959
|
+
function useDevelopmentStack(client, config = {}) {
|
|
960
|
+
let enhanced = client;
|
|
961
|
+
if (config.auth) {
|
|
962
|
+
enhanced = useAuthentication(enhanced, config.auth);
|
|
963
|
+
}
|
|
964
|
+
enhanced = useRetry(enhanced, {
|
|
965
|
+
maxRetries: 1,
|
|
966
|
+
delay: 100
|
|
967
|
+
});
|
|
968
|
+
enhanced = useLogging(enhanced, {
|
|
969
|
+
level: "debug",
|
|
970
|
+
includeRequestHeaders: true,
|
|
971
|
+
includeResponseHeaders: true,
|
|
972
|
+
includeRequestBody: true,
|
|
973
|
+
includeResponseBody: true
|
|
974
|
+
});
|
|
975
|
+
return enhanced;
|
|
976
|
+
}
|
|
977
|
+
function useBasicStack(client, config) {
|
|
978
|
+
return useRetry(useAuthentication(client, config.auth), { maxRetries: 2 });
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/errors/index.ts
|
|
982
|
+
var FetchError = class extends Error {
|
|
983
|
+
/**
|
|
984
|
+
* Creates a new FetchError.
|
|
985
|
+
* @param message - Error message
|
|
986
|
+
* @param cause - Optional underlying cause
|
|
987
|
+
*/
|
|
988
|
+
constructor(message, cause) {
|
|
989
|
+
super(message);
|
|
990
|
+
this.name = "FetchError";
|
|
991
|
+
if (cause !== void 0) {
|
|
992
|
+
this.cause = cause;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
var HttpError = class extends FetchError {
|
|
997
|
+
/**
|
|
998
|
+
* Creates a new HttpError.
|
|
999
|
+
* @param status - HTTP status code
|
|
1000
|
+
* @param statusText - HTTP status text
|
|
1001
|
+
* @param body - Response body
|
|
1002
|
+
* @param url - The request URL
|
|
1003
|
+
*/
|
|
1004
|
+
constructor(status, statusText, body, url) {
|
|
1005
|
+
super(`HTTP ${status} ${statusText} at ${url}`);
|
|
1006
|
+
this.name = "HttpError";
|
|
1007
|
+
this.status = status;
|
|
1008
|
+
this.statusText = statusText;
|
|
1009
|
+
this.body = body;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var NetworkError = class extends FetchError {
|
|
1013
|
+
/**
|
|
1014
|
+
* Creates a new NetworkError.
|
|
1015
|
+
* @param message - Error message
|
|
1016
|
+
* @param url - The request URL
|
|
1017
|
+
* @param cause - The underlying network error
|
|
1018
|
+
*/
|
|
1019
|
+
constructor(message, url, cause) {
|
|
1020
|
+
super(`Network error for ${url}: ${message}`, cause);
|
|
1021
|
+
this.name = "NetworkError";
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// src/index.ts
|
|
1026
|
+
var api = useProductionStack(
|
|
1027
|
+
new FetchClient({
|
|
58
1028
|
// Smart default: include cookies for session-based auth
|
|
59
1029
|
// Can be overridden by creating a custom FetchClient
|
|
60
|
-
credentials:
|
|
61
|
-
}),
|
|
1030
|
+
credentials: "same-origin"
|
|
1031
|
+
}),
|
|
1032
|
+
{
|
|
62
1033
|
// Smart defaults - users can override as needed
|
|
63
1034
|
retry: {
|
|
64
|
-
|
|
65
|
-
|
|
1035
|
+
maxRetries: 2,
|
|
1036
|
+
delay: 1e3
|
|
66
1037
|
},
|
|
67
1038
|
cache: {
|
|
68
|
-
|
|
69
|
-
|
|
1039
|
+
ttl: 5 * 60 * 1e3,
|
|
1040
|
+
// 5 minutes
|
|
1041
|
+
methods: ["GET"]
|
|
70
1042
|
},
|
|
71
1043
|
logging: {
|
|
72
|
-
|
|
1044
|
+
level: "info"
|
|
73
1045
|
},
|
|
74
1046
|
rateLimit: {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
export {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1047
|
+
maxRequests: 100,
|
|
1048
|
+
windowMs: 60 * 1e3
|
|
1049
|
+
// 100 requests per minute
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
var index_default = api;
|
|
1054
|
+
export {
|
|
1055
|
+
FetchClient,
|
|
1056
|
+
FetchError,
|
|
1057
|
+
HttpError,
|
|
1058
|
+
NetworkError,
|
|
1059
|
+
createAuthenticationMiddleware,
|
|
1060
|
+
createAuthorizationMiddleware,
|
|
1061
|
+
createCacheMiddleware,
|
|
1062
|
+
createLoggingMiddleware,
|
|
1063
|
+
createRateLimitMiddleware,
|
|
1064
|
+
createRetryMiddleware,
|
|
1065
|
+
index_default as default,
|
|
1066
|
+
useAuthentication,
|
|
1067
|
+
useAuthorization,
|
|
1068
|
+
useBasicStack,
|
|
1069
|
+
useCSRF,
|
|
1070
|
+
useCache,
|
|
1071
|
+
useDevelopmentStack,
|
|
1072
|
+
useLogging,
|
|
1073
|
+
useProductionStack,
|
|
1074
|
+
useRateLimit,
|
|
1075
|
+
useRetry
|
|
1076
|
+
};
|
|
102
1077
|
//# sourceMappingURL=index.js.map
|