1000fetches 0.2.0 → 0.2.2
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/LICENSE +1 -1
- package/README.md +94 -68
- package/dist/client-C7dvgNnD.js +2 -0
- package/dist/client-C7dvgNnD.js.map +1 -0
- package/dist/client-DOv6m2TJ.mjs +2 -0
- package/dist/client-DOv6m2TJ.mjs.map +1 -0
- package/dist/client.d.ts +22 -0
- package/dist/core.d.ts +57 -0
- package/dist/errors.d.ts +76 -0
- package/dist/http.cjs +2 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.mjs +2 -0
- package/dist/http.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -346
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/schema.d.ts +35 -0
- package/dist/types.d.ts +145 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/path.d.ts +73 -0
- package/dist/utils/streaming.d.ts +9 -0
- package/docs/API.md +754 -0
- package/docs/BEST_PRACTICES.md +606 -0
- package/docs/MIGRATION.md +719 -0
- package/package.json +40 -53
- package/dist/index.cjs.js +0 -2
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -787
- package/dist/index.es.js.map +0 -1
package/dist/index.es.js
DELETED
|
@@ -1,787 +0,0 @@
|
|
|
1
|
-
class HttpError extends Error {
|
|
2
|
-
name = "HttpError";
|
|
3
|
-
status;
|
|
4
|
-
statusText;
|
|
5
|
-
data;
|
|
6
|
-
response;
|
|
7
|
-
url;
|
|
8
|
-
method;
|
|
9
|
-
cause;
|
|
10
|
-
constructor(message, status, statusText, data, response, url, method, cause) {
|
|
11
|
-
const statusCategory = status >= 500 ? "Server Error" : status >= 400 ? "Client Error" : status >= 300 ? "Redirect" : "Success";
|
|
12
|
-
const dataString = data ? JSON.stringify(data) : "No response data";
|
|
13
|
-
const truncatedData = dataString.length > 500 ? dataString.substring(0, 500) + "..." : dataString;
|
|
14
|
-
const enhancedMessage = `HTTP ${statusCategory} (${status}): ${message}
|
|
15
|
-
Request: ${method} ${url}
|
|
16
|
-
Data: ${truncatedData}`;
|
|
17
|
-
super(enhancedMessage, { cause });
|
|
18
|
-
this.status = status;
|
|
19
|
-
this.statusText = statusText;
|
|
20
|
-
this.data = data;
|
|
21
|
-
this.response = response;
|
|
22
|
-
this.url = url;
|
|
23
|
-
this.method = method;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
class NetworkError extends Error {
|
|
27
|
-
name = "NetworkError";
|
|
28
|
-
cause;
|
|
29
|
-
constructor(message, cause) {
|
|
30
|
-
super(message, { cause });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
class SchemaValidationError extends Error {
|
|
34
|
-
name = "SchemaValidationError";
|
|
35
|
-
schema;
|
|
36
|
-
data;
|
|
37
|
-
cause;
|
|
38
|
-
constructor(message, schema, data, cause) {
|
|
39
|
-
super(message, { cause });
|
|
40
|
-
this.schema = schema;
|
|
41
|
-
this.data = data;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
class TimeoutError extends Error {
|
|
45
|
-
name = "TimeoutError";
|
|
46
|
-
cause;
|
|
47
|
-
constructor(message, cause) {
|
|
48
|
-
super(message, { cause });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
class PathParameterError extends Error {
|
|
52
|
-
name = "PathParameterError";
|
|
53
|
-
url;
|
|
54
|
-
requiredParams;
|
|
55
|
-
providedParams;
|
|
56
|
-
cause;
|
|
57
|
-
constructor(message, url, requiredParams, providedParams, cause) {
|
|
58
|
-
const enhancedMessage = `${message}
|
|
59
|
-
URL Template: ${url}
|
|
60
|
-
Expected: [${requiredParams.map((p) => `"${p}"`).join(", ")}], Actual: [${providedParams.map((p) => `"${p}"`).join(", ")}]`;
|
|
61
|
-
super(enhancedMessage, { cause });
|
|
62
|
-
this.url = url;
|
|
63
|
-
this.requiredParams = requiredParams;
|
|
64
|
-
this.providedParams = providedParams;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
class MiddlewareError extends Error {
|
|
68
|
-
name = "MiddlewareError";
|
|
69
|
-
type;
|
|
70
|
-
url;
|
|
71
|
-
method;
|
|
72
|
-
cause;
|
|
73
|
-
constructor(message, type, url, method, cause) {
|
|
74
|
-
const requestInfo = url ? `Request: ${method || "UNKNOWN"} ${url}` : "";
|
|
75
|
-
const enhancedMessage = requestInfo ? `${message}
|
|
76
|
-
${requestInfo}` : message;
|
|
77
|
-
super(enhancedMessage, { cause });
|
|
78
|
-
this.type = type;
|
|
79
|
-
this.url = url;
|
|
80
|
-
this.method = method;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
class SerializationError extends Error {
|
|
84
|
-
name = "SerializationError";
|
|
85
|
-
cause;
|
|
86
|
-
constructor(message, cause) {
|
|
87
|
-
super(message, { cause });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
class InvalidSchemaError extends Error {
|
|
91
|
-
name = "InvalidSchemaError";
|
|
92
|
-
schema;
|
|
93
|
-
cause;
|
|
94
|
-
constructor(message, schema, cause) {
|
|
95
|
-
super(message, { cause });
|
|
96
|
-
this.schema = schema;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
class AsyncSchemaValidationError extends Error {
|
|
100
|
-
name = "AsyncSchemaValidationError";
|
|
101
|
-
schema;
|
|
102
|
-
cause;
|
|
103
|
-
constructor(message, schema, cause) {
|
|
104
|
-
super(message, { cause });
|
|
105
|
-
this.schema = schema;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
class InvalidBaseUrlError extends Error {
|
|
109
|
-
name = "InvalidBaseUrlError";
|
|
110
|
-
baseUrl;
|
|
111
|
-
cause;
|
|
112
|
-
constructor(message, baseUrl, cause) {
|
|
113
|
-
const enhancedMessage = `${message}
|
|
114
|
-
Base URL: ${baseUrl}`;
|
|
115
|
-
super(enhancedMessage, { cause });
|
|
116
|
-
this.baseUrl = baseUrl;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function generatePath(path, params = {}) {
|
|
120
|
-
return path.replace(/:([^/\s?#]+)\??/g, (_match, paramName) => {
|
|
121
|
-
if (!/^[a-zA-Z0-9_]+$/.test(paramName)) {
|
|
122
|
-
throw new PathParameterError(
|
|
123
|
-
`Invalid path parameter name: "${paramName}"`,
|
|
124
|
-
path,
|
|
125
|
-
[paramName],
|
|
126
|
-
Object.keys(params)
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
const paramValue = params[paramName];
|
|
130
|
-
if (paramValue === void 0) {
|
|
131
|
-
throw new PathParameterError(
|
|
132
|
-
`Missing required path parameter: "${paramName}"`,
|
|
133
|
-
path,
|
|
134
|
-
[paramName],
|
|
135
|
-
Object.keys(params)
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
const stringValue = String(paramValue);
|
|
139
|
-
if (stringValue.includes("/") || stringValue.includes("?")) {
|
|
140
|
-
throw new PathParameterError(
|
|
141
|
-
`Invalid path parameter value for "${paramName}": contains invalid characters`,
|
|
142
|
-
path,
|
|
143
|
-
[paramName],
|
|
144
|
-
Object.keys(params)
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
return stringValue;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
function createStreamingStream(reader, onChunk, totalBytes, signal) {
|
|
151
|
-
let transferredBytes = 0;
|
|
152
|
-
let readerReleased = false;
|
|
153
|
-
let isAborted = false;
|
|
154
|
-
const releaseReader = () => {
|
|
155
|
-
if (!readerReleased) {
|
|
156
|
-
readerReleased = true;
|
|
157
|
-
try {
|
|
158
|
-
reader.releaseLock();
|
|
159
|
-
} catch {
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
const abortHandler = () => {
|
|
164
|
-
isAborted = true;
|
|
165
|
-
releaseReader();
|
|
166
|
-
};
|
|
167
|
-
signal?.addEventListener("abort", abortHandler);
|
|
168
|
-
return new ReadableStream({
|
|
169
|
-
async start(controller) {
|
|
170
|
-
try {
|
|
171
|
-
while (!isAborted) {
|
|
172
|
-
const { done, value } = await reader.read();
|
|
173
|
-
if (done) break;
|
|
174
|
-
transferredBytes += value.length;
|
|
175
|
-
onChunk(value, totalBytes, transferredBytes);
|
|
176
|
-
controller.enqueue(value);
|
|
177
|
-
}
|
|
178
|
-
if (!isAborted) {
|
|
179
|
-
controller.close();
|
|
180
|
-
}
|
|
181
|
-
} catch (error) {
|
|
182
|
-
if (!isAborted) {
|
|
183
|
-
controller.error(error);
|
|
184
|
-
}
|
|
185
|
-
throw error;
|
|
186
|
-
} finally {
|
|
187
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
188
|
-
releaseReader();
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
cancel() {
|
|
192
|
-
isAborted = true;
|
|
193
|
-
signal?.removeEventListener("abort", abortHandler);
|
|
194
|
-
releaseReader();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
async function toStreamableRequest(request, onUploadStreaming) {
|
|
199
|
-
if (!onUploadStreaming || !request.body) {
|
|
200
|
-
return request;
|
|
201
|
-
}
|
|
202
|
-
if (!request.body.getReader) {
|
|
203
|
-
return request;
|
|
204
|
-
}
|
|
205
|
-
const reader = request.body.getReader();
|
|
206
|
-
const totalBytes = request.headers.get("content-length") ? parseInt(request.headers.get("content-length") || "0", 10) : void 0;
|
|
207
|
-
const stream = createStreamingStream(
|
|
208
|
-
reader,
|
|
209
|
-
(chunk, totalBytes2, transferredBytes) => {
|
|
210
|
-
onUploadStreaming({
|
|
211
|
-
chunk,
|
|
212
|
-
totalBytes: totalBytes2,
|
|
213
|
-
transferredBytes
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
totalBytes,
|
|
217
|
-
request.signal
|
|
218
|
-
);
|
|
219
|
-
return new Request(request.url, {
|
|
220
|
-
method: request.method,
|
|
221
|
-
headers: request.headers,
|
|
222
|
-
body: stream,
|
|
223
|
-
signal: request.signal,
|
|
224
|
-
credentials: request.credentials,
|
|
225
|
-
cache: request.cache,
|
|
226
|
-
mode: request.mode,
|
|
227
|
-
redirect: request.redirect,
|
|
228
|
-
referrer: request.referrer,
|
|
229
|
-
referrerPolicy: request.referrerPolicy,
|
|
230
|
-
integrity: request.integrity,
|
|
231
|
-
keepalive: request.keepalive,
|
|
232
|
-
duplex: "half"
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
async function toStreamableResponse(response, onDownloadStreaming) {
|
|
236
|
-
if (!onDownloadStreaming || !response.body) {
|
|
237
|
-
return response;
|
|
238
|
-
}
|
|
239
|
-
if (!response.body.getReader) {
|
|
240
|
-
return response;
|
|
241
|
-
}
|
|
242
|
-
const reader = response.body.getReader();
|
|
243
|
-
const totalBytes = response.headers.get("content-length") ? parseInt(response.headers.get("content-length") || "0", 10) : void 0;
|
|
244
|
-
const stream = createStreamingStream(
|
|
245
|
-
reader,
|
|
246
|
-
(chunk, totalBytes2, transferredBytes) => {
|
|
247
|
-
onDownloadStreaming({
|
|
248
|
-
chunk,
|
|
249
|
-
totalBytes: totalBytes2,
|
|
250
|
-
transferredBytes
|
|
251
|
-
});
|
|
252
|
-
},
|
|
253
|
-
totalBytes
|
|
254
|
-
);
|
|
255
|
-
return new Response(stream, {
|
|
256
|
-
status: response.status,
|
|
257
|
-
statusText: response.statusText,
|
|
258
|
-
headers: response.headers
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
const DEFAULT_RETRY_OPTIONS = {
|
|
262
|
-
maxRetries: 3,
|
|
263
|
-
retryDelay: 300,
|
|
264
|
-
backoffFactor: 2,
|
|
265
|
-
retryStatusCodes: [429, 500, 502, 503, 504],
|
|
266
|
-
retryNetworkErrors: true,
|
|
267
|
-
maxRetryDelay: 3e4,
|
|
268
|
-
shouldRetry: () => true
|
|
269
|
-
};
|
|
270
|
-
function createHttpRequest(config = {}) {
|
|
271
|
-
const {
|
|
272
|
-
baseUrl = "",
|
|
273
|
-
headers: defaultHeaders = {},
|
|
274
|
-
timeout: defaultTimeout = 3e4,
|
|
275
|
-
fetch: customFetch = fetch,
|
|
276
|
-
serializeBody: customSerializeBody,
|
|
277
|
-
serializeParams: customSerializeParams,
|
|
278
|
-
onRequestMiddleware,
|
|
279
|
-
onResponseMiddleware
|
|
280
|
-
} = config;
|
|
281
|
-
return async function fetcher(url, options = {}) {
|
|
282
|
-
const {
|
|
283
|
-
method = "GET",
|
|
284
|
-
headers = {},
|
|
285
|
-
params,
|
|
286
|
-
pathParams,
|
|
287
|
-
body,
|
|
288
|
-
timeout = defaultTimeout,
|
|
289
|
-
signal,
|
|
290
|
-
credentials,
|
|
291
|
-
cache,
|
|
292
|
-
mode,
|
|
293
|
-
redirect,
|
|
294
|
-
onUploadStreaming,
|
|
295
|
-
onDownloadStreaming,
|
|
296
|
-
responseType,
|
|
297
|
-
retryOptions: requestRetryOptions
|
|
298
|
-
} = options;
|
|
299
|
-
let urlObj;
|
|
300
|
-
let resolvedUrl = generatePath(url, pathParams);
|
|
301
|
-
if (baseUrl || params) {
|
|
302
|
-
if (baseUrl) {
|
|
303
|
-
resolvedUrl = constructUrl(baseUrl, resolvedUrl);
|
|
304
|
-
urlObj = new URL(resolvedUrl);
|
|
305
|
-
} else {
|
|
306
|
-
urlObj = new URL(resolvedUrl);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
const baseRequestContext = {
|
|
310
|
-
url: resolvedUrl,
|
|
311
|
-
method,
|
|
312
|
-
params,
|
|
313
|
-
headers: new Headers({ ...defaultHeaders, ...headers }),
|
|
314
|
-
body,
|
|
315
|
-
signal,
|
|
316
|
-
fetchOptions: { credentials, cache, mode, redirect }
|
|
317
|
-
};
|
|
318
|
-
let requestContext = baseRequestContext;
|
|
319
|
-
if (onRequestMiddleware) {
|
|
320
|
-
try {
|
|
321
|
-
const contextCopy = {
|
|
322
|
-
...baseRequestContext,
|
|
323
|
-
headers: new Headers(baseRequestContext.headers),
|
|
324
|
-
fetchOptions: { ...baseRequestContext.fetchOptions }
|
|
325
|
-
};
|
|
326
|
-
requestContext = await onRequestMiddleware(contextCopy);
|
|
327
|
-
if (requestContext.url !== resolvedUrl) {
|
|
328
|
-
urlObj = new URL(requestContext.url);
|
|
329
|
-
}
|
|
330
|
-
} catch (error) {
|
|
331
|
-
throw new MiddlewareError(
|
|
332
|
-
createErrorMessage("Request middleware failed", error),
|
|
333
|
-
"request",
|
|
334
|
-
requestContext.url,
|
|
335
|
-
requestContext.method,
|
|
336
|
-
createStandardizedError(error, "Request middleware")
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (requestContext.params) {
|
|
341
|
-
if (!urlObj) {
|
|
342
|
-
urlObj = new URL(requestContext.url);
|
|
343
|
-
}
|
|
344
|
-
const serializedParams = customSerializeParams ? customSerializeParams(requestContext.params) : serializeQueryParams(requestContext.params);
|
|
345
|
-
if (serializedParams) {
|
|
346
|
-
if (customSerializeParams) {
|
|
347
|
-
const queryString = serializedParams.startsWith("?") ? serializedParams.slice(1) : serializedParams;
|
|
348
|
-
const separator = urlObj.search ? "&" : "?";
|
|
349
|
-
urlObj.search += separator + queryString;
|
|
350
|
-
} else {
|
|
351
|
-
const newSearchParams = new URLSearchParams(serializedParams);
|
|
352
|
-
for (const [key, value] of newSearchParams) {
|
|
353
|
-
urlObj.searchParams.append(key, value);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
requestContext.url = urlObj.toString();
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
const requestInit = {
|
|
360
|
-
method: requestContext.method,
|
|
361
|
-
headers: requestContext.headers,
|
|
362
|
-
...requestContext.fetchOptions
|
|
363
|
-
};
|
|
364
|
-
if (requestContext.body !== void 0 && (requestContext.method === "POST" || requestContext.method === "PUT" || requestContext.method === "PATCH")) {
|
|
365
|
-
let body2;
|
|
366
|
-
let contentType;
|
|
367
|
-
if (customSerializeBody) {
|
|
368
|
-
const serializedBody = customSerializeBody(requestContext.body);
|
|
369
|
-
if (serializedBody == null) {
|
|
370
|
-
body2 = "";
|
|
371
|
-
} else {
|
|
372
|
-
body2 = serializedBody;
|
|
373
|
-
if (isObjectLike(requestContext.body) && typeof serializedBody === "string" && !requestContext.headers.has("content-type")) {
|
|
374
|
-
contentType = "application/json";
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
const serialized = serializeRequestBody(requestContext.body);
|
|
379
|
-
body2 = serialized.body;
|
|
380
|
-
contentType = serialized.contentType;
|
|
381
|
-
}
|
|
382
|
-
requestInit.body = body2;
|
|
383
|
-
if (contentType) {
|
|
384
|
-
requestContext.headers.set("content-type", contentType);
|
|
385
|
-
}
|
|
386
|
-
requestInit.duplex = "half";
|
|
387
|
-
}
|
|
388
|
-
const mergedRetryOptions = {
|
|
389
|
-
...DEFAULT_RETRY_OPTIONS,
|
|
390
|
-
...config.retryOptions,
|
|
391
|
-
...requestRetryOptions
|
|
392
|
-
};
|
|
393
|
-
const maxRetries = mergedRetryOptions.maxRetries;
|
|
394
|
-
let lastError;
|
|
395
|
-
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
396
|
-
const controller = new AbortController();
|
|
397
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
398
|
-
const finalSignal = createCombinedSignal(
|
|
399
|
-
requestContext.signal,
|
|
400
|
-
controller.signal,
|
|
401
|
-
() => clearTimeout(timeoutId)
|
|
402
|
-
);
|
|
403
|
-
try {
|
|
404
|
-
const request = new Request(requestContext.url, requestInit);
|
|
405
|
-
const trackedRequest = onUploadStreaming ? await toStreamableRequest(request, onUploadStreaming) : request;
|
|
406
|
-
const response = await customFetch(trackedRequest.url, {
|
|
407
|
-
method: trackedRequest.method,
|
|
408
|
-
headers: trackedRequest.headers,
|
|
409
|
-
body: trackedRequest.body,
|
|
410
|
-
signal: finalSignal,
|
|
411
|
-
...requestContext.fetchOptions,
|
|
412
|
-
...trackedRequest.body && { duplex: "half" }
|
|
413
|
-
});
|
|
414
|
-
const trackedResponse = onDownloadStreaming ? await toStreamableResponse(response, onDownloadStreaming) : response;
|
|
415
|
-
const responseData = await processResponse(trackedResponse, {
|
|
416
|
-
responseType,
|
|
417
|
-
method,
|
|
418
|
-
url: requestContext.url
|
|
419
|
-
});
|
|
420
|
-
if (onResponseMiddleware) {
|
|
421
|
-
try {
|
|
422
|
-
const middlewareResult = await onResponseMiddleware(responseData);
|
|
423
|
-
if (!middlewareResult || typeof middlewareResult !== "object" || !("data" in middlewareResult)) {
|
|
424
|
-
throw new MiddlewareError(
|
|
425
|
-
"Response middleware must return a valid ResponseType object",
|
|
426
|
-
"response",
|
|
427
|
-
requestContext.url,
|
|
428
|
-
method
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
return middlewareResult;
|
|
432
|
-
} catch (error) {
|
|
433
|
-
throw new MiddlewareError(
|
|
434
|
-
createErrorMessage("Response middleware failed", error),
|
|
435
|
-
"response",
|
|
436
|
-
requestContext.url,
|
|
437
|
-
method,
|
|
438
|
-
createStandardizedError(error, "Response middleware")
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return responseData;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
clearTimeout(timeoutId);
|
|
445
|
-
lastError = createStandardizedError(error, "Request execution");
|
|
446
|
-
if (lastError.name === "AbortError") {
|
|
447
|
-
lastError = new TimeoutError(
|
|
448
|
-
`Request timeout after ${timeout}ms`,
|
|
449
|
-
lastError
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
if (attempt >= maxRetries || !await shouldRetry(lastError, attempt, mergedRetryOptions)) {
|
|
453
|
-
throw lastError;
|
|
454
|
-
}
|
|
455
|
-
const delay = calculateRetryDelay(attempt, mergedRetryOptions);
|
|
456
|
-
await sleep(delay);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
throw new Error("Retry loop terminated unexpectedly");
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
async function processResponse(response, options) {
|
|
463
|
-
const headers = Object.fromEntries(response.headers.entries());
|
|
464
|
-
let data;
|
|
465
|
-
try {
|
|
466
|
-
const requestedType = options.responseType;
|
|
467
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
468
|
-
const detectedType = contentType.includes("application/json") ? "json" : contentType.includes("text/") ? "text" : contentType.includes("application/octet-stream") || contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/") ? "arrayBuffer" : contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded") ? "formData" : void 0;
|
|
469
|
-
const finalType = requestedType ?? detectedType;
|
|
470
|
-
if (finalType === "json") {
|
|
471
|
-
data = await response.json();
|
|
472
|
-
} else if (finalType === "text") {
|
|
473
|
-
data = await response.text();
|
|
474
|
-
} else if (finalType === "blob") {
|
|
475
|
-
data = await response.blob();
|
|
476
|
-
} else if (finalType === "arrayBuffer") {
|
|
477
|
-
data = await response.arrayBuffer();
|
|
478
|
-
} else if (finalType === "formData") {
|
|
479
|
-
data = await response.formData();
|
|
480
|
-
} else {
|
|
481
|
-
const responseClone = response.clone();
|
|
482
|
-
try {
|
|
483
|
-
data = await response.json();
|
|
484
|
-
} catch {
|
|
485
|
-
data = await responseClone.text();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
} catch (error) {
|
|
489
|
-
throw new SerializationError(
|
|
490
|
-
createErrorMessage("Failed to parse response body", error),
|
|
491
|
-
error instanceof Error ? error : void 0
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
if (!response.ok) {
|
|
495
|
-
throw new HttpError(
|
|
496
|
-
`HTTP ${response.status} ${response.statusText}`,
|
|
497
|
-
response.status,
|
|
498
|
-
response.statusText,
|
|
499
|
-
data,
|
|
500
|
-
response,
|
|
501
|
-
options.url,
|
|
502
|
-
options.method
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
return {
|
|
506
|
-
data,
|
|
507
|
-
status: response.status,
|
|
508
|
-
statusText: response.statusText,
|
|
509
|
-
headers,
|
|
510
|
-
method: options.method,
|
|
511
|
-
url: options.url,
|
|
512
|
-
raw: response
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
async function shouldRetry(error, retryCount, retryOptions) {
|
|
516
|
-
if (!retryOptions) return false;
|
|
517
|
-
if (retryOptions.shouldRetry) {
|
|
518
|
-
return await retryOptions.shouldRetry(error, retryCount);
|
|
519
|
-
}
|
|
520
|
-
if (error instanceof HttpError) {
|
|
521
|
-
return retryOptions.retryStatusCodes?.includes(error.status) ?? false;
|
|
522
|
-
}
|
|
523
|
-
if (error instanceof NetworkError || error.name === "TypeError") {
|
|
524
|
-
return retryOptions.retryNetworkErrors ?? false;
|
|
525
|
-
}
|
|
526
|
-
return false;
|
|
527
|
-
}
|
|
528
|
-
function calculateRetryDelay(attempt, retryOptions) {
|
|
529
|
-
if (!retryOptions) return 0;
|
|
530
|
-
const baseDelay = retryOptions.retryDelay ?? 300;
|
|
531
|
-
const backoffFactor = retryOptions.backoffFactor ?? 2;
|
|
532
|
-
const maxDelay = retryOptions.maxRetryDelay ?? 3e4;
|
|
533
|
-
const delay = baseDelay * Math.pow(backoffFactor, attempt);
|
|
534
|
-
return Math.min(delay, maxDelay);
|
|
535
|
-
}
|
|
536
|
-
function isObjectLike(value) {
|
|
537
|
-
return value !== null && typeof value === "object" && value.constructor?.name === "Object" || Array.isArray(value) || value !== null && typeof value === "object" && "toJSON" in value && typeof value.toJSON === "function";
|
|
538
|
-
}
|
|
539
|
-
function serializeQueryParams(params) {
|
|
540
|
-
const searchParams = new URLSearchParams();
|
|
541
|
-
for (const [key, value] of Object.entries(params)) {
|
|
542
|
-
if (Array.isArray(value)) {
|
|
543
|
-
for (const item of value) {
|
|
544
|
-
if (item !== void 0 && item !== null) {
|
|
545
|
-
searchParams.append(key, String(item));
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
} else if (value !== void 0 && value !== null) {
|
|
549
|
-
searchParams.append(key, String(value));
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return searchParams.toString();
|
|
553
|
-
}
|
|
554
|
-
function serializeRequestBody(body) {
|
|
555
|
-
if (typeof body === "string" || body instanceof FormData || body instanceof URLSearchParams || body instanceof ArrayBuffer || body instanceof Blob || body instanceof ReadableStream) {
|
|
556
|
-
return { body };
|
|
557
|
-
}
|
|
558
|
-
if (isObjectLike(body)) {
|
|
559
|
-
return {
|
|
560
|
-
body: JSON.stringify(body),
|
|
561
|
-
contentType: "application/json"
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
if (body === null || body === void 0) {
|
|
565
|
-
return { body: "" };
|
|
566
|
-
}
|
|
567
|
-
return { body: String(body) };
|
|
568
|
-
}
|
|
569
|
-
function createErrorMessage(context, error) {
|
|
570
|
-
return `${context}: ${error instanceof Error ? error.message : String(error)}`;
|
|
571
|
-
}
|
|
572
|
-
function sleep(ms) {
|
|
573
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
574
|
-
}
|
|
575
|
-
function createCombinedSignal(requestSignal, timeoutSignal, cleanup) {
|
|
576
|
-
if (!requestSignal && !timeoutSignal) {
|
|
577
|
-
return new AbortController().signal;
|
|
578
|
-
}
|
|
579
|
-
if (requestSignal?.aborted) {
|
|
580
|
-
return requestSignal;
|
|
581
|
-
}
|
|
582
|
-
const combinedController = new AbortController();
|
|
583
|
-
const handleAbort = () => {
|
|
584
|
-
cleanup?.();
|
|
585
|
-
combinedController.abort();
|
|
586
|
-
};
|
|
587
|
-
requestSignal?.addEventListener("abort", handleAbort);
|
|
588
|
-
timeoutSignal?.addEventListener("abort", handleAbort);
|
|
589
|
-
return combinedController.signal;
|
|
590
|
-
}
|
|
591
|
-
function createStandardizedError(error, context) {
|
|
592
|
-
if (error instanceof Error) {
|
|
593
|
-
return error;
|
|
594
|
-
}
|
|
595
|
-
return new Error(`${context}: ${String(error)}`);
|
|
596
|
-
}
|
|
597
|
-
function constructUrl(baseUrl, requestUrl) {
|
|
598
|
-
if (requestUrl.startsWith("http://") || requestUrl.startsWith("https://")) {
|
|
599
|
-
return requestUrl;
|
|
600
|
-
}
|
|
601
|
-
const baseUrlObj = new URL(baseUrl);
|
|
602
|
-
const basePath = baseUrlObj.pathname.replace(/\/$/, "");
|
|
603
|
-
const [path, query] = requestUrl.split("?");
|
|
604
|
-
if (requestUrl.startsWith("/")) {
|
|
605
|
-
baseUrlObj.pathname = basePath + path;
|
|
606
|
-
} else if (requestUrl) {
|
|
607
|
-
baseUrlObj.pathname = basePath + "/" + path;
|
|
608
|
-
}
|
|
609
|
-
if (query) {
|
|
610
|
-
baseUrlObj.search = baseUrlObj.search ? `${baseUrlObj.search}&${query}` : `?${query}`;
|
|
611
|
-
}
|
|
612
|
-
return baseUrlObj.toString();
|
|
613
|
-
}
|
|
614
|
-
function isStandardSchema(obj) {
|
|
615
|
-
return obj !== null && (typeof obj === "object" || typeof obj === "function") && "~standard" in obj && typeof obj["~standard"] === "object" && obj["~standard"] !== null && "validate" in obj["~standard"] && typeof obj["~standard"].validate === "function" && "version" in obj["~standard"] && typeof obj["~standard"].version === "number" && "vendor" in obj["~standard"] && typeof obj["~standard"].vendor === "string";
|
|
616
|
-
}
|
|
617
|
-
function createSchemaValidator() {
|
|
618
|
-
return {
|
|
619
|
-
validate(schema, data) {
|
|
620
|
-
if (!isStandardSchema(schema)) {
|
|
621
|
-
throw new InvalidSchemaError(
|
|
622
|
-
"Schema must implement the Standard Schema interface",
|
|
623
|
-
schema
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
const result = schema["~standard"].validate(data);
|
|
627
|
-
if (result instanceof Promise) {
|
|
628
|
-
throw new AsyncSchemaValidationError(
|
|
629
|
-
"Async Standard Schema validation is not supported in this context",
|
|
630
|
-
schema
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
if (result.issues) {
|
|
634
|
-
throw new SchemaValidationError(
|
|
635
|
-
JSON.stringify(result.issues),
|
|
636
|
-
schema,
|
|
637
|
-
data
|
|
638
|
-
);
|
|
639
|
-
}
|
|
640
|
-
return result.value;
|
|
641
|
-
},
|
|
642
|
-
isSchema(obj) {
|
|
643
|
-
return isStandardSchema(obj);
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
function validateAndNormalizeBaseUrl(baseUrl) {
|
|
648
|
-
const hasLocation = typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.origin !== "null";
|
|
649
|
-
if (!baseUrl) {
|
|
650
|
-
return hasLocation ? window.location.origin : "";
|
|
651
|
-
}
|
|
652
|
-
if (baseUrl.startsWith("/")) {
|
|
653
|
-
return hasLocation ? new URL(baseUrl, window.location.origin).href.replace(/\/$/, "") : baseUrl.replace(/\/$/, "");
|
|
654
|
-
}
|
|
655
|
-
try {
|
|
656
|
-
new URL(baseUrl);
|
|
657
|
-
} catch {
|
|
658
|
-
throw new InvalidBaseUrlError(
|
|
659
|
-
`Invalid baseUrl: "${baseUrl}". Must be a valid absolute URL or relative path starting with "/".`,
|
|
660
|
-
baseUrl
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
664
|
-
}
|
|
665
|
-
function createExtractableResponse(promise) {
|
|
666
|
-
const extractable = promise;
|
|
667
|
-
extractable.data = async () => (await promise).data;
|
|
668
|
-
return extractable;
|
|
669
|
-
}
|
|
670
|
-
function createSchemaableResponse(url, options, requestHandler, schemaValidator) {
|
|
671
|
-
const basePromise = requestHandler(
|
|
672
|
-
url,
|
|
673
|
-
options
|
|
674
|
-
);
|
|
675
|
-
return Object.assign(basePromise, {
|
|
676
|
-
schema(schema) {
|
|
677
|
-
const validatedPromise = basePromise.then((response) => {
|
|
678
|
-
try {
|
|
679
|
-
const validatedData = schemaValidator.validate(
|
|
680
|
-
schema,
|
|
681
|
-
response.data
|
|
682
|
-
);
|
|
683
|
-
return {
|
|
684
|
-
...response,
|
|
685
|
-
data: validatedData
|
|
686
|
-
};
|
|
687
|
-
} catch (error) {
|
|
688
|
-
throw new SchemaValidationError(
|
|
689
|
-
`Schema validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
690
|
-
schema,
|
|
691
|
-
response.data,
|
|
692
|
-
error instanceof Error ? error : void 0
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
return createExtractableResponse(validatedPromise);
|
|
697
|
-
},
|
|
698
|
-
async data() {
|
|
699
|
-
return (await basePromise).data;
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
function createHttpClient(config = {}) {
|
|
704
|
-
const validatedConfig = {
|
|
705
|
-
...config,
|
|
706
|
-
baseUrl: validateAndNormalizeBaseUrl(config.baseUrl)
|
|
707
|
-
};
|
|
708
|
-
const requestHandler = createHttpRequest(validatedConfig);
|
|
709
|
-
const schemaValidator = config.schemaValidator ?? createSchemaValidator();
|
|
710
|
-
function get(url, ...args) {
|
|
711
|
-
return createSchemaableResponse(
|
|
712
|
-
url,
|
|
713
|
-
{
|
|
714
|
-
...args[0] ?? {},
|
|
715
|
-
method: "GET"
|
|
716
|
-
},
|
|
717
|
-
requestHandler,
|
|
718
|
-
schemaValidator
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
return {
|
|
722
|
-
get,
|
|
723
|
-
post: (url, body, ...args) => createSchemaableResponse(
|
|
724
|
-
url,
|
|
725
|
-
{
|
|
726
|
-
...args[0] ?? {},
|
|
727
|
-
method: "POST",
|
|
728
|
-
body
|
|
729
|
-
},
|
|
730
|
-
requestHandler,
|
|
731
|
-
schemaValidator
|
|
732
|
-
),
|
|
733
|
-
put: (url, body, ...args) => createSchemaableResponse(
|
|
734
|
-
url,
|
|
735
|
-
{
|
|
736
|
-
...args[0] ?? {},
|
|
737
|
-
method: "PUT",
|
|
738
|
-
body
|
|
739
|
-
},
|
|
740
|
-
requestHandler,
|
|
741
|
-
schemaValidator
|
|
742
|
-
),
|
|
743
|
-
patch: (url, body, ...args) => createSchemaableResponse(
|
|
744
|
-
url,
|
|
745
|
-
{
|
|
746
|
-
...args[0] ?? {},
|
|
747
|
-
method: "PATCH",
|
|
748
|
-
body
|
|
749
|
-
},
|
|
750
|
-
requestHandler,
|
|
751
|
-
schemaValidator
|
|
752
|
-
),
|
|
753
|
-
delete: (url, ...args) => createSchemaableResponse(
|
|
754
|
-
url,
|
|
755
|
-
{
|
|
756
|
-
...args[0] ?? {},
|
|
757
|
-
method: "DELETE"
|
|
758
|
-
},
|
|
759
|
-
requestHandler,
|
|
760
|
-
schemaValidator
|
|
761
|
-
),
|
|
762
|
-
/**
|
|
763
|
-
* Generic request method for custom HTTP methods and full control
|
|
764
|
-
*/
|
|
765
|
-
request: (url, options) => createSchemaableResponse(
|
|
766
|
-
url,
|
|
767
|
-
options ?? {},
|
|
768
|
-
requestHandler,
|
|
769
|
-
schemaValidator
|
|
770
|
-
)
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
const http = createHttpClient();
|
|
774
|
-
export {
|
|
775
|
-
AsyncSchemaValidationError,
|
|
776
|
-
HttpError,
|
|
777
|
-
MiddlewareError,
|
|
778
|
-
NetworkError,
|
|
779
|
-
PathParameterError,
|
|
780
|
-
SchemaValidationError,
|
|
781
|
-
SerializationError,
|
|
782
|
-
TimeoutError,
|
|
783
|
-
createHttpClient,
|
|
784
|
-
http as default,
|
|
785
|
-
http
|
|
786
|
-
};
|
|
787
|
-
//# sourceMappingURL=index.es.js.map
|