@scirexs/fetchy 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 scirexs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # fetchy
2
+
3
+ A lightweight, type-safe fetch wrapper with built-in retry logic, timeout handling, and automatic body parsing.
4
+
5
+ ## Features
6
+
7
+ - **Lightweight** - Zero dependencies, works in Deno, Node.js, and browsers
8
+ - **Simple API** - Drop-in replacement for native fetch with enhanced capabilities
9
+ - **Timeout Support** - Configurable request timeouts with automatic cancellation
10
+ - **Smart Retry Logic** - Exponential backoff with Retry-After header support
11
+ - **Type-Safe** - Full TypeScript support with generic type inference
12
+ - **Bearer Token Helper** - Built-in Authorization header management
13
+ - **Jitter Support** - Prevent thundering herd with randomized delays
14
+ - **Auto Body Parsing** - Automatic JSON serialization and Content-Type detection
15
+
16
+ ## Installation
17
+ ```bash
18
+ # npm
19
+ npm install @scirexs/fetchy
20
+
21
+ # JSR (Deno)
22
+ deno add jsr:@scirexs/fetchy
23
+ ```
24
+
25
+ ## Quick Start
26
+ ```ts
27
+ import { fetchy, fetchyb } from "@scirexs/fetchy";
28
+
29
+ // Simple GET request with timeout
30
+ const response = await fetchy("https://api.example.com/data", {
31
+ timeout: 10
32
+ });
33
+
34
+ // Auto-parsed JSON response
35
+ interface User {
36
+ id: number;
37
+ name: string;
38
+ }
39
+
40
+ const user = await fetchyb<User>("https://api.example.com/user/1", "json");
41
+ console.log(user?.name);
42
+ ```
43
+
44
+ ## API Reference
45
+
46
+ ### `fetchy(url, options?)`
47
+
48
+ Performs an HTTP request and returns the raw Response object.
49
+
50
+ #### Parameters
51
+ - `url`: string | URL | Request - The request URL
52
+ - `options`: FetchyOptions (optional) - Configuration options
53
+
54
+ #### Returns
55
+ `Promise<Response | null>`
56
+
57
+ #### Example
58
+ ```ts
59
+ const response = await fetchy("https://api.example.com/data", {
60
+ method: "POST",
61
+ body: { key: "value" },
62
+ timeout: 10,
63
+ retry: { max: 3, interval: 2 },
64
+ bearerToken: "your-token-here"
65
+ });
66
+
67
+ if (response?.ok) {
68
+ const data = await response.json();
69
+ }
70
+ ```
71
+
72
+ ### `fetchyb(url, type?, options?)`
73
+
74
+ Performs an HTTP request and automatically parses the response body.
75
+
76
+ #### Parameters
77
+ - `url`: string | URL | Request - The request URL
78
+ - `type`: "text" | "json" | "bytes" | "auto" (default: "auto") - Response parsing type
79
+ - `options`: FetchyOptions (optional) - Configuration options
80
+
81
+ #### Returns
82
+ `Promise<T | string | Uint8Array | null>`
83
+
84
+ #### Example
85
+ ```ts
86
+ // Automatic type detection from Content-Type header
87
+ const data = await fetchyb("https://api.example.com/data");
88
+
89
+ // Explicit JSON parsing with type assertion
90
+ interface Product {
91
+ id: number;
92
+ name: string;
93
+ price: number;
94
+ }
95
+ const product = await fetchyb<Product>("https://api.example.com/product/1", "json");
96
+
97
+ // Text content
98
+ const html = await fetchyb("https://example.com", "text");
99
+
100
+ // Binary data
101
+ const image = await fetchyb("https://example.com/image.png", "bytes");
102
+ ```
103
+
104
+ ## Configuration Options
105
+
106
+ ### FetchyOptions
107
+ ```ts
108
+ interface FetchyOptions {
109
+ // Standard fetch options (method, headers, etc.)
110
+ method?: string;
111
+ headers?: HeadersInit;
112
+
113
+ // Request body (auto-serializes JSON)
114
+ body?: JSONValue | FormData | URLSearchParams | Blob | ArrayBuffer | string;
115
+
116
+ // Timeout in seconds (default: 15)
117
+ timeout?: number;
118
+
119
+ // Retry configuration
120
+ retry?: {
121
+ max?: number; // Max retry attempts (default: 3)
122
+ interval?: number; // Base interval in seconds (default: 3)
123
+ maxInterval?: number; // Max interval cap (default: 30)
124
+ byHeader?: boolean; // Respect Retry-After header (default: true)
125
+ } | false; // Set to false to disable retry
126
+
127
+ // Bearer token (auto-adds "Bearer " prefix)
128
+ bearerToken?: string;
129
+
130
+ // Error throwing behavior
131
+ throwError?: {
132
+ onError?: boolean; // Throw on native errors (default: true)
133
+ onErrorStatus?: boolean; // Throw on 4xx/5xx status (default: false)
134
+ } | boolean; // Set to true to throw all errors
135
+
136
+ // Initial jitter delay in seconds
137
+ jitter?: number;
138
+
139
+ // Manual abort controller (if timeout occur, reason is set to "timeout")
140
+ abort?: AbortController;
141
+
142
+ // Redirect behavior
143
+ redirect?: "follow" | "error" | "manual";
144
+ }
145
+ ```
146
+
147
+ ### Default Values
148
+ ```ts
149
+ {
150
+ timeout: 15, // 15 seconds
151
+ jitter: 0, // No jitter
152
+ retry: {
153
+ max: 3, // 3 retry attempts
154
+ interval: 3, // 3 seconds base interval
155
+ maxInterval: 30, // 30 seconds max interval
156
+ byHeader: true // Respect Retry-After header
157
+ },
158
+ throwError: {
159
+ onError: true, // Throw parsing errors
160
+ onErrorStatus: false // Don't throw on HTTP errors
161
+ },
162
+ redirect: "follow" // Follow redirects
163
+ }
164
+ ```
165
+
166
+ ## Usage Examples
167
+
168
+ ### Basic Requests
169
+ ```ts
170
+ import { fetchy, fetchyb } from "@scirexs/fetchy";
171
+
172
+ // GET request
173
+ const data = await fetchyb("https://api.example.com/data", "json");
174
+
175
+ // POST with JSON body
176
+ const result = await fetchyb("https://api.example.com/create", "json", {
177
+ body: { name: "John", email: "john@example.com" }
178
+ });
179
+
180
+ // Custom headers
181
+ const response = await fetchy("https://api.example.com/data", {
182
+ headers: {
183
+ "X-Custom-Header": "value"
184
+ }
185
+ });
186
+ ```
187
+
188
+ ### Authentication
189
+ ```ts
190
+ // Bearer token authentication
191
+ const user = await fetchyb<User>("https://api.example.com/me", "json", {
192
+ bearerToken: "your-access-token"
193
+ });
194
+
195
+ // Custom authorization
196
+ const data = await fetchyb("https://api.example.com/data", "json", {
197
+ headers: {
198
+ "Authorization": "Basic " + btoa("user:pass")
199
+ }
200
+ });
201
+ ```
202
+
203
+ ### Timeout and Retry
204
+ ```ts
205
+ // Custom timeout
206
+ const response = await fetchy("https://slow-api.example.com", {
207
+ timeout: 30 // 30 seconds
208
+ });
209
+
210
+ // Retry with exponential backoff
211
+ const data = await fetchyb("https://api.example.com/data", "json", {
212
+ retry: {
213
+ max: 5, // Retry up to 5 times
214
+ interval: 2, // Start with 2 seconds
215
+ maxInterval: 60, // Cap at 60 seconds
216
+ byHeader: true // Respect Retry-After header
217
+ }
218
+ });
219
+
220
+ // Disable retry
221
+ const response = await fetchy("https://api.example.com/data", {
222
+ retry: false
223
+ });
224
+ ```
225
+
226
+ ### Error Handling
227
+ ```ts
228
+ import { fetchy, HTTPStatusError, RedirectError } from "@scirexs/fetchy";
229
+
230
+ // Return null on error (default)
231
+ const data = await fetchyb("https://api.example.com/data", "json");
232
+ if (data === null) {
233
+ console.log("Request failed");
234
+ }
235
+
236
+ // Throw on error
237
+ try {
238
+ const data = await fetchyb("https://api.example.com/data", "json", {
239
+ throwError: true
240
+ });
241
+ } catch (error) {
242
+ console.error("Request failed:", error);
243
+ }
244
+
245
+ // Throw only on HTTP errors
246
+ try {
247
+ const data = await fetchyb("https://api.example.com/data", "json", {
248
+ throwError: { onErrorStatus: true }
249
+ });
250
+ } catch (error) {
251
+ if (error instanceof HTTPStatusError) {
252
+ console.error("HTTP error:", error.message); // e.g., "404 Not Found"
253
+ }
254
+ }
255
+
256
+ // Handle redirects
257
+ try {
258
+ const response = await fetchy("https://example.com/redirect", {
259
+ redirect: "error"
260
+ });
261
+ } catch (error) {
262
+ if (error instanceof RedirectError) {
263
+ console.error("Unexpected redirect:", error.message);
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Advanced Usage
269
+ ```ts
270
+ // Jitter to prevent thundering herd
271
+ const response = await fetchy("https://api.example.com/data", {
272
+ jitter: 2, // Random delay up to 2 seconds before request
273
+ retry: { max: 3 }
274
+ });
275
+
276
+ // Manual abort control
277
+ const controller = new AbortController();
278
+
279
+ setTimeout(() => controller.abort(), 5000); // Abort after 5 seconds
280
+
281
+ const response = await fetchy("https://api.example.com/data", {
282
+ abort: controller
283
+ });
284
+
285
+ // Form data
286
+ const formData = new FormData();
287
+ formData.append("file", blob);
288
+ formData.append("name", "example");
289
+
290
+ const response = await fetchy("https://api.example.com/upload", {
291
+ body: formData
292
+ });
293
+
294
+ // URL-encoded form
295
+ const params = new URLSearchParams();
296
+ params.append("key", "value");
297
+
298
+ const response = await fetchy("https://api.example.com/form", {
299
+ body: params
300
+ });
301
+ ```
302
+
303
+ ### Type-Safe API Responses
304
+ ```ts
305
+ interface ApiResponse<T> {
306
+ success: boolean;
307
+ data: T;
308
+ error?: string;
309
+ }
310
+
311
+ interface Todo {
312
+ id: number;
313
+ title: string;
314
+ completed: boolean;
315
+ }
316
+
317
+ const response = await fetchyb<ApiResponse<Todo>>(
318
+ "https://api.example.com/todos/1",
319
+ "json"
320
+ );
321
+
322
+ if (response?.success) {
323
+ console.log(response.data.title); // Fully typed
324
+ }
325
+ ```
326
+
327
+ ## License
328
+
329
+ MIT
package/esm/main.js ADDED
@@ -0,0 +1,441 @@
1
+ export { _correctNumber, _fetchWithJitter, _fetchWithRetry, _fetchWithTimeout, _getBody, _getContentType, _getHeaders, _getNextInterval, _getOptions, _getRequestInit, _getRetryOption, _handleRedirectResponse, _isBool, _isJSONObject, _isNumber, _isPlainObject, _isString, _parseRetryAfter, _shouldNotRetry, _shouldRedirect, _throwError, _wait, DEFAULT, fetchy, fetchyb, HTTPStatusError, RedirectError, };
2
+ /*=============== Constant Values ===============*/
3
+ /**
4
+ * Default configuration values for fetchy.
5
+ * These values are used when corresponding options are not specified.
6
+ */
7
+ const DEFAULT = {
8
+ timeout: 15,
9
+ jitter: 0,
10
+ interval: 3,
11
+ maxInterval: 30,
12
+ max: 3,
13
+ byHeader: true,
14
+ onError: true,
15
+ onErrorStatus: false,
16
+ userRedirect: "follow",
17
+ };
18
+ /*=============== Main Code =====================*/
19
+ /**
20
+ * Error thrown when HTTP response has a non-OK status code (4xx, 5xx).
21
+ * Only thrown when throwError.onErrorStatus is set to true.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * try {
26
+ * await fetchy("https://api.example.com/data", {
27
+ * throwError: { onErrorStatus: true }
28
+ * });
29
+ * } catch (error) {
30
+ * if (error instanceof HTTPStatusError) {
31
+ * console.error("HTTP error:", error.message); // e.g., "404 Not Found"
32
+ * }
33
+ * }
34
+ * ```
35
+ */
36
+ class HTTPStatusError extends Error {
37
+ constructor(msg) {
38
+ super(msg);
39
+ this.name = "HTTPStatusError";
40
+ }
41
+ }
42
+ /**
43
+ * Error thrown when a redirect response is received and redirect option is set to "error".
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * try {
48
+ * await fetchy("https://example.com/redirect", {
49
+ * redirect: "error"
50
+ * });
51
+ * } catch (error) {
52
+ * if (error instanceof RedirectError) {
53
+ * console.error("Unexpected redirect:", error.message);
54
+ * }
55
+ * }
56
+ * ```
57
+ */
58
+ class RedirectError extends Error {
59
+ constructor(msg) {
60
+ super(msg);
61
+ this.name = "RedirectError";
62
+ }
63
+ }
64
+ async function fetchyb(url, type = "auto", options) {
65
+ const resp = await fetchy(url, options);
66
+ if (!resp || !resp.ok)
67
+ return null;
68
+ const btype = resp.headers.get("Content-Type") ?? "";
69
+ try {
70
+ if (type === "text" || (type === "auto" && btype.startsWith("text/")))
71
+ return await resp.text();
72
+ if (type === "json" || (type === "auto" && btype === "application/json"))
73
+ return await resp.json();
74
+ return await resp.bytes();
75
+ }
76
+ catch (e) {
77
+ if (_throwError("onError", options?.throwError))
78
+ throw e;
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Performs an HTTP request with enhanced features like timeout, retry, and automatic header management.
84
+ * Returns the raw Response object or null on failure, unless throwError is configured.
85
+ *
86
+ * @param url - The URL to fetch. Can be a string, URL object, or Request object.
87
+ * @param options - Configuration options for the request.
88
+ * @returns Response object or null on failure.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * import { fetchy } from "@scirexs/fetchy";
93
+ *
94
+ * // Simple GET request
95
+ * const response = await fetchy("https://api.example.com/data");
96
+ * if (response?.ok) {
97
+ * const data = await response.json();
98
+ * }
99
+ *
100
+ * // POST request with JSON body
101
+ * const response = await fetchy("https://api.example.com/create", {
102
+ * body: { name: "John", age: 30 },
103
+ * bearerToken: "your-token"
104
+ * });
105
+ *
106
+ * // With retry and timeout
107
+ * const response = await fetchy("https://api.example.com/data", {
108
+ * timeout: 10,
109
+ * retry: { max: 5, interval: 2 },
110
+ * throwError: { onErrorStatus: true }
111
+ * });
112
+ * ```
113
+ */
114
+ async function fetchy(url, options) {
115
+ try {
116
+ const opts = _getOptions(options);
117
+ const init = _getRequestInit(options, opts.abort);
118
+ const resp = await _fetchWithRetry(url, init, opts);
119
+ if (!resp.ok && opts.onErrorStatus)
120
+ throw new HTTPStatusError(`${resp.status} ${resp.statusText}`.trim());
121
+ return resp;
122
+ }
123
+ catch (e) {
124
+ if (_throwError("onError", options?.throwError))
125
+ throw e;
126
+ return null;
127
+ }
128
+ }
129
+ /*=============== Helper Code ===================*/
130
+ /**
131
+ * Checks if a value is a string.
132
+ * @internal
133
+ * @param v - Value to check.
134
+ * @returns True if the value is a string.
135
+ */
136
+ function _isString(v) {
137
+ return typeof v === "string";
138
+ }
139
+ /**
140
+ * Checks if a value is a number.
141
+ * @internal
142
+ * @param v - Value to check.
143
+ * @returns True if the value is a number.
144
+ */
145
+ function _isNumber(v) {
146
+ return typeof v === "number";
147
+ }
148
+ /**
149
+ * Checks if a value is a boolean.
150
+ * @internal
151
+ * @param v - Value to check.
152
+ * @returns True if the value is a boolean.
153
+ */
154
+ function _isBool(v) {
155
+ return typeof v === "boolean";
156
+ }
157
+ /**
158
+ * Checks if a value is a plain object (not array, null, or other object types).
159
+ * @internal
160
+ * @param v - Value to check.
161
+ * @returns True if the value is a plain object.
162
+ */
163
+ function _isPlainObject(v) {
164
+ return Boolean(v &&
165
+ typeof v === "object" &&
166
+ Object.prototype.toString.call(v).slice(8, -1) === "Object" &&
167
+ v.constructor === Object);
168
+ }
169
+ /**
170
+ * Determines whether to throw an error based on configuration.
171
+ * @internal
172
+ * @param prop - The error option property to check.
173
+ * @param options - Error configuration or boolean flag.
174
+ * @returns True if error should be thrown.
175
+ */
176
+ function _throwError(prop, options) {
177
+ return Boolean((options === void 0 && DEFAULT[prop]) ||
178
+ (typeof options === "boolean" && options) ||
179
+ (typeof options === "object" && (options[prop] ?? DEFAULT[prop])));
180
+ }
181
+ /**
182
+ * Corrects a number to be non-negative, using default if invalid.
183
+ * @internal
184
+ * @param dflt - Default value to use if number is invalid.
185
+ * @param num - Number to validate.
186
+ * @param integer - Whether to truncate to integer.
187
+ * @returns Corrected number.
188
+ */
189
+ function _correctNumber(dflt, num, integer = false) {
190
+ if (num === void 0 || num < 0)
191
+ return dflt;
192
+ return integer ? Math.trunc(num) : num;
193
+ }
194
+ function _getRetryOption(prop, off, options) {
195
+ if (_isBool(options))
196
+ return off;
197
+ if (options === void 0 || options[prop] === void 0)
198
+ return DEFAULT[prop];
199
+ if (_isNumber(options[prop]))
200
+ return _correctNumber(DEFAULT[prop], options[prop], prop === "max");
201
+ return options[prop];
202
+ }
203
+ /**
204
+ * Converts FetchyOptions to internal Options format with validated values.
205
+ * @internal
206
+ * @param options - User-provided options.
207
+ * @returns Normalized internal options.
208
+ */
209
+ function _getOptions(options) {
210
+ const timeout = _correctNumber(DEFAULT.timeout, options?.timeout);
211
+ return {
212
+ timeout,
213
+ jitter: _correctNumber(DEFAULT.jitter, options?.jitter),
214
+ interval: _getRetryOption("interval", 0, options?.retry),
215
+ maxInterval: _getRetryOption("maxInterval", 0, options?.retry),
216
+ max: _getRetryOption("max", 0, options?.retry),
217
+ byHeader: _getRetryOption("byHeader", false, options?.retry),
218
+ onErrorStatus: _throwError("onErrorStatus", options?.throwError),
219
+ abort: options?.abort ?? (timeout ? new AbortController() : undefined),
220
+ userRedirect: options?.redirect ?? DEFAULT.userRedirect,
221
+ };
222
+ }
223
+ /**
224
+ * Converts FetchyOptions to standard RequestInit format.
225
+ * @internal
226
+ * @param options - User-provided options.
227
+ * @param optsAbort - AbortController for timeout handling.
228
+ * @returns Standard RequestInit object.
229
+ */
230
+ function _getRequestInit(options, optsAbort) {
231
+ const { body, timeout, retry, bearerToken, throwError, jitter, abort, redirect, ...rest } = options ?? {};
232
+ return {
233
+ method: body === void 0 ? "GET" : "POST",
234
+ headers: _getHeaders(options),
235
+ ...(redirect && { redirect: redirect === "error" ? "manual" : redirect }),
236
+ ...(optsAbort && { signal: optsAbort.signal }),
237
+ ...(body && { body: _getBody(body) }),
238
+ ...rest,
239
+ };
240
+ }
241
+ /**
242
+ * Converts FetchyBody to standard BodyInit format.
243
+ * @internal
244
+ * @param body - Body content to convert.
245
+ * @returns Standard BodyInit or undefined.
246
+ */
247
+ function _getBody(body) {
248
+ return _isJSONObject(body) ? JSON.stringify(body) : body;
249
+ }
250
+ /**
251
+ * Checks if a value should be treated as JSON object for serialization.
252
+ * @internal
253
+ * @param arg - Value to check.
254
+ * @returns True if value should be JSON stringified.
255
+ */
256
+ function _isJSONObject(arg) {
257
+ return Boolean(arg === null || _isNumber(arg) || _isBool(arg) || Array.isArray(arg) || _isPlainObject(arg));
258
+ }
259
+ /**
260
+ * Constructs request headers with automatic Content-Type and Authorization.
261
+ * @internal
262
+ * @param options - User-provided options.
263
+ * @returns Headers object.
264
+ */
265
+ function _getHeaders(options) {
266
+ const type = _getContentType(options?.body);
267
+ return {
268
+ "Accept": "application/json, text/plain",
269
+ ...(type && { "Content-Type": type }),
270
+ ...(options?.bearerToken && { "Authorization": `Bearer ${options.bearerToken}` }),
271
+ ...options?.headers,
272
+ };
273
+ }
274
+ /**
275
+ * Determines Content-Type header based on body type.
276
+ * @internal
277
+ * @param body - Request body content.
278
+ * @returns Content-Type string or undefined.
279
+ */
280
+ function _getContentType(body) {
281
+ if (body === void 0 || body instanceof FormData)
282
+ return;
283
+ if (_isString(body))
284
+ return "text/plain";
285
+ if (body instanceof URLSearchParams)
286
+ return "application/x-www-form-urlencoded";
287
+ if (_isJSONObject(body))
288
+ return "application/json";
289
+ return "application/octet-stream";
290
+ }
291
+ /**
292
+ * Waits for specified seconds with optional randomization.
293
+ * @internal
294
+ * @param sec - Seconds to wait.
295
+ * @param random - Whether to randomize the delay.
296
+ */
297
+ async function _wait(sec, random = true) {
298
+ if (sec <= 0)
299
+ return;
300
+ const delay = Math.trunc((random ? Math.random() : 1) * sec * 1000);
301
+ await new Promise((resolve) => setTimeout(resolve, delay));
302
+ }
303
+ /**
304
+ * Checks if response is a redirect (3xx status).
305
+ * @internal
306
+ * @param resp - Response to check.
307
+ * @returns True if response is a redirect.
308
+ */
309
+ function _shouldRedirect(resp) {
310
+ return resp.status < 400 && resp.status >= 300;
311
+ }
312
+ /**
313
+ * Determines if retry should stop based on conditions and waits if continuing.
314
+ * @internal
315
+ * @param count - Current retry attempt number.
316
+ * @param opts - Internal options.
317
+ * @param resp - Response from previous attempt.
318
+ * @returns True if retry should stop.
319
+ */
320
+ async function _shouldNotRetry(count, opts, resp) {
321
+ if (count >= opts.max || resp?.ok)
322
+ return true;
323
+ if (resp && _shouldRedirect(resp)) {
324
+ if (opts.userRedirect === "manual")
325
+ return true;
326
+ if (opts.userRedirect === "error") {
327
+ opts.max = 0;
328
+ throw new RedirectError(`Received redirect response: ${resp.status}`);
329
+ }
330
+ }
331
+ const interval = _getNextInterval(count, opts, resp);
332
+ if (interval > opts.maxInterval)
333
+ return true;
334
+ await _wait(interval, false);
335
+ return false;
336
+ }
337
+ /**
338
+ * Calculates next retry interval using exponential backoff or Retry-After header.
339
+ * @internal
340
+ * @param count - Current retry attempt number.
341
+ * @param opts - Internal options.
342
+ * @param resp - Response from previous attempt.
343
+ * @returns Next retry interval in seconds.
344
+ */
345
+ function _getNextInterval(count, opts, resp) {
346
+ return opts.byHeader && resp
347
+ ? Math.max(_parseRetryAfter(resp.headers.get("Retry-After")?.trim() ?? ""), opts.interval)
348
+ : Math.min(Math.pow(Math.max(1, opts.interval), count), opts.maxInterval);
349
+ }
350
+ /**
351
+ * Parses Retry-After header value to seconds.
352
+ * @internal
353
+ * @param value - Retry-After header value (seconds or HTTP date).
354
+ * @returns Retry delay in seconds, or Infinity if invalid.
355
+ */
356
+ function _parseRetryAfter(value) {
357
+ if (!value)
358
+ return Infinity;
359
+ const sec1 = Number.parseInt(value, 10);
360
+ if (!Number.isNaN(sec1))
361
+ return sec1;
362
+ const sec2 = Math.ceil((new Date(value).getTime() - Date.now()) / 1000);
363
+ if (!Number.isNaN(sec2))
364
+ return sec2;
365
+ return Infinity;
366
+ }
367
+ /**
368
+ * Updates URL and method for redirect responses.
369
+ * @internal
370
+ * @param url - Original request URL.
371
+ * @param init - Request initialization object.
372
+ * @param resp - Redirect response.
373
+ * @returns Updated URL for next request.
374
+ */
375
+ function _handleRedirectResponse(url, init, resp) {
376
+ if (!resp.redirected)
377
+ return url;
378
+ if (resp.status === 303)
379
+ init.method = "GET";
380
+ return url instanceof Request ? new Request(resp.url, url) : resp.url;
381
+ }
382
+ /**
383
+ * Executes fetch with retry logic and exponential backoff.
384
+ * @internal
385
+ * @param url - Request URL.
386
+ * @param init - Request initialization object.
387
+ * @param opts - Internal options.
388
+ * @returns Response from successful request.
389
+ */
390
+ async function _fetchWithRetry(url, init, opts) {
391
+ if (!opts.max)
392
+ return await _fetchWithTimeout(url, init, opts);
393
+ for (let i = 1; i <= opts.max; i++) {
394
+ try {
395
+ const resp = await (opts.jitter ? _fetchWithJitter(url, init, opts) : _fetchWithTimeout(url, init, opts));
396
+ if (await _shouldNotRetry(i, opts, resp))
397
+ return resp;
398
+ url = _handleRedirectResponse(url, init, resp);
399
+ continue;
400
+ }
401
+ catch (e) {
402
+ if (await _shouldNotRetry(i, opts))
403
+ throw e;
404
+ continue;
405
+ }
406
+ }
407
+ throw new Error("never reach");
408
+ }
409
+ /**
410
+ * Executes fetch with initial jitter delay.
411
+ * @internal
412
+ * @param url - Request URL.
413
+ * @param init - Request initialization object.
414
+ * @param opts - Internal options.
415
+ * @returns Response from request.
416
+ */
417
+ async function _fetchWithJitter(url, init, opts) {
418
+ await _wait(opts.jitter);
419
+ return await _fetchWithTimeout(url, init, opts);
420
+ }
421
+ /**
422
+ * Executes fetch with timeout handling.
423
+ * @internal
424
+ * @param url - Request URL.
425
+ * @param init - Request initialization object.
426
+ * @param opts - Internal options.
427
+ * @returns Response from request.
428
+ */
429
+ async function _fetchWithTimeout(url, init, opts) {
430
+ const req = url instanceof Request ? url.clone() : url;
431
+ const id = opts.abort ? setTimeout(() => opts.abort?.abort("timeout"), opts.timeout * 1000) : 0;
432
+ try {
433
+ return await fetch(req, init);
434
+ }
435
+ catch (e) {
436
+ throw e;
437
+ }
438
+ finally {
439
+ clearTimeout(id);
440
+ }
441
+ }
package/esm/mod.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Exports main functions and types for external.
3
+ * @module
4
+ */
5
+ export { fetchy, fetchyb, HTTPStatusError, RedirectError } from "./main.js";
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/esm/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@scirexs/fetchy",
3
+ "version": "0.1.0",
4
+ "description": "A light fetch wrapper.",
5
+ "keywords": [
6
+ "fetch",
7
+ "typescript"
8
+ ],
9
+ "author": "scirexs",
10
+ "homepage": "https://github.com/scirexs/fetchy#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/scirexs/fetchy.git"
14
+ },
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/scirexs/fetchy/issues"
18
+ },
19
+ "module": "./esm/mod.js",
20
+ "types": "./types/mod.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./types/mod.d.ts",
25
+ "default": "./esm/mod.js"
26
+ }
27
+ }
28
+ },
29
+ "scripts": {},
30
+ "type": "module",
31
+ "sideEffects": false,
32
+ "_generatedBy": "dnt@dev"
33
+ }
@@ -0,0 +1,305 @@
1
+ export { _correctNumber, _fetchWithJitter, _fetchWithRetry, _fetchWithTimeout, _getBody, _getContentType, _getHeaders, _getNextInterval, _getOptions, _getRequestInit, _getRetryOption, _handleRedirectResponse, _isBool, _isJSONObject, _isNumber, _isPlainObject, _isString, _parseRetryAfter, _shouldNotRetry, _shouldRedirect, _throwError, _wait, DEFAULT, fetchy, fetchyb, HTTPStatusError, RedirectError, };
2
+ import type { ErrorOptions, FetchyBody, FetchyOptions, RetryOptions } from "./types.js";
3
+ /**
4
+ * Default configuration values for fetchy.
5
+ * These values are used when corresponding options are not specified.
6
+ */
7
+ declare const DEFAULT: Options;
8
+ /**
9
+ * Valid input types for fetch requests.
10
+ * @internal
11
+ */
12
+ type Input = string | URL | Request;
13
+ /**
14
+ * Internal normalized options used throughout the fetch process.
15
+ * @internal
16
+ */
17
+ interface Options {
18
+ timeout: number;
19
+ jitter: number;
20
+ interval: number;
21
+ maxInterval: number;
22
+ max: number;
23
+ byHeader: boolean;
24
+ onError?: boolean;
25
+ onErrorStatus: boolean;
26
+ abort?: AbortController;
27
+ userRedirect: "follow" | "error" | "manual";
28
+ }
29
+ /**
30
+ * Error thrown when HTTP response has a non-OK status code (4xx, 5xx).
31
+ * Only thrown when throwError.onErrorStatus is set to true.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * try {
36
+ * await fetchy("https://api.example.com/data", {
37
+ * throwError: { onErrorStatus: true }
38
+ * });
39
+ * } catch (error) {
40
+ * if (error instanceof HTTPStatusError) {
41
+ * console.error("HTTP error:", error.message); // e.g., "404 Not Found"
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ declare class HTTPStatusError extends Error {
47
+ constructor(msg: string);
48
+ }
49
+ /**
50
+ * Error thrown when a redirect response is received and redirect option is set to "error".
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * try {
55
+ * await fetchy("https://example.com/redirect", {
56
+ * redirect: "error"
57
+ * });
58
+ * } catch (error) {
59
+ * if (error instanceof RedirectError) {
60
+ * console.error("Unexpected redirect:", error.message);
61
+ * }
62
+ * }
63
+ * ```
64
+ */
65
+ declare class RedirectError extends Error {
66
+ constructor(msg: string);
67
+ }
68
+ /**
69
+ * Performs an HTTP request and automatically parses the response body based on Content-Type or specified type.
70
+ * Returns null if the request fails or response is not OK, unless throwError is configured.
71
+ *
72
+ * @param url - The URL to fetch. Can be a string, URL object, or Request object.
73
+ * @param type - The expected response type. "auto" detects type from Content-Type header.
74
+ * @param options - Configuration options for the request.
75
+ * @returns Parsed response body or null on failure.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { fetchyb } from "@scirexs/fetchy";
80
+ *
81
+ * // Automatic type detection
82
+ * const data = await fetchyb("https://api.example.com/user");
83
+ *
84
+ * // Explicit JSON parsing with type assertion
85
+ * interface User { id: number; name: string; }
86
+ * const user = await fetchyb<User>("https://api.example.com/user", "json");
87
+ *
88
+ * // Text response
89
+ * const text = await fetchyb("https://example.com/page", "text");
90
+ *
91
+ * // Binary data
92
+ * const bytes = await fetchyb("https://example.com/image.png", "bytes");
93
+ * ```
94
+ */
95
+ declare function fetchyb(url: Input, type: "text", options?: FetchyOptions): Promise<string | null>;
96
+ declare function fetchyb<T>(url: Input, type: "json", options?: FetchyOptions): Promise<T | null>;
97
+ declare function fetchyb(url: Input, type: "bytes", options?: FetchyOptions): Promise<Uint8Array | null>;
98
+ declare function fetchyb<T>(url: Input, type?: "auto", options?: FetchyOptions): Promise<T | string | Uint8Array | null>;
99
+ /**
100
+ * Performs an HTTP request with enhanced features like timeout, retry, and automatic header management.
101
+ * Returns the raw Response object or null on failure, unless throwError is configured.
102
+ *
103
+ * @param url - The URL to fetch. Can be a string, URL object, or Request object.
104
+ * @param options - Configuration options for the request.
105
+ * @returns Response object or null on failure.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { fetchy } from "@scirexs/fetchy";
110
+ *
111
+ * // Simple GET request
112
+ * const response = await fetchy("https://api.example.com/data");
113
+ * if (response?.ok) {
114
+ * const data = await response.json();
115
+ * }
116
+ *
117
+ * // POST request with JSON body
118
+ * const response = await fetchy("https://api.example.com/create", {
119
+ * body: { name: "John", age: 30 },
120
+ * bearerToken: "your-token"
121
+ * });
122
+ *
123
+ * // With retry and timeout
124
+ * const response = await fetchy("https://api.example.com/data", {
125
+ * timeout: 10,
126
+ * retry: { max: 5, interval: 2 },
127
+ * throwError: { onErrorStatus: true }
128
+ * });
129
+ * ```
130
+ */
131
+ declare function fetchy(url: Input, options?: FetchyOptions): Promise<Response | null>;
132
+ /**
133
+ * Checks if a value is a string.
134
+ * @internal
135
+ * @param v - Value to check.
136
+ * @returns True if the value is a string.
137
+ */
138
+ declare function _isString(v: unknown): v is string;
139
+ /**
140
+ * Checks if a value is a number.
141
+ * @internal
142
+ * @param v - Value to check.
143
+ * @returns True if the value is a number.
144
+ */
145
+ declare function _isNumber(v: unknown): v is number;
146
+ /**
147
+ * Checks if a value is a boolean.
148
+ * @internal
149
+ * @param v - Value to check.
150
+ * @returns True if the value is a boolean.
151
+ */
152
+ declare function _isBool(v: unknown): v is boolean;
153
+ /**
154
+ * Checks if a value is a plain object (not array, null, or other object types).
155
+ * @internal
156
+ * @param v - Value to check.
157
+ * @returns True if the value is a plain object.
158
+ */
159
+ declare function _isPlainObject(v: unknown): v is object;
160
+ /**
161
+ * Determines whether to throw an error based on configuration.
162
+ * @internal
163
+ * @param prop - The error option property to check.
164
+ * @param options - Error configuration or boolean flag.
165
+ * @returns True if error should be thrown.
166
+ */
167
+ declare function _throwError(prop: keyof ErrorOptions, options?: ErrorOptions | boolean): boolean;
168
+ /**
169
+ * Corrects a number to be non-negative, using default if invalid.
170
+ * @internal
171
+ * @param dflt - Default value to use if number is invalid.
172
+ * @param num - Number to validate.
173
+ * @param integer - Whether to truncate to integer.
174
+ * @returns Corrected number.
175
+ */
176
+ declare function _correctNumber(dflt: number, num?: number, integer?: boolean): number;
177
+ /**
178
+ * Gets retry option value from configuration with fallback to default.
179
+ * @internal
180
+ * @param prop - The retry option property to get.
181
+ * @param off - Fallback value when retry is disabled.
182
+ * @param options - Retry configuration.
183
+ * @returns The retry option value.
184
+ */
185
+ declare function _getRetryOption(prop: keyof RetryOptions, off: number, options?: RetryOptions | false): number;
186
+ declare function _getRetryOption(prop: keyof RetryOptions, off: boolean, options?: RetryOptions | false): boolean;
187
+ /**
188
+ * Converts FetchyOptions to internal Options format with validated values.
189
+ * @internal
190
+ * @param options - User-provided options.
191
+ * @returns Normalized internal options.
192
+ */
193
+ declare function _getOptions(options?: FetchyOptions): Options;
194
+ /**
195
+ * Converts FetchyOptions to standard RequestInit format.
196
+ * @internal
197
+ * @param options - User-provided options.
198
+ * @param optsAbort - AbortController for timeout handling.
199
+ * @returns Standard RequestInit object.
200
+ */
201
+ declare function _getRequestInit(options?: FetchyOptions, optsAbort?: AbortController): RequestInit;
202
+ /**
203
+ * Converts FetchyBody to standard BodyInit format.
204
+ * @internal
205
+ * @param body - Body content to convert.
206
+ * @returns Standard BodyInit or undefined.
207
+ */
208
+ declare function _getBody(body: FetchyBody): BodyInit | undefined;
209
+ /**
210
+ * Checks if a value should be treated as JSON object for serialization.
211
+ * @internal
212
+ * @param arg - Value to check.
213
+ * @returns True if value should be JSON stringified.
214
+ */
215
+ declare function _isJSONObject(arg?: FetchyBody): boolean;
216
+ /**
217
+ * Constructs request headers with automatic Content-Type and Authorization.
218
+ * @internal
219
+ * @param options - User-provided options.
220
+ * @returns Headers object.
221
+ */
222
+ declare function _getHeaders(options?: FetchyOptions): HeadersInit;
223
+ /**
224
+ * Determines Content-Type header based on body type.
225
+ * @internal
226
+ * @param body - Request body content.
227
+ * @returns Content-Type string or undefined.
228
+ */
229
+ declare function _getContentType(body?: FetchyBody): string | undefined;
230
+ /**
231
+ * Waits for specified seconds with optional randomization.
232
+ * @internal
233
+ * @param sec - Seconds to wait.
234
+ * @param random - Whether to randomize the delay.
235
+ */
236
+ declare function _wait(sec: number, random?: boolean): Promise<void>;
237
+ /**
238
+ * Checks if response is a redirect (3xx status).
239
+ * @internal
240
+ * @param resp - Response to check.
241
+ * @returns True if response is a redirect.
242
+ */
243
+ declare function _shouldRedirect(resp: Response): boolean;
244
+ /**
245
+ * Determines if retry should stop based on conditions and waits if continuing.
246
+ * @internal
247
+ * @param count - Current retry attempt number.
248
+ * @param opts - Internal options.
249
+ * @param resp - Response from previous attempt.
250
+ * @returns True if retry should stop.
251
+ */
252
+ declare function _shouldNotRetry(count: number, opts: Options, resp?: Response): Promise<boolean>;
253
+ /**
254
+ * Calculates next retry interval using exponential backoff or Retry-After header.
255
+ * @internal
256
+ * @param count - Current retry attempt number.
257
+ * @param opts - Internal options.
258
+ * @param resp - Response from previous attempt.
259
+ * @returns Next retry interval in seconds.
260
+ */
261
+ declare function _getNextInterval(count: number, opts: Options, resp?: Response): number;
262
+ /**
263
+ * Parses Retry-After header value to seconds.
264
+ * @internal
265
+ * @param value - Retry-After header value (seconds or HTTP date).
266
+ * @returns Retry delay in seconds, or Infinity if invalid.
267
+ */
268
+ declare function _parseRetryAfter(value: string): number;
269
+ /**
270
+ * Updates URL and method for redirect responses.
271
+ * @internal
272
+ * @param url - Original request URL.
273
+ * @param init - Request initialization object.
274
+ * @param resp - Redirect response.
275
+ * @returns Updated URL for next request.
276
+ */
277
+ declare function _handleRedirectResponse(url: Input, init: RequestInit, resp: Response): Input;
278
+ /**
279
+ * Executes fetch with retry logic and exponential backoff.
280
+ * @internal
281
+ * @param url - Request URL.
282
+ * @param init - Request initialization object.
283
+ * @param opts - Internal options.
284
+ * @returns Response from successful request.
285
+ */
286
+ declare function _fetchWithRetry(url: Input, init: RequestInit, opts: Options): Promise<Response>;
287
+ /**
288
+ * Executes fetch with initial jitter delay.
289
+ * @internal
290
+ * @param url - Request URL.
291
+ * @param init - Request initialization object.
292
+ * @param opts - Internal options.
293
+ * @returns Response from request.
294
+ */
295
+ declare function _fetchWithJitter(url: Input, init: RequestInit, opts: Options): Promise<Response>;
296
+ /**
297
+ * Executes fetch with timeout handling.
298
+ * @internal
299
+ * @param url - Request URL.
300
+ * @param init - Request initialization object.
301
+ * @param opts - Internal options.
302
+ * @returns Response from request.
303
+ */
304
+ declare function _fetchWithTimeout(url: Input, init: RequestInit, opts: Options): Promise<Response>;
305
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
package/types/mod.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Exports main functions and types for external.
3
+ * @module
4
+ */
5
+ export { fetchy, fetchyb, HTTPStatusError, RedirectError } from "./main.js";
6
+ export type { ErrorOptions, FetchyBody, FetchyOptions, JSONValue, RetryOptions } from "./types.js";
7
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Represents a JSON-compatible value that can be serialized and deserialized.
3
+ * This type includes primitives, arrays, and plain objects with string keys.
4
+ */
5
+ export type JSONValue = string | number | boolean | null | JSONValue[] | {
6
+ [key: string]: JSONValue;
7
+ };
8
+ /**
9
+ * Represents the body content that can be sent in a fetch request.
10
+ * Includes JSON-compatible values and standard BodyInit types except ReadableStream.
11
+ */
12
+ export type FetchyBody = JSONValue | Exclude<BodyInit, ReadableStream>;
13
+ /**
14
+ * Configuration options for fetchy requests.
15
+ * Extends standard RequestInit but provides additional features like timeout, retry, and error handling.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { fetchy } from "@scirexs/fetchy";
20
+ *
21
+ * const response = await fetchy("https://api.example.com/data", {
22
+ * method: "POST",
23
+ * body: { key: "value" },
24
+ * timeout: 10,
25
+ * retry: { max: 3, interval: 2 },
26
+ * bearerToken: "your-token-here"
27
+ * });
28
+ * ```
29
+ */
30
+ export interface FetchyOptions extends Omit<RequestInit, "body" | "signal"> {
31
+ /** Request body content. Automatically serializes JSON objects. */
32
+ body?: FetchyBody;
33
+ /** Request timeout in seconds. Default is 15 seconds. */
34
+ timeout?: number;
35
+ /** Retry configuration. Set to false to disable retry functionality. */
36
+ retry?: RetryOptions | false;
37
+ /** Bearer token for Authorization header. Automatically adds "Bearer " prefix. */
38
+ bearerToken?: string;
39
+ /** Error throwing behavior configuration. Set to true to throw all errors. */
40
+ throwError?: ErrorOptions | boolean;
41
+ /** Initial jitter delay in seconds before sending the request. Adds randomness to prevent thundering herd. */
42
+ jitter?: number;
43
+ /** AbortController for manual request cancellation. If not provided, an internal controller is created for timeout. */
44
+ abort?: AbortController;
45
+ }
46
+ /**
47
+ * Configuration options for retry behavior.
48
+ * Supports exponential backoff and respects Retry-After headers.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const retryOptions: RetryOptions = {
53
+ * interval: 3,
54
+ * maxInterval: 30,
55
+ * max: 5,
56
+ * byHeader: true
57
+ * };
58
+ * ```
59
+ */
60
+ export interface RetryOptions {
61
+ /** Base interval in seconds between retries. Used for exponential backoff calculation. Default is 3 seconds. */
62
+ interval?: number;
63
+ /** Maximum interval in seconds between retries. Caps the exponential backoff. Default is 30 seconds. */
64
+ maxInterval?: number;
65
+ /** Maximum number of retry attempts. Default is 3. */
66
+ max?: number;
67
+ /** Whether to respect Retry-After header from response. Default is true. */
68
+ byHeader?: boolean;
69
+ }
70
+ /**
71
+ * Configuration options for error throwing behavior.
72
+ * Allows fine-grained control over which errors should be thrown vs. returned as null.
73
+ */
74
+ export interface ErrorOptions {
75
+ /** Whether to throw errors during response parsing (e.g., JSON parsing errors). Default is true. */
76
+ onError?: boolean;
77
+ /** Whether to throw HTTPStatusError for non-OK status codes (4xx, 5xx). Default is false. */
78
+ onErrorStatus?: boolean;
79
+ }
80
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtG;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAEvE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzE,mEAAmE;IACnE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC7B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC;IACpC,8GAA8G;IAC9G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uHAAuH;IACvH,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,gHAAgH;IAChH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB"}