diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index bf37343c..b5e2195d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- node: ["18.x", "20.x", "22.x"]
+ node: ['18.x', '20.x', '22.x', '24.x']
os: [ubuntu-latest, macOS-latest]
steps:
diff --git a/.gitignore b/.gitignore
index 8ab705bc..7f262210 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,5 +110,6 @@ package/
dist/*.ts
dist/**/*.ts
+dist/**/*.mts
!dist/*.map
!dist/**/*.map
diff --git a/README.md b/README.md
index beaba63a..a5783659 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,14 @@
-
Fast, lightweight (~4 KB gzipped) and reusable data fetching
+
Fast, lightweight (~5 KB gzipped) and reusable data fetching
-
The last fetch wrapper you will ever need.
"fetchff" stands for "fetch fast & flexibly"
[npm-url]: https://npmjs.org/package/fetchff
[npm-image]: https://img.shields.io/npm/v/fetchff.svg
-[![NPM version][npm-image]][npm-url] [](https://github.com/MattCCC/fetchff) [](https://github.com/MattCCC/fetchff) [](http://npm-stat.com/charts.html?package=fetchff) [](https://bundlephobia.com/result?p=fetchff) [](https://security.snyk.io/package/npm/fetchff)
+[![NPM version][npm-image]][npm-url] [](https://github.com/MattCCC/fetchff) [](https://github.com/MattCCC/fetchff) [](http://npm-stat.com/charts.html?package=fetchff) [](https://bundlephobia.com/result?p=fetchff) [](https://security.snyk.io/package/npm/fetchff)
@@ -36,18 +35,15 @@ Also, managing multitude of API connections in large applications can be complex
To address these challenges, the `fetchf()` provides several enhancements:
1. **Consistent Error Handling:**
-
- In JavaScript, the native `fetch()` function does not reject the Promise for HTTP error statuses such as 404 (Not Found) or 500 (Internal Server Error). Instead, `fetch()` resolves the Promise with a `Response` object, where the `ok` property indicates the success of the request. If the request encounters a network error or fails due to other issues (e.g., server downtime), `fetch()` will reject the Promise.
- The `fetchff` plugin aligns error handling with common practices and makes it easier to manage errors consistently by rejecting erroneous status codes.
2. **Enhanced Retry Mechanism:**
-
- **Retry Configuration:** You can configure the number of retries, delay between retries, and exponential backoff for failed requests. This helps to handle transient errors effectively.
- **Custom Retry Logic:** The `shouldRetry` asynchronous function allows for custom retry logic based on the error from `response.error` and attempt count, providing flexibility to handle different types of failures.
- **Retry Conditions:** Errors are only retried based on configurable retry conditions, such as specific HTTP status codes or error types.
3. **Improved Error Visibility:**
-
- **Error Wrapping:** The `createApiFetcher()` and `fetchf()` wrap errors in a custom `ResponseError` class, which provides detailed information about the request and response. This makes debugging easier and improves visibility into what went wrong.
4. **Extended settings:**
@@ -87,10 +83,11 @@ To address these challenges, the `fetchf()` provides several enhancements:
- **Smart Retry Mechanism**: Features exponential backoff for intelligent error handling and retry mechanisms.
- **Request Deduplication**: Set the time during which requests are deduplicated (treated as same request).
- **Cache Management**: Dynamically manage cache with configurable expiration, custom keys, and selective invalidation.
+- **Network Revalidation**: Automatically revalidate data on window focus and network reconnection for fresh data.
- **Dynamic URLs Support**: Easily manage routes with dynamic parameters, such as `/user/:userId`.
- **Error Handling**: Flexible error management at both global and individual request levels.
- **Request Cancellation**: Utilizes `AbortController` to cancel previous requests automatically.
-- **Timeouts**: Set timeouts globally or per request to prevent hanging operations.
+- **Adaptive Timeouts**: Smart timeout adjustment based on connection speed for optimal user experience.
- **Fetching Strategies**: Handle failed requests with various strategies - promise rejection, silent hang, soft fail, or default response.
- **Requests Chaining**: Easily chain multiple requests using promises for complex API interactions.
- **Native `fetch()` Support**: Utilizes the built-in `fetch()` API, providing a modern and native solution for making HTTP requests.
@@ -122,9 +119,11 @@ yarn add fetchff
### Standalone usage
-#### `fetchf()`
+#### `fetchf(url, config)`
+
+_Alias: `fetchff(url, config)`_
-It is a functional wrapper for `fetch()`. It seamlessly enhances it with additional settings like the retry mechanism and error handling improvements. The `fetchf()` can be used directly as a function, simplifying the usage and making it easier to integrate with functional programming styles. The `fetchf()` makes requests independently of `createApiFetcher()` settings.
+A simple function that wraps the native `fetch()` and adds extra features like retries and better error handling. Use `fetchf()` directly for quick, enhanced requests - no need to set up `createApiFetcher()`. It works independently and is easy to use in any codebase.
#### Example
@@ -139,9 +138,58 @@ const { data, error } = await fetchf('/api/user-details', {
});
```
-### Multiple API Endpoints
+### Global Configuration
+
+#### `getDefaultConfig()`
+
+
+ Click to expand
+
+
+Returns the current global default configuration used for all requests. This is useful for inspecting or debugging the effective global settings.
+
+```typescript
+import { getDefaultConfig } from 'fetchff';
+
+// Retrieve the current global default config
+const config = getDefaultConfig();
+console.log('Current global fetchff config:', config);
+```
+
+
+
+#### `setDefaultConfig(customConfig)`
+
+
+ Click to expand
+
+
+Allows you to globally override the default configuration for all requests. This is useful for setting application-wide defaults like timeouts, headers, or retry policies.
+
+```typescript
+import { setDefaultConfig } from 'fetchff';
+
+// Set global defaults for all requests
+setDefaultConfig({
+ timeout: 10000, // 10 seconds for all requests
+ headers: {
+ Authorization: 'Bearer your-token',
+ },
+ retry: {
+ retries: 2,
+ delay: 1500,
+ },
+});
+
+// All subsequent requests will use these defaults
+const { data } = await fetchf('/api/data'); // Uses 10s timeout and retry config
+```
+
+
+
+### Instance with many API endpoints
-#### `createApiFetcher()`
+#### `createApiFetcher(config)`
Click to expand
@@ -184,7 +232,7 @@ const { data, error } = await api.getUser({
All the Request Settings can be directly used in the function as global settings for all endpoints. They can be also used within the `endpoints` property (on per-endpoint basis). The exposed `endpoints` property is as follows:
- **`endpoints`**:
- Type: `EndpointsConfig`
+ Type: `EndpointsConfig`
List of your endpoints. Each endpoint is an object that accepts all the Request Settings (see the Basic Settings below). The endpoints are required to be specified.
#### How It Works
@@ -245,9 +293,238 @@ You can access `api.config` property directly to modify global headers and other
You can access `api.endpoints` property directly to modify the endpoints list. This can be useful if you want to append or remove global endpoints. This is a property, not a function.
-#### `api.getInstance()`
+
+
+### Advanced Utilities
+
+
+ Click to expand
+
+
+#### Cache Management
+
+##### `mutate(key, newData, settings)`
+
+Programmatically update cached data without making a network request. Useful for optimistic updates or reflecting changes from other operations.
+
+**Parameters:**
+
+- `key` (string): The cache key to update
+- `newData` (any): The new data to store in cache
+- `settings` (object, optional): Configuration options
+ - `revalidate` (boolean): Whether to trigger background revalidation after update
+
+```typescript
+import { mutate } from 'fetchff';
+
+// Update cache for a specific cache key
+await mutate('/api/users', newUserData);
+
+// Update with options
+await mutate('/api/users', updatedData, {
+ revalidate: true, // Trigger background revalidation
+});
+```
+
+##### `getCache(key)`
+
+Directly retrieve cached data for a specific cache key. Useful for reading the current cached response without triggering a network request.
+
+**Parameters:**
+
+- `key` (string): The cache key to retrieve (equivalent to `cacheKey` from request config or `config.cacheKey` from response object)
+
+**Returns:** The cached response object, or `null` if not found
+
+```typescript
+import { getCache } from 'fetchff';
+
+// Get cached data for a specific key assuming you set {cacheKey: ''/api/user-profile'} in config
+const cachedResponse = getCache('/api/user-profile');
+if (cachedResponse) {
+ console.log('Cached user profile:', cachedResponse.data);
+}
+```
+
+##### `setCache(key, response, ttl, staleTime)`
+
+Directly set cache data for a specific key. Unlike `mutate()`, this doesn't trigger revalidation by default. This is a low level function to directly set cache data based on particular key. If unsure, use the `mutate()` with `revalidate: false` instead.
+
+**Parameters:**
+
+- `key` (string): The cache key to set. It must match the cache key of the request.
+- `response` (any): The full response object to store in cache
+- `ttl` (number, optional): Time to live for the cache entry, in seconds. Determines how long the cached data remains valid before expiring. If not specified, the default `0` value will be used (discard cache immediately), if `-1` specified then the cache will be held until manually removed using `deleteCache(key)` function.
+- `staleTime` (number, optional): Duration, in seconds, for which cached data is considered "fresh" before it becomes eligible for background revalidation. If not specified, the default stale time applies.
+
+```typescript
+import { setCache } from 'fetchff';
+
+// Set cache data with custom ttl and staleTime
+setCache('/api/user-profile', userData, 600, 60); // Cache for 10 minutes, fresh for 1 minute
+
+// Set cache for specific endpoint infinitely
+setCache('/api/user-settings', userSettings, -1);
+```
+
+##### `deleteCache(key)`
+
+Remove cached data for a specific cache key. Useful for cache invalidation when you know data is stale.
+
+**Parameters:**
+
+- `key` (string): The cache key to delete
+
+```typescript
+import { deleteCache } from 'fetchff';
+
+// Delete specific cache entry
+deleteCache('/api/user-profile');
+
+// Delete cache after user logout
+const logout = () => {
+ deleteCache('/api/user/*'); // Delete all user-related cache
+};
+```
+
+#### Revalidation Management
+
+##### `revalidate(key, isStaleRevalidation)`
+
+Manually trigger revalidation for a specific cache entry, forcing a fresh network request to update the cached data.
+
+**Parameters:**
+
+- `key` (string): The cache key to revalidate
+- `isStaleRevalidation` (boolean, optional): Whether this is a background revalidation that doesn't mark as in-flight
+
+```typescript
+import { revalidate } from 'fetchff';
+
+// Revalidate specific cache entry
+await revalidate('/api/user-profile');
+
+// Revalidate with custom cache key
+await revalidate('custom-cache-key');
+
+// Background revalidation (doesn't mark as in-flight)
+await revalidate('/api/user-profile', true);
+```
+
+##### `revalidateAll(type, isStaleRevalidation)`
+
+Trigger revalidation for all cache entries associated with a specific event type (focus or online).
+
+**Parameters:**
+
+- `type` (string): The revalidation event type ('focus' or 'online')
+- `isStaleRevalidation` (boolean, optional): Whether this is a background revalidation
+
+```typescript
+import { revalidateAll } from 'fetchff';
+
+// Manually trigger focus revalidation for all relevant entries
+revalidateAll('focus');
+
+// Manually trigger online revalidation for all relevant entries
+revalidateAll('online');
+```
+
+##### `removeRevalidators(type)`
+
+Clean up revalidation event listeners for a specific event type. Useful for preventing memory leaks when you no longer need automatic revalidation.
+
+**Parameters:**
+
+- `type` (string): The revalidation event type to remove ('focus' or 'online')
+
+```typescript
+import { removeRevalidators } from 'fetchff';
+
+// Remove all focus revalidation listeners
+removeRevalidators('focus');
+
+// Remove all online revalidation listeners
+removeRevalidators('online');
+
+// Typically called during cleanup
+// e.g., in React useEffect cleanup or when unmounting components
+```
+
+#### Pub/Sub System
+
+##### `subscribe(key, callback)`
+
+Subscribe to cache updates and data changes. Receive notifications when specific cache entries are updated.
+
+**Parameters:**
+
+- `key` (string): The cache key to subscribe to
+- `callback` (function): Function called when cache is updated
+ - `response` (any): The full response object
+
+**Returns:** Function to unsubscribe from updates
+
+```typescript
+import { subscribe } from 'fetchff';
+
+// Subscribe to cache changes for a specific key
+const unsubscribe = subscribe('/api/user-data', (response) => {
+ console.log('Cache updated with response:', response);
+ console.log('Response data:', response.data);
+ console.log('Response status:', response.status);
+});
+
+// Clean up subscription when no longer needed
+unsubscribe();
+```
+
+#### Request Management
+
+##### `abortRequest(key, error)`
+
+Programmatically abort in-flight requests for a specific cache key or URL pattern.
+
+**Parameters:**
-If you initialize API handler with your custom `fetcher`, then this function will return the instance created using `fetcher.create()` function. Your fetcher can include anything. It will be triggering `fetcher.request()` instead of native fetch() that is available by default. It gives you ultimate flexibility on how you want your requests to be made.
+- `key` (string): The cache key or URL pattern to abort
+- `error` (Error, optional): Custom error to throw for aborted requests
+
+```typescript
+import { abortRequest } from 'fetchff';
+
+// Abort specific request by cache key
+abortRequest('/api/slow-operation');
+
+// Useful for cleanup when component unmounts or route changes
+const cleanup = () => {
+ abortRequest('/api/user-dashboard');
+};
+```
+
+#### Network Detection
+
+##### `isSlowConnection()`
+
+Check if the user is on a slow network connection (2G/3G). Useful for adapting application behavior based on connection speed.
+
+**Parameters:** None
+
+**Returns:** Boolean indicating if connection is slow
+
+```typescript
+import { isSlowConnection } from 'fetchff';
+
+// Check connection speed and adapt behavior
+if (isSlowConnection()) {
+ console.log('User is on a slow connection');
+ // Reduce image quality, disable auto-refresh, etc.
+}
+
+// Use in conditional logic
+const shouldAutoRefresh = !isSlowConnection();
+const imageQuality = isSlowConnection() ? 'low' : 'high';
+```
@@ -275,20 +552,198 @@ You can pass the settings:
You can also use all native [`fetch()` settings](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters).
-| | Type | Default | Description |
-| -------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| baseURL (alias: apiUrl) | `string` | | Your API base url. |
-| url | `string` | | URL path e.g. /user-details/get |
-| method | `string` | `GET` | Default request method e.g. GET, POST, DELETE, PUT etc. All methods are supported. |
-| params | `object` `URLSearchParams` `NameValuePair[]` | `{}` | Query Parameters - a key-value pairs added to the URL to send extra information with a request. If you pass an object, it will be automatically converted. It works with nested objects, arrays and custom data structures similarly to what `jQuery` used to do in the past. If you use `createApiFetcher()` then it is the first argument of your `api.yourEndpoint()` function. You can still pass configuration in 3rd argument if want to. You can pass key-value pairs where the values can be strings, numbers, or arrays. For example, if you pass `{ foo: [1, 2] }`, it will be automatically serialized into `foo[]=1&foo[]=2` in the URL. |
-| body (alias: data) | `object` `string` `FormData` `URLSearchParams` `Blob` `ArrayBuffer` `ReadableStream` | `{}` | The body is the data sent with the request, such as JSON, text, or form data, included in the request payload for POST, PUT, or PATCH requests. |
-| urlPathParams | `object` | `{}` | It lets you dynamically replace segments of your URL with specific values in a clear and declarative manner. This feature is especially handy for constructing URLs with variable components or identifiers. For example, suppose you need to update user details and have a URL template like `/user-details/update/:userId`. With `urlPathParams`, you can replace `:userId` with a real user ID, such as `123`, resulting in the URL `/user-details/update/123`. |
-| flattenResponse | `boolean` | `false` | When set to `true`, this option flattens the nested response data. This means you can access the data directly without having to use `response.data.data`. It works only if the response structure includes a single `data` property. |
-| defaultResponse | `any` | `null` | Default response when there is no data or when endpoint fails depending on the chosen `strategy` |
-| withCredentials | `boolean` | `false` | Indicates whether credentials (such as cookies) should be included with the request. This equals to `credentials: "include"` in native `fetch()`. In Node.js, cookies are not managed automatically. Use a fetch polyfill or library that supports cookies if needed. |
-| timeout | `number` | `30000` | You can set a request timeout in milliseconds. By default 30 seconds (30000 ms). The timeout option applies to each individual request attempt including retries and polling. `0` means that the timeout is disabled. |
-| logger | `Logger` | `null` | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. |
-| fetcher | `FetcherInstance` | | A custom adapter (an instance / object) that exposes `create()` function so to create instance of API Fetcher. The `create()` should return `request()` function that would be used when making the requests. The native `fetch()` is used if the fetcher is not provided. |
+| | Type | Default | Description |
+| -------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| baseURL (alias: apiUrl) | `string` | `undefined` | Your API base url. |
+| url | `string` | `undefined` | URL path e.g. /user-details/get |
+| method | `string` | `'GET'` | Default request method e.g. GET, POST, DELETE, PUT etc. All methods are supported. |
+| params | `object` `URLSearchParams` `NameValuePair[]` | `undefined` | Query Parameters - a key-value pairs added to the URL to send extra information with a request. If you pass an object, it will be automatically converted. It works with nested objects, arrays and custom data structures similarly to what `jQuery` used to do in the past. If you use `createApiFetcher()` then it is the first argument of your `api.yourEndpoint()` function. You can still pass configuration in 3rd argument if want to. You can pass key-value pairs where the values can be strings, numbers, or arrays. For example, if you pass `{ foo: [1, 2] }`, it will be automatically serialized into `foo[]=1&foo[]=2` in the URL. |
+| body (alias: data) | `object` `string` `FormData` `URLSearchParams` `Blob` `ArrayBuffer` `ReadableStream` | `undefined` | The body is the data sent with the request, such as JSON, text, or form data, included in the request payload for POST, PUT, or PATCH requests. |
+| urlPathParams | `object` | `undefined` | It lets you dynamically replace segments of your URL with specific values in a clear and declarative manner. This feature is especially handy for constructing URLs with variable components or identifiers. For example, suppose you need to update user details and have a URL template like `/user-details/update/:userId`. With `urlPathParams`, you can replace `:userId` with a real user ID, such as `123`, resulting in the URL `/user-details/update/123`. |
+| flattenResponse | `boolean` | `false` | When set to `true`, this option flattens the nested response data. This means you can access the data directly without having to use `response.data.data`. It works only if the response structure includes a single `data` property. |
+| select | `(data: any) => any` | `undefined` | Function to transform or select a subset of the response data before it is returned. Called with the raw response data and should return the transformed data. Useful for mapping, picking, or shaping the response. |
+| defaultResponse | `any` | `null` | Default response when there is no data or when endpoint fails depending on the chosen `strategy` |
+| withCredentials | `boolean` | `false` | Indicates whether credentials (such as cookies) should be included with the request. This equals to `credentials: "include"` in native `fetch()`. In Node.js, cookies are not managed automatically. Use a fetch polyfill or library that supports cookies if needed. |
+| timeout | `number` | `30000` / `60000` | You can set a request timeout in milliseconds. **Default is adaptive**: 30 seconds (30000 ms) for normal connections, 60 seconds (60000 ms) on slow connections (2G/3G). The timeout option applies to each individual request attempt including retries and polling. `0` means that the timeout is disabled. |
+| dedupeTime | `number` | `0` | Time window, in milliseconds, during which identical requests are deduplicated (treated as single request). If set to `0`, deduplication is disabled. |
+| cacheTime | `number` | `undefined` | Specifies the duration, in seconds, for which a cache entry is considered "fresh." Once this time has passed, the entry is considered stale and may be refreshed with a new request. Set to -1 for indefinite cache. By default no caching. |
+| staleTime | `number` | `undefined` | Specifies the duration, in seconds, for which cached data is considered "fresh." During this period, cached data will be returned immediately, but a background revalidation (network request) will be triggered to update the cache. If set to `0`, background revalidation is disabled and revalidation is triggered on every access. |
+| refetchOnFocus | `boolean` | `false` | When set to `true`, automatically revalidates (refetches) data when the browser window regains focus. **Note: This bypasses the cache and always makes a fresh network request** to ensure users see the most up-to-date data when they return to your application from another tab or window. Particularly useful for applications that display real-time or frequently changing data, but should be used judiciously to avoid unnecessary network traffic. |
+| refetchOnReconnect | `boolean` | `false` | When set to `true`, automatically revalidates (refetches) data when the browser regains internet connectivity after being offline. **This uses background revalidation to silently update data** without showing loading states to users. Helps ensure your application displays fresh data after network interruptions. Works by listening to the browser's `online` event. |
+| logger | `Logger` | `null` | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. |
+| fetcher | `CustomFetcher` | `undefined` | A custom fetcher async function. By default, the native `fetch()` is used. If you use your own fetcher, default response parsing e.g. `await response.json()` call will be skipped. Your fetcher should return data. |
+
+> đ **Additional Settings Available:**
+> The table above shows the most commonly used settings. Many more advanced configuration options are available and documented in their respective sections below, including:
+>
+> - **đ Retry Mechanism** - `retries`, `delay`, `maxDelay`, `backoff`, `resetTimeout`, `retryOn`, `shouldRetry`
+> - **đļ Polling Configuration** - `pollingInterval`, `pollingDelay`, `maxPollingAttempts`, `shouldStopPolling`
+> - **đī¸ Cache Management** - `cacheKey`, `cacheBuster`, `skipCache`, `cacheErrors`
+> - **â Request Cancellation** - `cancellable`, `rejectCancelled`
+> - **đ Interceptors** - `onRequest`, `onResponse`, `onError`, `onRetry`
+> - **đ Error Handling** - `strategy`
+
+### Performance Implications of Settings
+
+Understanding the performance impact of different settings helps you optimize for your specific use case:
+
+#### **High-Performance Settings**
+
+**Minimize Network Requests:**
+
+```typescript
+// Aggressive caching for static data
+const staticConfig = {
+ cacheTime: 3600, // 1 hour cache
+ staleTime: 1800, // 30 minutes freshness
+ dedupeTime: 10000, // 10 seconds deduplication
+};
+
+// Result: 90%+ reduction in network requests
+```
+
+**Optimize for Mobile/Slow Connections:**
+
+```typescript
+const mobileOptimized = {
+ timeout: 60000, // Longer timeout for slow connections (auto-adaptive)
+ retry: {
+ retries: 5, // More retries for unreliable connections
+ delay: 2000, // Longer initial delay (auto-adaptive)
+ backoff: 2.0, // Aggressive backoff
+ },
+ cacheTime: 900, // Longer cache on mobile
+};
+```
+
+#### **Memory vs Network Trade-offs**
+
+**Memory-Efficient (Low Cache):**
+
+```typescript
+const memoryEfficient = {
+ cacheTime: 60, // Short cache (1 minute)
+ staleTime: undefined, // No stale-while-revalidate
+ dedupeTime: 1000, // Short deduplication
+};
+// Pros: Low memory usage
+// Cons: More network requests, slower perceived performance
+```
+
+**Network-Efficient (High Cache):**
+
+```typescript
+const networkEfficient = {
+ cacheTime: 1800, // Long cache (30 minutes)
+ staleTime: 300, // 5 minutes stale-while-revalidate
+ dedupeTime: 5000, // Longer deduplication
+};
+// Pros: Fewer network requests, faster user experience
+// Cons: Higher memory usage, potentially stale data
+```
+
+#### **Feature Performance Impact**
+
+| Feature | Performance Impact | Best Use Case |
+| -------------------------- | ----------------------------------- | ------------------------------------------- |
+| **Caching** | âŦī¸ 70-90% fewer requests | Static or semi-static data |
+| **Deduplication** | âŦī¸ 50-80% fewer concurrent requests | High-traffic applications |
+| **Stale-while-revalidate** | âŦī¸ 90% faster perceived loading | Dynamic data that tolerates brief staleness |
+| **Request cancellation** | âŦī¸ Reduced bandwidth waste | Search-as-you-type, rapid navigation |
+| **Retry mechanism** | âŦī¸ 95%+ success rate | Mission-critical operations |
+| **Polling** | âŦī¸ Real-time updates | Live data monitoring |
+
+#### **Adaptive Performance by Connection**
+
+FetchFF automatically adapts timeouts and retry delays based on connection speed:
+
+```typescript
+// Automatic adaptation (no configuration needed)
+const adaptiveRequest = fetchf('/api/data');
+
+// On fast connections (WiFi/4G):
+// - timeout: 30 seconds
+// - retry delay: 1 second â 1.5s â 2.25s...
+// - max retry delay: 30 seconds
+
+// On slow connections (2G/3G):
+// - timeout: 60 seconds
+// - retry delay: 2 seconds â 3s â 4.5s...
+// - max retry delay: 60 seconds
+```
+
+#### **Performance Patterns**
+
+**Progressive Loading (Best UX):**
+
+```typescript
+// Layer 1: Instant response with cache
+const quickData = await fetchf('/api/summary', {
+ cacheTime: 300,
+ staleTime: 60,
+});
+
+// Layer 2: Background enhancement
+fetchf('/api/detailed-data', {
+ strategy: 'silent',
+ cacheTime: 600,
+ onResponse(response) {
+ updateUIWithDetailedData(response.data);
+ },
+});
+```
+
+**Bandwidth-Conscious Loading:**
+
+```typescript
+// Check connection before expensive operations
+import { isSlowConnection } from 'fetchff';
+
+const loadUserDashboard = async () => {
+ const isSlowConn = isSlowConnection();
+
+ // Essential data always loads
+ const userData = await fetchf('/api/user', {
+ cacheTime: isSlowConn ? 600 : 300, // Longer cache on slow connections
+ });
+
+ // Optional data only on fast connections
+ if (!isSlowConn) {
+ fetchf('/api/user/analytics', { strategy: 'silent' });
+ fetchf('/api/user/recommendations', { strategy: 'silent' });
+ }
+};
+```
+
+#### **Performance Monitoring**
+
+Track key metrics to optimize your settings:
+
+```typescript
+const performanceConfig = {
+ onRequest(config) {
+ console.time(`request-${config.url}`);
+ },
+
+ onResponse(response) {
+ console.timeEnd(`request-${response.config.url}`);
+
+ // Track cache hit rate
+ if (response.fromCache) {
+ incrementMetric('cache.hits');
+ } else {
+ incrementMetric('cache.misses');
+ }
+ },
+
+ onError(error) {
+ incrementMetric('requests.failed');
+ console.warn('Request failed:', error.config.url, error.status);
+ },
+};
+```
+
+> âšī¸ **Note:** This is just an example. You need to implement the `incrementMetric` function yourself to record or report performance metrics as needed in your application.
@@ -343,9 +798,6 @@ const { data } = await fetchf('https://api.example.com/endpoint', {
The `fetchff` plugin automatically injects a set of default headers into every request. These default headers help ensure that requests are consistent and include necessary information for the server to process them correctly.
-- **`Content-Type`**: `application/json;charset=utf-8`
- Specifies that the request body contains JSON data and sets the character encoding to UTF-8.
-
- **`Accept`**: `application/json, text/plain, */*`
Indicates the media types that the client is willing to receive from the server. This includes JSON, plain text, and any other types.
@@ -355,6 +807,22 @@ The `fetchff` plugin automatically injects a set of default headers into every r
> â ī¸ **Accept-Encoding in Node.js:**
> In Node.js, decompression is handled by the fetch implementation, and users should ensure their environment supports the encodings.
+- **`Content-Type`**:
+ Set automatically based on the request body type:
+ - For JSON-serializable bodies (objects, arrays, etc.):
+ `application/json; charset=utf-8`
+ - For `URLSearchParams`:
+ `application/x-www-form-urlencoded`
+ - For `ArrayBuffer`/typed arrays:
+ `application/octet-stream`
+ - For `FormData`, `Blob`, `File`, or `ReadableStream`:
+ **Not set** as the header is handled automatically by the browser and by Node.js 18+ native fetch.
+
+ The `Content-Type` header is **never overridden** if you set it manually.
+
+**Summary:**
+You only need to set headers manually if you want to override these defaults. Otherwise, `fetchff` will handle the correct headers for most use cases, including advanced scenarios like file uploads, form submissions, and binary data.
+
## đ Interceptors
@@ -381,63 +849,325 @@ const { data } = await fetchf('https://api.example.com/', {
console.error('Request failed:', error);
console.error('Request config:', config);
},
+ onRetry(response, attempt) {
+ // Log retry attempts for monitoring and debugging
+ console.warn(
+ `Retrying request (attempt ${attempt + 1}):`,
+ response.config.url,
+ );
+
+ // Modify config for the upcoming retry request
+ response.config.headers['Authorization'] = 'Bearer your-new-token';
+
+ // Log error details for failed attempts
+ if (response.error) {
+ console.warn(
+ `Retry reason: ${response.error.status} - ${response.error.statusText}`,
+ );
+ }
+
+ // You can implement custom retry logic or monitoring here
+ // For example, send retry metrics to your analytics service
+ },
+ retry: {
+ retries: 3,
+ delay: 1000,
+ backoff: 1.5,
+ },
});
```
### Configuration
-The following options are available for configuring interceptors in the `RequestHandler`:
+The following options are available for configuring interceptors in the `fetchff` settings:
-- **`onRequest`**:
+- **`onRequest(config) => config`**:
Type: `RequestInterceptor | RequestInterceptor[]`
- A function or an array of functions that are invoked before sending a request. Each function receives the request configuration object as its argument, allowing you to modify request parameters, headers, or other settings.
- _Default:_ `(config) => config` (no modification).
+ A function or an array of functions that are invoked before sending a request. Each function receives the request configuration object as its argument, allowing you to modify request parameters, headers, or other settings.
+ _Default:_ `undefined` (no modification).
-- **`onResponse`**:
+- **`onResponse(response) => response`**:
Type: `ResponseInterceptor | ResponseInterceptor[]`
A function or an array of functions that are invoked when a response is received. Each function receives the full response object, enabling you to process the response, handle status codes, or parse data as needed.
- _Default:_ `(response) => response` (no modification).
+ _Default:_ `undefined` (no modification).
-- **`onError`**:
+- **`onError(error) => error`**:
Type: `ErrorInterceptor | ErrorInterceptor[]`
A function or an array of functions that handle errors when a request fails. Each function receives the error and request configuration as arguments, allowing you to implement custom error handling logic or logging.
- _Default:_ `(error) => error` (no modification).
+ _Default:_ `undefined` (no modification).
-### How It Works
+- **`onRetry(response, attempt) => response`**:
+ Type: `RetryInterceptor | RetryInterceptor[]`
+ A function or an array of functions that are invoked before each retry attempt. Each function receives the response object (containing error information) and the current attempt number as arguments, allowing you to implement custom retry logging, monitoring, or conditional retry logic.
+ _Default:_ `undefined` (no retry interception).
-1. **Request Interception**:
- Before a request is sent, the `onRequest` interceptors are invoked. These interceptors can modify the request configuration, such as adding headers or changing request parameters.
+All interceptors are asynchronous and can modify the provided config or response objects. You don't have to return a value, but if you do, any returned properties will be merged into the original argument.
-2. **Response Interception**:
- Once a response is received, the `onResponse` interceptors are called. These interceptors allow you to handle the response data, process status codes, or transform the response before it is returned to the caller.
+### Interceptor Execution Order
-3. **Error Interception**:
- If a request fails and an error occurs, the `onError` interceptors are triggered. These interceptors provide a way to handle errors, such as logging or retrying requests, based on the error and the request configuration.
+`fetchff` follows specific execution patterns for interceptor chains:
-4. **Custom Handling**:
- Each interceptor function provides a flexible way to customize request and response behavior. You can use these functions to integrate with other systems, handle specific cases, or modify requests and responses as needed.
+#### **Request Interceptors: FIFO (First In, First Out)**
-
+Request interceptors execute in the order they are defined - from global to specific:
-## đī¸ Cache Management
+```typescript
+// Execution order: 1 â 2 â 3 â 4
+const api = createApiFetcher({
+ onRequest: (config) => {
+ /* 1. Global interceptor */
+ },
+ endpoints: {
+ getData: {
+ onRequest: (config) => {
+ /* 2. Endpoint interceptor */
+ },
+ },
+ },
+});
-
- Click to expand
-
- The caching mechanism in fetchf() and createApiFetcher() enhances performance by reducing redundant network requests and reusing previously fetched data when appropriate. This system ensures that cached responses are managed efficiently and only used when considered "fresh". Below is a breakdown of the key parameters that control caching behavior and their default values.
-
+await api.getData({
+ onRequest: (config) => {
+ /* 3. Request interceptor */
+ },
+});
+```
-> â ī¸ **When using in Node.js:**
-> Cache and deduplication are in-memory and per-process. For distributed or serverless environments, consider external caching if persistence is needed.
+#### **Response Interceptors: LIFO (Last In, First Out)**
-### Example
+Response interceptors execute in reverse order - from specific to global:
```typescript
-const { data } = await fetchf('https://api.example.com/', {
- cacheTime: 300, // Cache is valid for 5 minutes
- cacheKey: (config) => `${config.url}-${config.method}`, // Custom cache key based on URL and method
- cacheBuster: (config) => config.method === 'POST', // Bust cache for POST requests
- skipCache: (response, config) => response.status !== 200, // Skip caching on non-200 responses
+// Execution order: 3 â 2 â 1
+const api = createApiFetcher({
+ onResponse: (response) => {
+ /* 3. Global interceptor (executes last) */
+ },
+ endpoints: {
+ getData: {
+ onResponse: (response) => {
+ /* 2. Endpoint interceptor */
+ },
+ },
+ },
+});
+
+await api.getData({
+ onResponse: (response) => {
+ /* 1. Request interceptor (executes first) */
+ },
+});
+```
+
+This pattern ensures that:
+
+- **Request interceptors** can progressively enhance configuration from general to specific
+- **Response interceptors** can process data from specific to general, allowing request-level interceptors to handle the response first before global cleanup or logging
+
+### How It Works
+
+1. **Request Interception**:
+ Before a request is sent, the `onRequest` interceptors are invoked. These interceptors can modify the request configuration, such as adding headers or changing request parameters.
+
+2. **Response Interception**:
+ Once a response is received, the `onResponse` interceptors are called. These interceptors allow you to handle the response data, process status codes, or transform the response before it is returned to the caller.
+
+3. **Error Interception**:
+ If a request fails and an error occurs, the `onError` interceptors are triggered. These interceptors provide a way to handle errors, such as logging or retrying requests, based on the error and the request configuration.
+
+4. **Custom Handling**:
+ Each interceptor function provides a flexible way to customize request and response behavior. You can use these functions to integrate with other systems, handle specific cases, or modify requests and responses as needed.
+
+
+
+## đ Network Revalidation
+
+
+ Click to expand
+
+
+`fetchff` provides intelligent network revalidation features that automatically keep your data fresh based on user interactions and network connectivity. These features help ensure users always see up-to-date information without manual intervention.
+
+### Focus Revalidation
+
+When `refetchOnFocus` is enabled, requests are automatically triggered when the browser window regains focus (e.g., when users switch back to your tab).
+
+```typescript
+const { data } = await fetchf('/api/user-profile', {
+ refetchOnFocus: true, // Revalidate when window gains focus
+ cacheTime: 300, // Cache for 5 minutes, but still revalidate on focus
+});
+```
+
+### Network Reconnection Revalidation
+
+The `refetchOnReconnect` feature automatically revalidates data when the browser detects that internet connectivity has been restored after being offline.
+
+```typescript
+const { data } = await fetchf('/api/notifications', {
+ refetchOnReconnect: true, // Revalidate when network reconnects
+ cacheTime: 600, // Cache for 10 minutes, but revalidate when back online
+});
+```
+
+### Adaptive Timeouts
+
+`fetchff` automatically adjusts request timeouts based on connection speed to provide optimal user experience:
+
+```typescript
+// Automatically uses:
+// - 30 seconds timeout on normal connections
+// - 60 seconds timeout on slow connections (2G/3G)
+const { data } = await fetchf('/api/data');
+
+// You can still override with custom timeout
+const { data: customTimeout } = await fetchf('/api/data', {
+ timeout: 10000, // Force 10 seconds regardless of connection speed
+});
+
+// Check connection speed manually
+import { isSlowConnection } from 'fetchff';
+
+if (isSlowConnection()) {
+ console.log('User is on a slow connection');
+ // Adjust your app behavior accordingly
+}
+```
+
+### How It Works
+
+1. **Event Listeners**: `fetchff` automatically attaches global event listeners for `focus` and `online` events when needed
+2. **Background Revalidation**: Network revalidation uses background requests that don't show loading states to users
+3. **Automatic Cleanup**: Event listeners are properly managed and cleaned up to prevent memory leaks
+4. **Smart Caching**: Revalidation works alongside caching - fresh data updates the cache for future requests
+5. **Stale-While-Revalidate**: Use `staleTime` to control when background revalidation happens automatically
+6. **Connection Awareness**: Automatically detects connection speed and adjusts timeouts for better reliability
+
+### Configuration Options
+
+Both revalidation features can be configured globally or per-request, and work seamlessly with cache timing:
+
+```typescript
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ baseURL: 'https://api.example.com',
+ // Global settings apply to all endpoints
+ refetchOnFocus: true,
+ refetchOnReconnect: true,
+ cacheTime: 300, // Cache for 5 minutes
+ staleTime: 60, // Consider fresh for 1 minute, then background revalidate
+ endpoints: {
+ getCriticalData: {
+ url: '/critical-data',
+ // Override global settings for specific endpoints
+ refetchOnFocus: true,
+ refetchOnReconnect: true,
+ staleTime: 30, // More aggressive background revalidation for critical data
+ },
+ getStaticData: {
+ url: '/static-data',
+ // Disable revalidation for static data
+ refetchOnFocus: false,
+ refetchOnReconnect: false,
+ staleTime: 3600, // Background revalidate after 1 hour
+ },
+ },
+});
+```
+
+### Use Cases
+
+**Focus Revalidation** is ideal for:
+
+- Real-time dashboards and analytics
+- Social media feeds and chat applications
+- Financial data and trading platforms
+- Any data that changes frequently while users are away
+
+**Reconnection Revalidation** is perfect for:
+
+- Mobile applications with intermittent connectivity
+- Offline-capable applications
+- Critical data that must be current when online
+- Applications used in areas with unstable internet
+
+### Best Practices
+
+1. **Combine with appropriate cache and stale times**:
+
+ ```typescript
+ const { data } = await fetchf('/api/live-data', {
+ cacheTime: 300, // Cache for 5 minutes
+ staleTime: 30, // Consider fresh for 30 seconds
+ refetchOnFocus: true, // Also revalidate on focus
+ refetchOnReconnect: true,
+ });
+ ```
+
+2. **Use `staleTime` for automatic background updates** - Data stays fresh without user interaction:
+
+ ```typescript
+ // Good: Automatic background revalidation for dynamic data
+ const { data: notifications } = await fetchf('/api/notifications', {
+ cacheTime: 600, // Cache for 10 minutes
+ staleTime: 60, // Background revalidate after 1 minute
+ refetchOnFocus: true,
+ });
+
+ // Good: Less frequent updates for semi-static data
+ const { data: userProfile } = await fetchf('/api/profile', {
+ cacheTime: 1800, // Cache for 30 minutes
+ staleTime: 600, // Background revalidate after 10 minutes
+ refetchOnReconnect: true,
+ });
+ ```
+
+3. **Use selectively** - Don't enable for all requests to avoid unnecessary network traffic:
+
+ ```typescript
+ // Good: Enable for critical, changing data
+ const { data: userNotifications } = await fetchf('/api/notifications', {
+ refetchOnFocus: true,
+ refetchOnReconnect: true,
+ });
+
+ // Avoid: Don't enable for static configuration data
+ const { data: appConfig } = await fetchf('/api/config', {
+ cacheTime: 3600, // Cache for 1 hour
+ staleTime: 0, // Disable background revalidation
+ refetchOnFocus: false,
+ refetchOnReconnect: false,
+ });
+ ```
+
+4. **Consider user experience** - Network revalidation happens silently in the background, providing smooth UX without loading spinners.
+
+> â ī¸ **Browser Support**: These features work in all modern browsers that support the `focus` and `online` events. In server-side environments (Node.js), these options are safely ignored.
+
+
+
+## đī¸ Cache Management
+
+
+ Click to expand
+
+ The caching mechanism in fetchf() and createApiFetcher() enhances performance by reducing redundant network requests and reusing previously fetched data when appropriate. This system ensures that cached responses are managed efficiently and only used when considered "fresh". Below is a breakdown of the key parameters that control caching behavior and their default values.
+
+
+> â ī¸ **When using in Node.js:**
+> Cache and deduplication are in-memory and per-process. For distributed or serverless environments, consider external caching if persistence is needed.
+
+### Example
+
+```typescript
+const { data } = await fetchf('https://api.example.com/', {
+ cacheTime: 300, // Cache is valid for 5 minutes, set -1 for indefinite cache. By default no cache.
+ cacheKey: (config) => `${config.url}-${config.method}`, // Custom cache key based on URL and method, default automatically generated
+ cacheBuster: (config) => config.method === 'POST', // Bust cache for POST requests, by default no busting.
+ skipCache: (response, config) => response.status !== 200, // Skip caching on non-200 responses, by default no skipping
+ cacheErrors: false, // Cache error responses as well as successful ones, default false
+ staleTime: 600, // Data is considered fresh for 10 minutes before background revalidation (0 by default, meaning no background revalidation)
});
```
@@ -447,13 +1177,27 @@ The caching system can be fine-tuned using the following options when configurin
- **`cacheTime`**:
Type: `number`
- Specifies the duration, in seconds, for which a cache entry is considered "fresh." Once this time has passed, the entry is considered stale and may be refreshed with a new request.
- _Default:_ `0` (no caching).
+ Specifies the duration, in seconds, for which a cache entry is considered "fresh." Once this time has passed, the entry is considered stale and may be refreshed with a new request. Set to -1 for indefinite cache.
+ _Default:_ `undefined` (no caching).
- **`cacheKey`**:
- Type: `CacheKeyFunction`
- A function used to generate a custom cache key for the request. If not provided, a default key is created by hashing various parts of the request, including `Method`, `URL`, query parameters, and headers.
- _Default:_ Auto-generated based on request properties.
+ Type: `CacheKeyFunction | string`
+ A string or function used to generate a custom cache key for the request cache, deduplication etc. If not provided, a default key is created by hashing various parts of the request, including `Method`, `URL`, query parameters, and headers etc. Providing string can help to greatly improve the performance of the requests, avoid unnecessary request flooding etc.
+
+ You can provide either:
+ - A **string**: Used directly as the cache key for all requests using matching string.
+ - A **function**: Receives the full request config as an argument and should return a unique string key. This allows you to include any relevant part of the request (such as URL, method, params, body, or custom logic) in the cache key.
+
+ **Example:**
+
+ ```typescript
+ cacheKey: (config) =>
+ `${config.method}:${config.url}:${JSON.stringify(config.params)}`;
+ ```
+
+ This flexibility ensures you can control cache granularityâwhether you want to cache per endpoint, per user, or based on any other criteria.
+
+ _Default:_ Auto-generated based on request properties (see below).
- **`cacheBuster`**:
Type: `CacheBusterFunction`
@@ -465,22 +1209,213 @@ The caching system can be fine-tuned using the following options when configurin
A function that determines whether caching should be skipped based on the response. This allows for fine-grained control over whether certain responses are cached or not, such as skipping non-`200` responses.
_Default:_ `(response, config) => false` (no skipping).
-### How It Works
+- **`cacheErrors`**:
+ Type: `boolean`
+ Determines whether error responses (such as HTTP 4xx or 5xx) should also be cached. If set to `true`, both successful and error responses are stored in the cache. If `false`, only successful responses are cached.
+ _Default:_ `false`.
+
+- **`staleTime`**:
+ Specifies the time in seconds during which cached data is considered "fresh" before it becomes stale and triggers background revalidation (SWR: stale-while-revalidate).
+ - Set to a number greater than `0` to enable SWR: cached data will be served instantly, and a background request will update the cache after this period.
+ - Set to `0` to treat data as stale immediately (always eligible for refetch).
+ - Set to `undefined` to disable SWR: data is never considered stale and background revalidation is not performed.
+ _Default:_ `undefined` to disable SWR pattern (data is never considered stale) or `300` (5 minutes) in libraries like React.
+
+ ### How It Works
+ 1. **Cache Lookup**:
+ When a request is made, `fetchff` first checks the internal cache for a matching entry using the generated cache key. If a valid and "fresh" cache entry exists (within `cacheTime`), the cached response is returned immediately. If the native `fetch()` option `cache: 'reload'` is set, the internal cache is bypassed and a fresh request is made.
+
+ 2. **Cache Key Generation**:
+ Each request is uniquely identified by a cache key, which is auto-generated from the URL, method, params, headers, and other relevant options. You can override this by providing a custom `cacheKey` string or function for fine-grained cache control.
+
+ 3. **Cache Busting**:
+ If a `cacheBuster` function is provided, it determines whether to invalidate (bust) the cache for a given request. This is useful for scenarios like forcing fresh data on `POST` requests or after certain actions.
+
+ 4. **Conditional Caching**:
+ The `skipCache` function allows you to decide, per response, whether it should be stored in the cache. For example, you can skip caching for error responses (like HTTP 4xx/5xx) or based on custom logic.
-1. **Request and Cache Check**:
- When a request is made, the cache is first checked for an existing entry. If a valid cache entry is found and is still "fresh" (based on `cacheTime`), the cached response is returned immediately. Note that when the native `fetch()` setting called `cache` is set to `reload` the request will automatically skip the internal cache.
+ 5. **Network Request and Cache Update**:
+ If no valid cache entry is found, or if caching is skipped or busted, the request is sent to the network. The response is then cached according to your configuration, making it available for future requests.
-2. **Cache Key**:
- A cache key uniquely identifies each request. By default, the key is generated based on the URL and other relevant request options. Custom keys can be provided using the `cacheKey` function.
+### đ Cache and Deduplication Integration
-3. **Cache Busting**:
- If the `cacheBuster` function is defined, it determines whether to invalidate and refresh the cache for specific requests. This is useful for ensuring that certain requests, such as `POST` requests, always fetch new data.
+Understanding how caching works together with request deduplication is crucial for optimal performance:
-4. **Skipping Cache**:
- The `skipCache` function provides flexibility in deciding whether to store a response in the cache. For example, you might skip caching responses that have a `4xx` or `5xx` status code.
+#### **Cache-First, Then Deduplication**
-5. **Final Outcome**:
- If no valid cache entry is found, or the cache is skipped or busted, the request proceeds to the network, and the response is cached based on the provided configuration.
+```typescript
+// Multiple components requesting the same data
+const userProfile1 = useFetcher('/api/user/123', { cacheTime: 300 });
+const userProfile2 = useFetcher('/api/user/123', { cacheTime: 300 });
+const userProfile3 = useFetcher('/api/user/123', { cacheTime: 300 });
+
+// Flow:
+// 1. First request checks cache â cache miss â network request initiated
+// 2. Second request checks cache â cache miss â joins in-flight request (deduplication)
+// 3. Third request checks cache â cache miss â joins in-flight request (deduplication)
+// 4. When network response arrives â cache is populated â all requests receive same data
+```
+
+#### **Cache Hit Scenarios**
+
+```typescript
+// First request (cache miss - goes to network)
+const request1 = fetchf('/api/data', { cacheTime: 300, dedupeTime: 5000 });
+
+// After 2 seconds - cache hit (no deduplication needed)
+setTimeout(() => {
+ const request2 = fetchf('/api/data', { cacheTime: 300, dedupeTime: 5000 });
+ // Returns cached data immediately, no network request
+}, 2000);
+
+// After 10 minutes - cache expired, new request
+setTimeout(() => {
+ const request3 = fetchf('/api/data', { cacheTime: 300, dedupeTime: 5000 });
+ // Cache expired â new network request â potential for deduplication again
+}, 600000);
+```
+
+#### **Deduplication Window vs Cache Time**
+
+- **`dedupeTime`**: Prevents duplicate requests during a short time window (milliseconds)
+- **`cacheTime`**: Stores successful responses for longer periods (seconds)
+- **Integration**: Deduplication handles concurrent requests, caching handles subsequent requests
+
+```typescript
+const config = {
+ dedupeTime: 2000, // 2 seconds - for rapid concurrent requests
+ cacheTime: 300, // 5 minutes - for longer-term storage
+};
+
+// Timeline example:
+// T+0ms: Request A initiated â network call starts
+// T+500ms: Request B initiated â joins Request A (deduplication)
+// T+1500ms: Request C initiated â joins Request A (deduplication)
+// T+2500ms: Request D initiated â deduplication window expired, but cache hit!
+// T+6000ms: Request E initiated â cache hit (no network call needed)
+```
+
+### â° Understanding staleTime vs cacheTime
+
+The relationship between `staleTime` and `cacheTime` enables sophisticated data freshness strategies:
+
+#### **Cache States and Timing**
+
+```typescript
+const fetchWithTimings = fetchf('/api/user-feed', {
+ cacheTime: 600, // Cache for 10 minutes
+ staleTime: 60, // Consider fresh for 1 minute
+});
+
+// Data lifecycle:
+// T+0: Fresh data - served from cache, no background request
+// T+30s: Still fresh - served from cache, no background request
+// T+90s: Stale but cached - served from cache + background revalidation
+// T+300s: Still stale - served from cache + background revalidation
+// T+650s: Cache expired - network request required, shows loading state
+```
+
+#### **Practical Combinations**
+
+**High-Frequency Updates (Real-time Data)**
+
+```typescript
+const realtimeData = {
+ cacheTime: 30, // Cache for 30 seconds
+ staleTime: 5, // Fresh for 5 seconds only
+ // Result: Frequent background updates, always responsive UI
+};
+```
+
+**Balanced Performance (User Data)**
+
+```typescript
+const userData = {
+ cacheTime: 300, // Cache for 5 minutes
+ staleTime: 60, // Fresh for 1 minute
+ // Result: Good performance + reasonable freshness
+};
+```
+
+**Static Content (Configuration)**
+
+```typescript
+const staticConfig = {
+ cacheTime: 3600, // Cache for 1 hour
+ staleTime: 1800, // Fresh for 30 minutes
+ // Result: Minimal network usage for rarely changing data
+};
+```
+
+#### **Background Revalidation Behavior**
+
+```typescript
+// When staleTime expires but cacheTime hasn't:
+const { data } = await fetchf('/api/notifications', {
+ cacheTime: 600, // 10 minutes total cache
+ staleTime: 120, // 2 minutes of "freshness"
+});
+
+// T+0: Returns cached data immediately, no background request
+// T+150s: Returns cached data immediately + triggers background request
+// T+150s: Background request completes â cache silently updated
+// T+650s: Cache expired â full loading state + network request
+```
+
+### Auto-Generated Cache Key Properties
+
+By default, `fetchff` generates a cache key automatically using a combination of the following request properties:
+
+| Property | Description | Default Value |
+| ----------------- | ----------------------------------------------------------------------------------------- | --------------- |
+| `method` | The HTTP method used for the request (e.g., GET, POST). | `'GET'` |
+| `url` | The full request URL, including the base URL and endpoint path. | `''` |
+| `headers` | Request headers, **filtered to include only cache-relevant headers** (see below). | |
+| `body` | The request payload (for POST, PUT, PATCH, etc.), stringified if it's an object or array. | |
+| `credentials` | Indicates whether credentials (cookies) are included in the request. | `'same-origin'` |
+| `params` | Query parameters serialized into the URL (objects, arrays, etc. are stringified). | |
+| `urlPathParams` | Dynamic URL path parameters (e.g., `/user/:id`), stringified and encoded. | |
+| `withCredentials` | Whether credentials (cookies) are included in the request. | |
+
+#### Header Filtering for Cache Keys
+
+To ensure stable cache keys and prevent unnecessary cache misses, `fetchff` only includes headers that affect response content in cache key generation. The following headers are included:
+
+**Content Negotiation:**
+
+- `accept` - Affects response format (JSON, HTML, etc.)
+- `accept-language` - Affects localization of response
+- `accept-encoding` - Affects response compression
+
+**Authentication & Authorization:**
+
+- `authorization` - Affects access to protected resources
+- `x-api-key` - Token-based access control
+- `cookie` - Session-based authentication
+
+**Request Context:**
+
+- `content-type` - Affects how request body is interpreted
+- `origin` - Relevant for CORS or tenant-specific APIs
+- `referer` - May influence API behavior
+- `user-agent` - Only if server returns client-specific content
+
+**Custom Headers:**
+
+- `x-requested-with` - Distinguishes AJAX requests
+- `x-client-id` - Per-client/partner identity
+- `x-tenant-id` - Multi-tenant segmentation
+- `x-user-id` - Explicit user context
+- `x-app-version` - Version-specific behavior
+- `x-feature-flag` - Feature rollout controls
+- `x-device-id` - Device-specific responses
+- `x-platform` - Platform-specific content (iOS, Android, web)
+- `x-session-id` - Session-specific responses
+- `x-locale` - Locale-specific content
+
+Headers like `user-agent`, `accept-encoding`, `connection`, `cache-control`, tracking IDs, and proxy-related headers are **excluded** from cache key generation as they don't affect the actual response content.
+
+These properties are combined and hashed to create a unique cache key for each request. This ensures that requests with different parameters, bodies, or cache-relevant headers are cached separately while maintaining stable cache keys across requests that only differ in non-essential headers. If that does not suffice, you can always use `cacheKey` (string | function) and supply it to particular requests. You can also build your own `cacheKey` function and simply update defaults to reflect it in all requests. Auto key generation would be entirely skipped in such scenarios.
@@ -619,7 +1554,7 @@ The following options are available for configuring polling in the `RequestHandl
- **`maxPollingAttempts`**:
Type: `number`
- Maximum number of polling attempts before stopping. Set to `< 1` for unlimited attempts.
+ Maximum number of polling attempts before stopping. Set to `0` or negative number for unlimited attempts.
_Default:_ `0` (unlimited).
- **`shouldStopPolling`**:
@@ -659,8 +1594,8 @@ The retry mechanism can be used to handle transient errors and improve the relia
const { data } = await fetchf('https://api.example.com/', {
retry: {
retries: 5,
- delay: 100,
- maxDelay: 5000,
+ delay: 100, // Override default adaptive delay (normally 1s/2s based on connection)
+ maxDelay: 5000, // Override default adaptive maxDelay (normally 30s/60s based on connection)
resetTimeout: true, // Resets the timeout for each retry attempt
backoff: 1.5,
retryOn: [500, 503],
@@ -687,6 +1622,11 @@ const { data } = await fetchf('https://api.example.com/', {
In this example, the request will retry only on HTTP status codes 500 and 503, as specified in the `retryOn` array. The `resetTimeout` option ensures that the timeout is restarted for each retry attempt. The custom `shouldRetry` function adds further logic: if the server response contains `{"bookId": "none"}`, a retry is forced. Otherwise, the request will retry only if the current attempt number is less than 3. Although the `retries` option is set to 5, the `shouldRetry` function limits the maximum attempts to 3 (the initial request plus 2 retries).
+**Note:** When not overridden, `fetchff` automatically adapts retry delays based on connection speed:
+
+- **Normal connections**: 1s initial delay, 30s max delay
+- **Slow connections (2G/3G)**: 2s initial delay, 60s max delay
+
Additionally, you can handle "Not Found" (404) responses or other specific status codes in your retry logic. For example, you might want to retry when the status text is "Not Found":
```typescript
@@ -696,6 +1636,8 @@ shouldRetry(response, attempt) {
return true;
}
// ...other logic
+
+ return null; // Fallback to `retryOn` status code check
}
```
@@ -714,13 +1656,13 @@ The retry mechanism is configured via the `retry` option when instantiating the
- **`delay`**:
Type: `number`
- Initial delay (in milliseconds) before the first retry attempt. Subsequent retries use an exponentially increasing delay based on the `backoff` parameter.
- _Default:_ `1000` (1 second).
+ Initial delay (in milliseconds) before the first retry attempt. **Default is adaptive**: 1 second (1000 ms) for normal connections, 2 seconds (2000 ms) on slow connections (2G/3G). Subsequent retries use an exponentially increasing delay based on the `backoff` parameter.
+ _Default:_ `1000` / `2000` (adaptive based on connection speed).
- **`maxDelay`**:
Type: `number`
- Maximum delay (in milliseconds) between retry attempts. The delay will not exceed this value, even if the exponential backoff would suggest a longer delay.
- _Default:_ `30000` (30 seconds).
+ Maximum delay (in milliseconds) between retry attempts. **Default is adaptive**: 30 seconds (30000 ms) for normal connections, 60 seconds (60000 ms) on slow connections (2G/3G). The delay will not exceed this value, even if the exponential backoff would suggest a longer delay.
+ _Default:_ `30000` / `60000` (adaptive based on connection speed).
- **`backoff`**:
Type: `number`
@@ -735,7 +1677,6 @@ The retry mechanism is configured via the `retry` option when instantiating the
- **`retryOn`**:
Type: `number[]`
Array of HTTP status codes that should trigger a retry. By default, retries are triggered for the following status codes:
-
- `408` - Request Timeout
- `409` - Conflict
- `425` - Too Early
@@ -745,17 +1686,18 @@ The retry mechanism is configured via the `retry` option when instantiating the
- `503` - Service Unavailable
- `504` - Gateway Timeout
-- **`shouldRetry(response, currentAttempt)`**:
- Type: `RetryFunction`
- Function that determines whether a retry should be attempted based on the error from response object (accessed by: response.error ), and the current attempt number. This function receives the error object and the attempt number as arguments.
- _Default:_ Retry up to the number of specified attempts.
+If used in conjunction with `shouldRetry`, the `shouldRetry` function takes priority, and falls back to `retryOn` only if it returns `null`.
+
+- **`shouldRetry(response: FetchResponse, currentAttempt: Number) => boolean`**:
+ Type: `RetryFunction`
+ Function that determines whether a retry should be attempted based on the error or successful response (if `shouldRetry` is provided) object, and the current attempt number. This function receives the error object and the attempt number as arguments. The boolean returned indicates decision. If `true` then it should retry, if `false` then abort and don't retry, if `null` then fallback to `retryOn` status codes check.
+ _Default:_ `undefined`.
### How It Works
1. **Initial Request**: When a request fails, the retry mechanism captures the failure and checks if it should retry based on the `retryOn` configuration and the result of the `shouldRetry` function.
2. **Retry Attempts**: If a retry is warranted:
-
- The request is retried up to the specified number of attempts (`retries`).
- Each retry waits for a delay before making the next attempt. The delay starts at the initial `delay` value and increases exponentially based on the `backoff` factor, but will not exceed the `maxDelay`.
- If `resetTimeout` is enabled, the timeout is reset for each retry attempt.
@@ -839,16 +1781,16 @@ You can use the `onResponse` interceptor to customize how the response is handle
```typescript
interface FetchResponse<
ResponseData = any,
+ RequestBody = any,
QueryParams = any,
PathParams = any,
- RequestBody = any,
> extends Response {
data: ResponseData | null; // The parsed response data, or null/defaultResponse if unavailable
error: ResponseError<
ResponseData,
+ RequestBody,
QueryParams,
- PathParams,
- RequestBody
+ PathParams
> | null; // Error details if the request failed, otherwise null
config: RequestConfig; // The configuration used for the request
status: number; // HTTP status code
@@ -879,7 +1821,7 @@ The whole response of the native `fetch()` is attached as well.
Error object in `error` looks as follows:
-- **Type**: `ResponseError | null`
+- **Type**: `ResponseError | null`
- An object with details about any error that occurred or `null` otherwise.
- **`name`**: The name of the error, that is `ResponseError`.
@@ -889,6 +1831,7 @@ Error object in `error` looks as follows:
- **`request`**: Details about the HTTP request that was sent (e.g., URL, method, headers).
- **`config`**: The configuration object used for the request, including URL, method, headers, and query parameters.
- **`response`**: The full response object received from the server, including all headers and body.
+- **`isCancelled`**: A boolean property on the error object indicating whether the request was cancelled before completion
@@ -912,12 +1855,24 @@ The native `fetch()` API function doesn't throw exceptions for HTTP errors like
Promises are rejected, and global error handling is triggered. You must use `try/catch` blocks to handle errors.
```typescript
+import { fetchf } from 'fetchff';
+
try {
- const { data } = await fetchf('https://api.example.com/', {
- strategy: 'reject', // It is default so it does not really needs to be specified
+ const { data } = await fetchf('https://api.example.com/users', {
+ strategy: 'reject', // Default strategy - can be omitted
+ timeout: 5000,
});
+
+ console.log('Users fetched successfully:', data);
} catch (error) {
- console.error(error.status, error.statusText, error.response, error.config);
+ // Handle specific error types
+ if (error.status === 404) {
+ console.error('API endpoint not found');
+ } else if (error.status >= 500) {
+ console.error('Server error:', error.statusText);
+ } else {
+ console.error('Request failed:', error.message);
+ }
}
```
@@ -929,12 +1884,29 @@ try {
> You must always check the error property in the response object to detect and handle errors.
```typescript
-const { data, error } = await fetchf('https://api.example.com/', {
+import { fetchf } from 'fetchff';
+
+const { data, error } = await fetchf('https://api.example.com/users', {
strategy: 'softFail',
+ timeout: 5000,
});
if (error) {
- console.error(error.status, error.statusText, error.response, error.config);
+ // Handle errors without try/catch
+ console.error('Request failed:', {
+ status: error.status,
+ message: error.message,
+ url: error.config?.url,
+ });
+
+ // Show user-friendly error message
+ if (error.status === 429) {
+ console.log('Rate limited. Please try again later.');
+ } else if (error.status >= 500) {
+ console.log('Server temporarily unavailable. Please try again.');
+ }
+} else {
+ console.log('Users fetched successfully:', data);
}
```
@@ -948,14 +1920,31 @@ Check `Response Object` section below to see how `error` object is structured.
> You must always check the error property in the response object to detect and handle errors.
```typescript
-const { data, error } = await fetchf('https://api.example.com/', {
- strategy: 'defaultResponse',
- defaultResponse: {},
-});
+import { fetchf } from 'fetchff';
+
+const { data, error } = await fetchf(
+ 'https://api.example.com/user-preferences',
+ {
+ strategy: 'defaultResponse',
+ defaultResponse: {
+ theme: 'light',
+ language: 'en',
+ notifications: true,
+ },
+ timeout: 5000,
+ },
+);
if (error) {
- console.error('Request failed', data); // "data" will be equal to {} if there is an error
+ console.warn('Failed to load user preferences, using defaults:', data);
+ // Log error for debugging but continue with default values
+ console.error('Preferences API error:', error.message);
+} else {
+ console.log('User preferences loaded:', data);
}
+
+// Safe to use data regardless of error state
+document.body.className = data.theme;
```
**`silent`**:
@@ -994,6 +1983,209 @@ myLoadingProcess();
5. **Custom Error Handling**:
Depending on the strategy chosen, you can tailor how errors are managed, either by handling them directly within response objects, using default responses, or managing them silently.
+### đ¯ Choosing the Right Error Strategy
+
+Understanding when to use each error handling strategy is crucial for building robust applications:
+
+#### **`reject` Strategy - Traditional Error Handling**
+
+**When to Use:**
+
+- Building applications with established error boundaries
+- Need consistent error propagation through promise chains
+- Integration with existing try/catch error handling patterns
+- Critical operations where failures must be explicitly handled
+
+**Best For:**
+
+```typescript
+// API calls where failure must stop execution
+try {
+ const { data } = await fetchf('/api/payment/process', {
+ method: 'POST',
+ body: paymentData,
+ strategy: 'reject', // Default - can be omitted
+ });
+
+ // Only proceed if payment succeeded
+ await processOrderCompletion(data);
+} catch (error) {
+ // Handle payment failure explicitly
+ showPaymentErrorModal(error.message);
+ revertOrderState();
+}
+```
+
+#### **`softFail` Strategy - Graceful Error Handling**
+
+**When to Use:**
+
+- Building user-friendly interfaces that degrade gracefully
+- Multiple API calls where some failures are acceptable
+- React/Vue components that need to handle loading/error states
+- Data fetching where partial failures shouldn't break the UI
+
+**Best For:**
+
+```typescript
+// Dashboard with multiple data sources
+const { data: userStats, error: statsError } = await fetchf('/api/user/stats', {
+ strategy: 'softFail',
+});
+const { data: notifications, error: notifError } = await fetchf('/api/notifications', {
+ strategy: 'softFail',
+});
+
+// Render what we can, gracefully handle what failed
+return (
+
+ {userStats && }
+ {statsError && Stats temporarily unavailable }
+
+ {notifications && }
+ {notifError && Notifications unavailable }
+
+);
+```
+
+#### **`defaultResponse` Strategy - Fallback Values**
+
+**When to Use:**
+
+- Optional features that should work even when API fails
+- Configuration or preferences that have sensible defaults
+- Non-critical data that can fall back to static values
+- Progressive enhancement scenarios
+
+**Best For:**
+
+```typescript
+// User preferences with fallbacks
+const { data: preferences } = await fetchf('/api/user/preferences', {
+ strategy: 'defaultResponse',
+ defaultResponse: {
+ theme: 'light',
+ language: 'en',
+ notifications: true,
+ autoSave: false,
+ },
+});
+
+// Safe to use preferences regardless of API status
+applyTheme(preferences.theme);
+setLanguage(preferences.language);
+```
+
+#### **`silent` Strategy - Fire-and-Forget**
+
+**When to Use:**
+
+- Analytics and telemetry data
+- Non-critical background operations
+- Optional data prefetching
+- Logging and monitoring calls
+
+**Best For:**
+
+```typescript
+// Analytics tracking (don't let failures affect user experience)
+const trackUserAction = (action: string, data: any) => {
+ fetchf('/api/analytics/track', {
+ method: 'POST',
+ body: { action, data, timestamp: Date.now() },
+ strategy: 'silent',
+ onError(error) {
+ // Log error for debugging, but don't disrupt user flow
+ console.warn('Analytics tracking failed:', error.message);
+ },
+ });
+
+ // This function never throws, never shows loading states
+ // User interaction continues uninterrupted
+};
+
+// Background data prefetching
+const prefetchNextPage = () => {
+ fetchf('/api/articles/page/2', {
+ strategy: 'silent',
+ cacheTime: 300, // Cache for later use
+ });
+ // No need to await or handle response
+};
+```
+
+### đ Performance Strategy Matrix
+
+Choose strategies based on your application's needs:
+
+| Use Case | Strategy | Benefits | Trade-offs |
+| ----------------------- | ----------------- | ------------------------------------------------- | --------------------------------------- |
+| **Critical Operations** | `reject` | Explicit error handling, prevents data corruption | Requires try/catch, can break user flow |
+| **UI Components** | `softFail` | Graceful degradation, better UX | Need to check error property |
+| **Optional Features** | `defaultResponse` | Always provides usable data | May mask real issues |
+| **Background Tasks** | `silent` | Never disrupts user experience | Errors may go unnoticed |
+
+### đ§ Advanced Strategy Patterns
+
+#### **Hybrid Error Handling**
+
+```typescript
+// Combine strategies for optimal UX
+const fetchUserDashboard = async (userId: string) => {
+ // Critical user data - must succeed
+ const { data: userData } = await fetchf(`/api/users/${userId}`, {
+ strategy: 'reject',
+ });
+
+ // Optional widgets - graceful degradation
+ const { data: stats, error: statsError } = await fetchf(
+ `/api/users/${userId}/stats`,
+ {
+ strategy: 'softFail',
+ },
+ );
+
+ // Preferences with fallbacks
+ const { data: preferences } = await fetchf(
+ `/api/users/${userId}/preferences`,
+ {
+ strategy: 'defaultResponse',
+ defaultResponse: DEFAULT_USER_PREFERENCES,
+ },
+ );
+
+ // Background analytics - fire and forget
+ fetchf('/api/analytics/dashboard-view', {
+ method: 'POST',
+ body: { userId, timestamp: Date.now() },
+ strategy: 'silent',
+ });
+
+ return { userData, stats, statsError, preferences };
+};
+```
+
+#### **Progressive Enhancement**
+
+```typescript
+// Start with defaults, enhance with API data
+const enhanceWithApiData = async () => {
+ // Immediate render with defaults
+ let config = DEFAULT_APP_CONFIG;
+ renderApp(config);
+
+ // Enhance with API data when available
+ const { data: apiConfig } = await fetchf('/api/config', {
+ strategy: 'defaultResponse',
+ defaultResponse: DEFAULT_APP_CONFIG,
+ });
+
+ // Re-render with enhanced config
+ config = { ...config, ...apiConfig };
+ renderApp(config);
+};
+```
+
#### `onError`
The `onError` option can be configured to intercept errors:
@@ -1051,167 +2243,548 @@ if (error) {
The `fetchff` package provides comprehensive TypeScript typings to enhance development experience and ensure type safety. Below are details on the available, exportable types for both `createApiFetcher()` and `fetchf()`.
-### Generic Typings
+### Typings for `fetchf()`
+
+The `fetchf()` function includes types that help configure and manage network requests effectively:
+
+```typescript
+interface AddBookRequest {
+ response: AddBookResponseData;
+ params: AddBookQueryParams;
+ urlPathParams: AddBookPathParams;
+ body: AddBookRequestBody;
+}
+
+// You could also use: fetchf> as a shorthand so not to create additional request interface
+const { data: book } = await fetchf('/api/add-book', {
+ method: 'POST',
+});
+// Your book is of type AddBookResponseData
+```
+
+- **`Req`**: Represents a shorter 4-generics version of request object type for endpoints, allowing you to compose the shape of the request payload, query parameters, and path parameters for each request using a couple inline generics e.g. `fetchf()`. While there is no plan for deprecation, this is for compatibility with older versions only. Aim to use the new method with single generic presented above instead. We don't use overload here to keep it all fast and snappy.
+- **`RequestConfig`**: Main configuration options for the `fetchf()` function, including request settings, interceptors, and retry configurations.
+- **`RetryConfig`**: Configuration options for retry mechanisms, including the number of retries, delay between retries, and backoff strategies.
+- **`CacheConfig`**: Configuration options for caching, including cache time, custom cache keys, and cache invalidation rules.
+- **`PollingConfig`**: Configuration options for polling, including polling intervals and conditions to stop polling.
+- **`ErrorStrategy`**: Defines strategies for handling errors, such as rejection, soft fail, default response, and silent modes.
+
+For a complete list of types and their definitions, refer to the [request-handler.ts](https://github.com/MattCCC/fetchff/blob/master/src/types/request-handler.ts) file.
+
+### Typings for `createApiFetcher()`
+
+The `createApiFetcher()` function provides a robust set of types to define and manage API interactions.
+
+- **`EndpointTypes`**: Represents the list of API endpoints with their respective settings. It is your own interface that you can pass to this generic. It will be cross-checked against the `endpoints` object in your `createApiFetcher()` configuration. Each endpoint can be configured with its own specific types such as Response Data Structure, Query Parameters, URL Path Parameters or Request Body. Example:
+
+```typescript
+interface EndpointTypes {
+ fetchBook: Endpoint<{
+ response: Book;
+ params: BookQueryParams;
+ urlPathParams: BookPathParams;
+ }>;
+ // or shorter version: fetchBook: EndpointReq;
+ addBook: Endpoint<{
+ response: Book;
+ body: BookBody;
+ params: BookQueryParams;
+ urlPathParams: BookPathParams;
+ }>;
+ // or shorter version: fetchBook: EndpointReq;
+ someOtherEndpoint: Endpoint; // The generic is fully optional but it must be defined for endpoint not to output error
+}
+
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
+ endpoints: {
+ fetchBook: {
+ url: '/get-book',
+ },
+ addBook: {
+ url: '/add-book',
+ method: 'POST',
+ },
+ },
+});
+
+const { data: book } = await api.addBook();
+// book will be of type Book
+```
+
+
+
+- **`Endpoint<{response: ResponseData, params: QueryParams, urlPathParams: PathParams, body: RequestBody}>`**: Represents an API endpoint function, allowing to be defined with optional response data (default `DefaultResponse`), query parameters (default `QueryParams`), URL path parameters (default `DefaultUrlParams`), and request body (default `DefaultPayload`).
+- **`RequestInterceptor`**: Function to modify request configurations before they are sent.
+- **`ResponseInterceptor`**: Function to process responses before they are handled by the application.
+- **`ErrorInterceptor`**: Function to handle errors when a request fails.
+- **`CustomFetcher`**: Represents the custom `fetcher` function.
+
+For a full list of types and detailed definitions, refer to the [api-handler.ts](https://github.com/MattCCC/fetchff/blob/master/src/types/api-handler.ts) file.
+
+### Generic Typings
+
+The `fetchff` package includes several generic types to handle various aspects of API requests and responses:
+
+- **`QueryParams`**: Represents query parameters for requests. Can be an object, `URLSearchParams`, an array of name-value pairs, or `null`.
+- **`BodyPayload`**: Represents the request body. Can be `BodyInit`, an object, an array, a string, or `null`.
+- **`UrlPathParams`**: Represents URL path parameters. Can be an object or `null`.
+- **`DefaultResponse`**: Default response for all requests. Default is: `any`.
+
+### Benefits of Using Typings
+
+- **Type Safety**: Ensures that configurations and requests adhere to expected formats, reducing runtime errors and improving reliability.
+- **Autocompletion**: Provides better support for autocompletion in editors, making development faster and more intuitive.
+- **Documentation**: Helps in understanding available options and their expected values, improving code clarity and maintainability.
+
+
+
+## đ Sanitization
+
+
+ Click to expand
+
+
+FetchFF includes robust built-in sanitization mechanisms that protect your application from common security vulnerabilities. These safeguards are automatically applied without requiring any additional configuration.
+
+### Prototype Pollution Prevention
+
+The library implements automatic protection against prototype pollution attacks by:
+
+- Removing dangerous properties like `__proto__`, `constructor`, and `prototype` from objects
+- Sanitizing all user-provided data before processing it
+
+```typescript
+// Example of protection against prototype pollution
+const userInput = {
+ id: 123,
+ __proto__: { malicious: true },
+};
+
+// The sanitization happens automatically
+const response = await fetchf('/api/users', {
+ params: userInput, // The __proto__ property will be removed
+});
+```
+
+### Input Sanitization Features
+
+1. **Object Sanitization**
+ - All incoming objects are sanitized via the `sanitizeObject` utility
+ - Creates shallow copies of input objects with dangerous properties removed
+ - Applied automatically to request configurations, headers, and other objects
+
+2. **URL Parameter Safety**
+ - Path parameters are properly encoded using `encodeURIComponent`
+ - Query parameters are safely serialized and encoded
+ - Prevents URL injection attacks and ensures valid URL formatting
+
+3. **Data Validation**
+ - Checks for JSON serializability of request bodies
+ - Detects circular references that could cause issues
+ - Properly handles different data types (strings, arrays, objects, etc.)
+
+4. **Depth Control**
+ - Prevents excessive recursion with depth limitations
+ - Mitigates stack overflow attacks through query parameter manipulation
+ - Maximum depth is controlled by `MAX_DEPTH` constant (default: 10)
+
+### Implementation Details
+
+The sanitization process is applied at multiple levels:
+
+- During request configuration building
+- When processing URL path parameters
+- When serializing query parameters
+- When handling request and response interceptors
+- During retry and polling operations
+
+This multi-layered approach ensures that all data passing through the library is properly sanitized, significantly reducing the risk of injection attacks and other security vulnerabilities.
+
+```typescript
+// Example of safe URL path parameter handling
+const { data } = await api.getUser({
+ urlPathParams: {
+ id: 'user-id with spaces & special chars',
+ },
+ // Automatically encoded to: /users/user-id%20with%20spaces%20%26%20special%20chars
+});
+```
+
+Security is a core design principle of FetchFF, with sanitization mechanisms running automatically to provide protection without adding complexity to your code.
+
+
+
+## âī¸ React Integration
+
+
+ Click to expand
+
+
+FetchFF offers a high-performance React hook, `useFetcher(url, config)`, for efficient data fetching in React applications. This hook provides built-in caching, automatic request deduplication, comprehensive state management etc. Its API mirrors the native `fetch` and `fetchf(url, config)` signatures, allowing you to pass all standard and advanced configuration options seamlessly. Designed with React best practices in mind, `useFetcher` ensures optimal performance and a smooth developer experience.
+
+### Basic Usage
+
+```tsx
+import { useFetcher } from 'fetchff/react';
+
+function UserProfile({ userId }: { userId: string }) {
+ const { data, error, isLoading, refetch } = useFetcher(
+ `/api/users/${userId}`,
+ );
+
+ if (isLoading) return Loading...
;
+ if (error) return Error: {error.message}
;
+
+ return (
+
+
{data.name}
+ Refresh
+
+ );
+}
+```
+
+### Hook API
+
+The `useFetcher(url, config)` hook returns an object with the following properties:
+
+- **`data: ResponseData | null`**
+ The fetched data, typed as `T` (generic), or `null` if not available.
+- **`error: ResponseError | null`**
+ Error object if the request failed, otherwise `null`.
+- **`isLoading: boolean`**
+ `true` while data is being loaded for the first time or during a fetch.
+- **`isFetching: boolean`**
+ `true` when currently fetching (fetch is in progress).
+- **`config: RequestConfig`**
+ The configuration object used for the request.
+- **`headers: Record`**
+ Response headers from the last successful request.
+- **`refetch: (forceRefresh: boolean = true) => Promise | null>`**
+ Function to manually trigger a new request. It always uses `softFail` strategy and returns a new FetchResponse object. The `forceRefresh` is set to `true` by default - it will bypass cache and force new request and cache refresh.
+- **`mutate: (data: ResponseData, settings: MutationSettings) => Promise | null>`**
+ Function to update cached data directly, by passing new data. The `settings` object contains currently `revalidate` (boolean) property. If set to `true`, a new request will be made after cache and component data are updated.
+
+### Configuration Options
+
+All standard FetchFF options are supported, plus React-specific features:
+
+```tsx
+const { data, error, isLoading } = useFetcher('/api/data', {
+ // Cache for 5 minutes
+ cacheTime: 300,
+
+ // Deduplicate requests within 2 seconds
+ dedupeTime: 2000,
+
+ // Revalidate when window regains focus
+ refetchOnFocus: true,
+
+ // Don't fetch immediately (useful for POST requests; React specific)
+ immediate: false,
+
+ // Custom error handling
+ strategy: 'softFail',
+
+ // Request configuration
+ method: 'POST',
+ body: { name: 'John' },
+ headers: { Authorization: 'Bearer token' },
+});
+```
+
+> **Note on `immediate` behavior**: By default, only GET and HEAD requests (RFC 7231 safe methods) trigger automatically when the component mounts. Other HTTP methods like POST, PUT, DELETE require either setting `immediate: true` explicitly or calling `refetch()` manually. This prevents unintended side effects from automatic execution of non-safe HTTP operations.
+
+### Conditional Requests
+
+Only fetch when conditions are met (`immediate` option is `true`):
+
+```tsx
+function ConditionalData({
+ shouldFetch,
+ userId,
+}: {
+ shouldFetch: boolean;
+ userId?: string;
+}) {
+ const { data, isLoading } = useFetcher(`/api/users/${userId}`, {
+ immediate: shouldFetch && !!userId,
+ });
+
+ // Will only fetch when shouldFetch is true and userId exists
+ return {data ? data.name : 'No data'}
;
+}
+```
+
+You can also pass `null` as the URL to conditionally skip a request:
+
+```tsx
+function ConditionalData({
+ shouldFetch,
+ userId,
+}: {
+ shouldFetch: boolean;
+ userId?: string;
+}) {
+ const { data, isLoading } = useFetcher(
+ shouldFetch && userId ? `/api/users/${userId}` : null,
+ );
+
+ // Will only fetch when shouldFetch is true and userId exists
+ return {data ? data.name : 'No data'}
;
+}
+```
+
+> **Note:** Passing `null` as the URL to conditionally skip a request is a legacy/deprecated approach (commonly used in SWR plugin). For new code, prefer using the `immediate` option for conditional fetching. The `null` URL method is still supported for backwards compatibility.
+
+### Dynamic URLs and Parameters
+
+```tsx
+function SearchResults({ query }: { query: string }) {
+ const { data, isLoading } = useFetcher('/api/search', {
+ params: { q: query, limit: 10 },
+ // Only fetch when query exists
+ immediate: !!query,
+ });
-The `fetchff` package includes several generic types to handle various aspects of API requests and responses:
+ return (
+
+ {isLoading &&
Searching...
}
+ {data?.results?.map((item) => (
+
{item.title}
+ ))}
+
+ );
+}
+```
-- **`QueryParams`**: Represents query parameters for requests. Can be an object, `URLSearchParams`, an array of name-value pairs, or `null`.
-- **`BodyPayload`**: Represents the request body. Can be `BodyInit`, an object, an array, a string, or `null`.
-- **`UrlPathParams`**: Represents URL path parameters. Can be an object or `null`.
-- **`DefaultResponse`**: Default response for all requests. Default is: `any`.
+### Mutations and Cache Updates
-### Typings for `createApiFetcher()`
+```tsx
+function TodoList() {
+ const { data: todos, mutate, refetch } = useFetcher('/api/todos');
+
+ const addTodo = async (text: string) => {
+ // Optimistically update the cache
+ const newTodo = { id: Date.now(), text, completed: false };
+ mutate([...todos, newTodo]);
+
+ try {
+ // Make the actual request
+ await fetchf('/api/todos', {
+ method: 'POST',
+ body: { text },
+ });
+
+ // Revalidate to get the real data
+ refetch();
+ } catch (error) {
+ // Revert on error
+ mutate(todos);
+ }
+ };
-The `createApiFetcher()` function provides a robust set of types to define and manage API interactions.
+ return (
+
+ {todos?.map((todo) => (
+
{todo.text}
+ ))}
+
addTodo('New todo')}>Add Todo
+
+ );
+}
+```
-The key types are:
+### Error Handling
-- **`EndpointsMethods`**: Represents the list of API endpoints with their respective settings. It is your own interface that you can pass to this generic. It will be cross-checked against the `endpoints` object in your `createApiFetcher()` configuration. Each endpoint can be configured with its own specific settings such as Response Payload, Query Parameters and URL Path Parameters.
-- **`Endpoint`**: Represents an API endpoint function, allowing to be defined with optional query parameters, URL path parameters, request configuration (settings), and request body (data).
-- **`EndpointsSettings`**: Configuration for API endpoints, including query parameters, URL path parameters, and additional request configurations. Default is `typeof endpoints`.
-- **`RequestInterceptor`**: Function to modify request configurations before they are sent.
-- **`ResponseInterceptor`**: Function to process responses before they are handled by the application.
-- **`ErrorInterceptor`**: Function to handle errors when a request fails.
-- **`CreatedCustomFetcherInstance`**: Represents the custom `fetcher` instance created by its `create()` function.
+```tsx
+function DataWithErrorHandling() {
+ const { data, error, isLoading, refetch } = useFetcher('/api/data', {
+ retry: {
+ retries: 3,
+ delay: 1000,
+ backoff: 1.5,
+ },
+ });
-For a full list of types and detailed definitions, refer to the [api-handler.ts](https://github.com/MattCCC/fetchff/blob/docs-update/src/types/api-handler.ts) file.
+ if (isLoading) return Loading...
;
-### Typings for `fetchf()`
+ if (error) {
+ return (
+
+
Error: {error.message}
+
Try Again
+
+ );
+ }
-The `fetchf()` function includes types that help configure and manage network requests effectively:
+ return {JSON.stringify(data)}
;
+}
+```
-- **`RequestHandlerConfig`**: Main configuration options for the `fetchf()` function, including request settings, interceptors, and retry configurations.
-- **`RetryConfig`**: Configuration options for retry mechanisms, including the number of retries, delay between retries, and backoff strategies.
-- **`CacheConfig`**: Configuration options for caching, including cache time, custom cache keys, and cache invalidation rules.
-- **`PollingConfig`**: Configuration options for polling, including polling intervals and conditions to stop polling.
-- **`ErrorStrategy`**: Defines strategies for handling errors, such as rejection, soft fail, default response, and silent modes.
+### Suspense Support
-For a complete list of types and their definitions, refer to the [request-handler.ts](https://github.com/MattCCC/fetchff/blob/docs-update/src/types/request-handler.ts) file.
+Use with React Suspense for declarative loading states:
-### Benefits of Using Typings
+```tsx
+import { Suspense } from 'react';
-- **Type Safety**: Ensures that configurations and requests adhere to expected formats, reducing runtime errors and improving reliability.
-- **Autocompletion**: Provides better support for autocompletion in editors, making development faster and more intuitive.
-- **Documentation**: Helps in understanding available options and their expected values, improving code clarity and maintainability.
+function DataComponent() {
+ const { data } = useFetcher('/api/data', {
+ strategy: 'reject', // Required for Suspense
+ });
-
+ return {data.title}
;
+}
-## đ Sanitization
+function App() {
+ return (
+ Loading...}>
+
+
+ );
+}
+```
-
- Click to expand
-
+### TypeScript Support
-FetchFF includes robust built-in sanitization mechanisms that protect your application from common security vulnerabilities. These safeguards are automatically applied without requiring any additional configuration.
+Full TypeScript support with automatic type inference:
-### Prototype Pollution Prevention
+```tsx
+interface User {
+ id: number;
+ name: string;
+ email: string;
+}
-The library implements automatic protection against prototype pollution attacks by:
+interface UserParams {
+ include?: string[];
+}
-- Removing dangerous properties like `__proto__`, `constructor`, and `prototype` from objects
-- Sanitizing all user-provided data before processing it
+function UserComponent({ userId }: { userId: string }) {
+ const { data, error } = useFetcher(`/api/users/${userId}`, {
+ params: { include: ['profile', 'settings'] } as UserParams,
+ });
-```typescript
-// Example of protection against prototype pollution
-const userInput = {
- id: 123,
- __proto__: { malicious: true },
-};
+ // data is automatically typed as User | null
+ // error is typed as ResponseError | null
-// The sanitization happens automatically
-const response = await fetchf('/api/users', {
- params: userInput, // The __proto__ property will be removed
-});
+ return {data?.name}
;
+}
```
-### Input Sanitization Features
+### Performance Features
-1. **Object Sanitization**
+- **Automatic deduplication**: Multiple components requesting the same data share a single request
+- **Smart caching**: Configurable cache with automatic invalidation
+- **Minimal re-renders**: Optimized to prevent unnecessary component updates (relies on native React functionality)
+- **Background revalidation**: Keep data fresh without blocking the UI (use `staleTime` setting to control the time)
- - All incoming objects are sanitized via the `sanitizeObject` utility
- - Creates shallow copies of input objects with dangerous properties removed
- - Applied automatically to request configurations, headers, and other objects
+### Best Practices
-2. **URL Parameter Safety**
+1. **Use conditional requests** for dependent data:
- - Path parameters are properly encoded using `encodeURIComponent`
- - Query parameters are safely serialized and encoded
- - Prevents URL injection attacks and ensures valid URL formatting
+```tsx
+const { data: user } = useFetcher('/api/user');
+const { data: posts } = useFetcher(user ? `/api/users/${user.id}/posts` : null);
+```
-3. **Data Validation**
+2. **Configure appropriate cache times** based on data volatility:
- - Checks for JSON serializability of request bodies
- - Detects circular references that could cause issues
- - Properly handles different data types (strings, arrays, objects, etc.)
+```tsx
+// Static data - cache for 1 hour
+const { data: config } = useFetcher('/api/config', { cacheTime: 3600 });
-4. **Depth Control**
- - Prevents excessive recursion with depth limitations
- - Mitigates stack overflow attacks through query parameter manipulation
- - Maximum depth is controlled by `MAX_DEPTH` constant (default: 10)
+// Dynamic data - cache for 30 seconds
+const { data: feed } = useFetcher('/api/feed', { cacheTime: 30 });
+```
-### Implementation Details
+3. **Use focus revalidation** for critical data:
-The sanitization process is applied at multiple levels:
+```tsx
+const { data } = useFetcher('/api/critical-data', {
+ refetchOnFocus: true,
+});
+```
-- During request configuration building
-- When processing URL path parameters
-- When serializing query parameters
-- When handling request and response interceptors
-- During retry and polling operations
+4. **Handle loading and error states** appropriately:
-This multi-layered approach ensures that all data passing through the library is properly sanitized, significantly reducing the risk of injection attacks and other security vulnerabilities.
+```tsx
+const { data, error, isLoading } = useFetcher('/api/data');
-```typescript
-// Example of safe URL path parameter handling
-const { data } = await api.getUser({
- urlPathParams: {
- id: 'user-id with spaces & special chars',
- },
- // Automatically encoded to: /users/user-id%20with%20spaces%20%26%20special%20chars
-});
+if (isLoading) return ;
+if (error) return ;
+return ;
```
-Security is a core design principle of FetchFF, with sanitization mechanisms running automatically to provide protection without adding complexity to your code.
+5. **Leverage `staleTime` to control background revalidation:**
+
+```tsx
+// Data is considered fresh for 10 minutes; background revalidation happens after
+const { data } = useFetcher('/api/notifications', { staleTime: 600 });
+```
+
+- Use a longer `staleTime` for rarely changing data to minimize unnecessary network requests.
+- Use a shorter `staleTime` for frequently updated data to keep the UI fresh.
+- Setting `staleTime: 0` disables the staleTime (default).
+- Combine `staleTime` with `cacheTime` for fine-grained cache and revalidation control.
+- Adjust `staleTime` per endpoint based on how critical or dynamic the data is.
## Comparison with other libraries
-| Feature | fetchff | ofetch | wretch | axios | native fetch() |
-| -------------------------------------------------- | ----------- | ----------- | ------------ | ------------ | -------------- |
-| **Unified API Client** | â
| -- | -- | -- | -- |
-| **Smart Request Cache** | â
| -- | -- | -- | -- |
-| **Automatic Request Deduplication** | â
| -- | -- | -- | -- |
-| **Custom Fetching Adapter** | â
| -- | -- | -- | -- |
-| **Built-in Error Handling** | â
| -- | â
| -- | -- |
-| **Customizable Error Handling** | â
| -- | â
| â
| -- |
-| **Retries with exponential backoff** | â
| -- | -- | -- | -- |
-| **Advanced Query Params handling** | â
| -- | -- | -- | -- |
-| **Custom Retry logic** | â
| â
| â
| -- | -- |
-| **Easy Timeouts** | â
| â
| â
| â
| -- |
-| **Polling Functionality** | â
| -- | -- | -- | -- |
-| **Easy Cancellation of stale (previous) requests** | â
| -- | -- | -- | -- |
-| **Default Responses** | â
| -- | -- | -- | -- |
-| **Custom adapters (fetchers)** | â
| -- | -- | â
| -- |
-| **Global Configuration** | â
| -- | â
| â
| -- |
-| **TypeScript Support** | â
| â
| â
| â
| â
|
-| **Built-in AbortController Support** | â
| -- | -- | -- | -- |
-| **Request Interceptors** | â
| â
| â
| â
| -- |
-| **Request and Response Transformation** | â
| â
| â
| â
| -- |
-| **Integration with libraries** | â
| â
| â
| â
| -- |
-| **Request Queuing** | â
| -- | -- | -- | -- |
-| **Multiple Fetching Strategies** | â
| -- | -- | -- | -- |
-| **Dynamic URLs** | â
| -- | â
| -- | -- |
-| **Automatic Retry on Failure** | â
| â
| -- | â
| -- |
-| **Automatically handle 429 Retry-After headers** | â
| -- | -- | -- | -- |
-| **Built-in Input Sanitization** | â
| -- | -- | -- | -- |
-| **Prototype Pollution Protection** | â
| -- | -- | -- | -- |
-| **Server-Side Rendering (SSR) Support** | â
| â
| -- | -- | -- |
-| **Minimal Installation Size** | đĸ (4.3 KB) | đĄ (6.5 KB) | đĸ (2.21 KB) | đ´ (13.7 KB) | đĸ (0 KB) |
+_fetchff uniquely combines advanced input sanitization, prototype pollution protection, unified cache across React and direct fetches, multiple error handling strategies, and a declarative API repository patternâall in a single lightweight package._
+
+| Feature | fetchff | ofetch | wretch | axios | native fetch() | swr |
+| -------------------------------------------------- | ----------- | ----------- | ------------ | ------------ | -------------- | --------------- |
+| **Unified API Client** | â
| -- | -- | -- | -- | -- |
+| **Smart Request Cache** | â
| -- | -- | -- | -- | â
|
+| **Automatic Request Deduplication** | â
| -- | -- | -- | -- | â
|
+| **Revalidation on Window Focus** | â
| -- | -- | -- | -- | â
|
+| **Custom Fetching Adapter** | â
| -- | -- | -- | -- | â
|
+| **Built-in Error Handling** | â
| -- | â
| -- | -- | -- |
+| **Customizable Error Handling** | â
| -- | â
| â
| -- | â
|
+| **Retries with exponential backoff** | â
| -- | -- | -- | -- | -- |
+| **Advanced Query Params handling** | â
| -- | -- | -- | -- | -- |
+| **Custom Response Based Retry logic** | â
| â
| â
| -- | -- | -- |
+| **Easy Timeouts** | â
| â
| â
| â
| -- | -- |
+| **Adaptive Timeouts (Connection-aware)** | â
| -- | -- | -- | -- | -- |
+| **Conditional Polling Functionality** | â
| -- | -- | -- | -- | -- |
+| **Easy Cancellation of stale (previous) requests** | â
| -- | -- | -- | -- | -- |
+| **Default Responses** | â
| -- | -- | -- | -- | â
|
+| **Custom adapters (fetchers)** | â
| -- | -- | â
| -- | â
|
+| **Global Configuration** | â
| -- | â
| â
| -- | â
|
+| **TypeScript Support** | â
| â
| â
| â
| â
| â
|
+| **Built-in AbortController Support** | â
| -- | -- | -- | -- | -- |
+| **Request Interceptors** | â
| â
| â
| â
| -- | -- |
+| **Safe deduping + cancellation** | â
| -- | -- | -- | -- | -- |
+| **Response-based polling decisions** | â
| -- | -- | -- | -- | -- |
+| **Request/Response Data Transformation** | â
| â
| â
| â
| -- | -- |
+| **Works with Multiple Frameworks** | â
| â
| â
| â
| â
| -- |
+| **Works across multiple instances or layers** | â
| -- | -- | -- | -- | -- (only React) |
+| **Concurrent Request Deduplication** | â
| -- | -- | -- | -- | â
|
+| **Flexible Error Handling Strategies** | â
| -- | â
| â
| -- | â
|
+| **Dynamic URLs with Path and query separation** | â
| -- | â
| -- | -- | -- |
+| **Automatic Retry on Failure** | â
| â
| -- | â
| -- | â
|
+| **Automatically handle 429 Retry-After headers** | â
| -- | -- | -- | -- | -- |
+| **Built-in Input Sanitization** | â
| -- | -- | -- | -- | -- |
+| **Prototype Pollution Protection** | â
| -- | -- | -- | -- | -- |
+| **RFC 7231 Safe Methods Auto-execution** | â
| -- | -- | -- | -- | -- |
+| **First Class React Integration** | â
| -- | -- | -- | -- | â
|
+| **Shared cache for React and direct fetches** | â
| -- | -- | -- | -- | -- |
+| **Per-endpoint and per-request config merging** | â
| -- | -- | -- | -- | -- |
+| **Declarative API repository pattern** | â
| -- | -- | -- | -- | -- |
+| **Supports Server-Side Rendering (SSR)** | â
| â
| â
| â
| â
| â
|
+| **SWR Pattern Support** | â
| -- | -- | -- | -- | â
|
+| **Revalidation on Tab Focus** | â
| -- | -- | -- | -- | â
|
+| **Revalidation on Network Reconnect** | â
| -- | -- | -- | -- | â
|
+| **Minimal Installation Size** | đĸ (5.2 KB) | đĄ (6.5 KB) | đĸ (2.21 KB) | đ´ (13.7 KB) | đĸ (0 KB) | đĄ (6.2 KB) |
## âī¸ Examples
-Click to expand particular examples below. You can also check [examples.ts](./docs/examples/examples.ts) for more examples of usage.
+Click to expand particular examples below. You can also check [docs/examples/](./docs/examples/) for more examples of usage.
### All Settings
@@ -1238,19 +2811,23 @@ const api = createApiFetcher({
flattenResponse: false, // If true, flatten nested response data.
defaultResponse: null, // Default response when there is no data or endpoint fails.
withCredentials: true, // Pass cookies to all requests.
- timeout: 30000, // Request timeout in milliseconds (30s in this example).
+ timeout: 30000, // Request timeout in milliseconds. Defaults to 30s (60s on slow connections), can be overridden.
dedupeTime: 0, // Time window, in milliseconds, during which identical requests are deduplicated (treated as single request).
+ immediate: false, // If false, disables automatic request on initialization (useful for POST or conditional requests, React-specific)
+ staleTime: 600, // Data is considered fresh for 10 minutes before background revalidation (disabled by default)
pollingInterval: 5000, // Interval in milliseconds between polling attempts. Setting 0 disables polling.
pollingDelay: 1000, // Wait 1 second before beginning each polling attempt
maxPollingAttempts: 10, // Stop polling after 10 attempts
shouldStopPolling: (response, attempt) => false, // Function to determine if polling should stop based on the response. Return true to stop polling, or false to continue.
method: 'get', // Default request method.
params: {}, // Default params added to all requests.
+ urlPathParams: {}, // Dynamic URL path parameters for replacing segments like /user/:id
data: {}, // Alias for 'body'. Default data passed to POST, PUT, DELETE and PATCH requests.
cacheTime: 300, // Cache time in seconds. In this case it is valid for 5 minutes (300 seconds)
cacheKey: (config) => `${config.url}-${config.method}`, // Custom cache key based on URL and method
cacheBuster: (config) => config.method === 'POST', // Bust cache for POST requests
skipCache: (response, config) => response.status !== 200, // Skip caching on non-200 responses
+ cacheErrors: false, // Cache error responses as well as successful ones, default false
onError(error) {
// Interceptor on error
console.error('Request failed', error);
@@ -1274,9 +2851,9 @@ const api = createApiFetcher({
},
retry: {
retries: 3, // Number of retries on failure.
- delay: 1000, // Initial delay between retries in milliseconds.
+ delay: 1000, // Initial delay between retries in milliseconds. Defaults to 1s (2s on slow connections), can be overridden.
backoff: 1.5, // Backoff factor for retry delay.
- maxDelay: 30000, // Maximum delay between retries in milliseconds.
+ maxDelay: 30000, // Maximum delay between retries in milliseconds. Defaults to 30s (60s on slow connections), can be overridden.
resetTimeout: true, // Reset the timeout when retrying requests.
retryOn: [408, 409, 425, 429, 500, 502, 503, 504], // HTTP status codes to retry on.
shouldRetry: async (response, attempts) => {
@@ -1369,11 +2946,12 @@ interface Books {
}
interface BookQueryParams {
- newBook: boolean;
+ newBook?: boolean;
+ category?: string;
}
interface BookPathParams {
- bookId?: number;
+ bookId: number;
}
```
@@ -1384,37 +2962,66 @@ import { createApiFetcher } from 'fetchff';
const endpoints = {
fetchBooks: {
- url: 'books',
+ url: '/books',
+ method: 'GET' as const,
},
fetchBook: {
- url: 'books/:bookId',
+ url: '/books/:bookId',
+ method: 'GET' as const,
},
-};
-
-// No need to specify all endpoints types. For example, the "fetchBooks" is inferred automatically.
-interface EndpointsList {
- fetchBook: Endpoint;
+} as const;
+
+// Define endpoints with proper typing
+interface EndpointTypes {
+ fetchBook: Endpoint<{
+ response: Book;
+ params: BookQueryParams;
+ urlPathParams: BookPathParams;
+ }>;
+ fetchBooks: Endpoint<{ response: Books; params: BookQueryParams }>;
}
-type EndpointsConfiguration = typeof endpoints;
-
-const api = createApiFetcher({
- apiUrl: 'https://example.com/api/',
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
+ strategy: 'softFail',
endpoints,
});
+
+export { api };
+export type { Book, Books, BookQueryParams, BookPathParams };
```
```typescript
+// Usage with full type safety
+import { api, type Book, type Books } from './api';
+
+// Properly typed request with URL params
const book = await api.fetchBook({
params: { newBook: true },
urlPathParams: { bookId: 1 },
});
-// Will return an error since "rating" does not exist in "BookQueryParams"
-const anotherBook = await api.fetchBook({ params: { rating: 5 } });
+if (book.error) {
+ console.error('Failed to fetch book:', book.error.message);
+} else {
+ console.log('Book title:', book.data?.title);
+}
+
+// For example, this will cause a TypeScript error as 'rating' doesn't exist in BookQueryParams
+// const invalidBook = await api.fetchBook({
+// params: { rating: 5 }
+// });
-// You can also pass generic type directly to the request
-const books = await api.fetchBooks();
+// Generic type can be passed directly for additional type safety
+const books = await api.fetchBooks({
+ params: { category: 'fiction' },
+});
+
+if (books.error) {
+ console.error('Failed to fetch books:', books.error.message);
+} else {
+ console.log('Total books:', books.data?.totalResults);
+}
```
@@ -1432,13 +3039,11 @@ const endpoints = {
getPosts: {
url: '/posts/:subject',
},
-
getUser: {
// Generally there is no need to specify method: 'get' for GET requests as it is default one. It can be adjusted using global "method" setting
method: 'get',
url: '/user-details',
},
-
updateUserDetails: {
method: 'post',
url: '/user-details/update/:userId',
@@ -1446,14 +3051,30 @@ const endpoints = {
},
};
-interface EndpointsList {
- getPosts: Endpoint;
+interface PostsResponse {
+ posts: Array<{ id: number; title: string; content: string }>;
+ totalCount: number;
}
-type EndpointsConfiguration = typeof endpoints;
+interface PostsQueryParams {
+ additionalInfo?: string;
+ limit?: number;
+}
-const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+interface PostsPathParams {
+ subject: string;
+}
+
+interface EndpointTypes {
+ getPosts: Endpoint<{
+ response: PostsResponse;
+ params: PostsQueryParams;
+ urlPathParams: PostsPathParams;
+ }>;
+}
+
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
endpoints,
onError(error) {
console.log('Request failed', error);
@@ -1464,12 +3085,14 @@ const api = createApiFetcher({
});
// Fetch user data - "data" will return data directly
-// GET to: http://example.com/api/user-details?userId=1&ratings[]=1&ratings[]=2
-const { data } = await api.getUser({ params: { userId: 1, ratings: [1, 2] } });
+// GET to: https://example.com/api/user-details?userId=1&ratings[]=1&ratings[]=2
+const { data } = await api.getUser({
+ params: { userId: 1, ratings: [1, 2] },
+});
// Fetch posts - "data" will return data directly
-// GET to: http://example.com/api/posts/myTestSubject?additionalInfo=something
-const { data } = await api.getPosts({
+// GET to: https://example.com/api/posts/test?additionalInfo=something
+const { data: postsData } = await api.getPosts({
params: { additionalInfo: 'something' },
urlPathParams: { subject: 'test' },
});
@@ -1498,29 +3121,19 @@ In the example above we fetch data from an API for user with an ID of 1. We also
```typescript
-import { createApiFetcher, RequestConfig, FetchResponse } from 'fetchff';
-
-// Define the custom fetcher object
-const customFetcher = {
- create() {
- // Create instance here. It will be called at the beginning of every request.
- return {
- // This function will be called whenever a request is being fired.
- request: async (config: RequestConfig): Promise => {
- // Implement your custom fetch logic here
- const response = await fetch(config.url, config);
- // Optionally, process or transform the response
- return response;
- },
- };
- },
-};
+import { createApiFetcher } from 'fetchff';
// Create the API fetcher with the custom fetcher
const api = createApiFetcher({
baseURL: 'https://api.example.com/',
retry: retryConfig,
- fetcher: customFetcher, // Provide the custom fetcher object directly
+ // This function will be called whenever a request is being fired.
+ async fetcher(config) {
+ // Implement your custom fetch logic here
+ const response = await fetch(config.url, config);
+ // Optionally, process or transform the response
+ return response;
+ },
endpoints: {
getBooks: {
url: 'books/all',
@@ -1544,7 +3157,7 @@ const api = createApiFetcher({
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
@@ -1563,7 +3176,7 @@ async function sendMessage() {
console.log('Message sent successfully');
} catch (error) {
- console.log(error);
+ console.error('Message failed to send:', error.message);
}
}
@@ -1582,7 +3195,7 @@ sendMessage();
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
@@ -1599,9 +3212,9 @@ async function sendMessage() {
});
if (error) {
- console.error('Request Error', error);
+ console.error('Request Error', error.message);
} else {
- console.log('Message sent successfully');
+ console.log('Message sent successfully:', data);
}
}
@@ -1620,12 +3233,11 @@ sendMessage();
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
url: '/send-message/:postId',
-
// You can also specify strategy and other settings in global list of endpoints, but just for this endpoint
// strategy: 'defaultResponse',
},
@@ -1633,25 +3245,24 @@ const api = createApiFetcher({
});
async function sendMessage() {
- const { data } = await api.sendMessage({
+ const { data, error } = await api.sendMessage({
body: { message: 'Text' },
urlPathParams: { postId: 1 },
strategy: 'defaultResponse',
// null is a default setting, you can change it to empty {} or anything
- // defaultResponse: null,
+ defaultResponse: { status: 'failed', message: 'Default response' },
onError(error) {
// Callback is still triggered here
- console.log(error);
+ console.error('API error:', error.message);
},
});
- if (data === null) {
- // Because of the strategy, if API call fails, it will just return null
+ if (error) {
+ console.warn('Message failed to send, using default response:', data);
return;
}
- // You can do something with the response here
- console.log('Message sent successfully');
+ console.log('Message sent successfully:', data);
}
sendMessage();
@@ -1669,12 +3280,11 @@ sendMessage();
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
url: '/send-message/:postId',
-
// You can also specify strategy and other settings in here, just for this endpoint
// strategy: 'silent',
},
@@ -1687,7 +3297,7 @@ async function sendMessage() {
urlPathParams: { postId: 1 },
strategy: 'silent',
onError(error) {
- console.log(error);
+ console.error('Silent error logged:', error.message);
},
});
@@ -1711,7 +3321,7 @@ sendMessage();
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
@@ -1721,17 +3331,21 @@ const api = createApiFetcher({
});
async function sendMessage() {
- await api.sendMessage({
- body: { message: 'Text' },
- urlPathParams: { postId: 1 },
- onError(error) {
- console.log('Error', error.message);
- console.log(error.response);
- console.log(error.config);
- },
- });
+ try {
+ await api.sendMessage({
+ body: { message: 'Text' },
+ urlPathParams: { postId: 1 },
+ onError(error) {
+ console.error('Error intercepted:', error.message);
+ console.error('Response:', error.response);
+ console.error('Config:', error.config);
+ },
+ });
- console.log('Message sent successfully');
+ console.log('Message sent successfully');
+ } catch (error) {
+ console.error('Final error handler:', error.message);
+ }
}
sendMessage();
@@ -1752,11 +3366,11 @@ import { createApiFetcher } from 'fetchff';
// Initialize API fetcher with endpoints
const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
endpoints: {
getUser: { url: '/user' },
- createPost: { url: '/post' },
+ createPost: { url: '/post', method: 'POST' },
},
- apiUrl: 'https://example.com/api',
});
async function fetchUserAndCreatePost(userId: number, postData: any) {
@@ -1806,74 +3420,148 @@ app.get('/api/proxy', async (req, res) => {
`fetchff` is designed to seamlessly integrate with any popular frameworks like Next.js, libraries like React, Vue, React Query and SWR. It is written in pure JS so you can effortlessly manage API requests with minimal setup, and without any dependencies.
-#### Using with React
+#### Advanced Caching Strategies
Click to expand
- You can implement a `useFetcher()` hook to handle the data fetching. Since this package has everything included, you don't really need anything more than a simple hook to utilize.
-Create `api.ts` file:
+```typescript
+import { fetchf, mutate, deleteCache } from 'fetchff';
+
+// Example: User dashboard with smart caching
+const fetchUserDashboard = async (userId: string) => {
+ return await fetchf(`/api/users/${userId}/dashboard`, {
+ cacheTime: 300, // Cache for 5 minutes
+ staleTime: 60, // Background revalidate after 1 minute
+ cacheKey: `user-dashboard-${userId}`, // Custom cache key
+ skipCache: (response) => response.status === 503, // Skip caching on service unavailable
+ refetchOnFocus: true, // Refresh when user returns to tab
+ });
+};
-```tsx
-import { createApiFetcher } from 'fetchff';
+// Example: Optimistic updates with cache mutations
+const updateUserProfile = async (userId: string, updates: any) => {
+ // Optimistically update cache
+ const currentData = await fetchf(`/api/users/${userId}`);
+ await mutate(`/api/users/${userId}`, { ...currentData.data, ...updates });
-export const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
- strategy: 'softFail',
- endpoints: {
- getProfile: {
- url: '/profile/:id',
- },
- },
-});
+ try {
+ // Make actual API call
+ const response = await fetchf(`/api/users/${userId}`, {
+ method: 'PATCH',
+ body: updates,
+ });
+
+ // Update cache with real response
+ await mutate(`/api/users/${userId}`, response.data, { revalidate: true });
+
+ return response;
+ } catch (error) {
+ // Revert cache on error
+ await mutate(`/api/users/${userId}`, currentData.data);
+ throw error;
+ }
+};
+
+// Example: Cache invalidation after user logout
+const logout = async () => {
+ await fetchf('/api/auth/logout', { method: 'POST' });
+
+ // Clear all user-related cache
+ deleteCache('/api/user*');
+ deleteCache('/api/dashboard*');
+};
```
-Create `useFetcher.ts` file:
+
-```tsx
-export const useFetcher = (apiFunction) => {
- const [data, setData] = useState(null);
- const [error] = useState(null);
- const [isLoading, setLoading] = useState(true);
+#### Real-time Polling Implementation
- useEffect(() => {
- const fetchData = async () => {
- setLoading(true);
+
+ Click to expand
+
+
+```typescript
+import { fetchf } from 'fetchff';
- const { data, error } = await apiFunction();
+// Example: Job status monitoring with intelligent polling
+const monitorJobStatus = async (jobId: string) => {
+ return await fetchf(`/api/jobs/${jobId}/status`, {
+ pollingInterval: 2000, // Poll every 2 seconds
+ pollingDelay: 500, // Wait 500ms before first poll
+ maxPollingAttempts: 30, // Max 30 attempts (1 minute total)
+
+ shouldStopPolling(response, attempt) {
+ // Stop polling when job is complete or failed
+ if (
+ response.data?.status === 'completed' ||
+ response.data?.status === 'failed'
+ ) {
+ return true;
+ }
- if (error) {
- setError(error);
- } else {
- setData(data);
+ // Stop if we've been polling for too long
+ if (attempt >= 30) {
+ console.warn('Job monitoring timeout after 30 attempts');
+ return true;
}
- setLoading(false);
- };
+ return false;
+ },
- fetchData();
- }, [apiFunction]);
+ onResponse(response) {
+ console.log(`Job ${jobId} status:`, response.data?.status);
- return { data, error, isLoading, setData };
+ // Update UI progress if available
+ if (response.data?.progress) {
+ updateProgressBar(response.data.progress);
+ }
+ },
+ });
};
-```
-Call the API in the components:
+// Example: Server health monitoring
+const monitorServerHealth = async () => {
+ return await fetchf('/api/health', {
+ pollingInterval: 30000, // Check every 30 seconds
+ shouldStopPolling(response, attempt) {
+ // Never stop health monitoring (until manually cancelled)
+ return false;
+ },
-```tsx
-export const ProfileComponent = ({ id }) => {
- const {
- data: profile,
- error,
- isLoading,
- } = useFetcher(() => api.getProfile({ urlPathParams: { id } }));
+ onResponse(response) {
+ const isHealthy = response.data?.status === 'healthy';
+ updateHealthIndicator(isHealthy);
- if (isLoading) return Loading...
;
- if (error) return Error: {error.message}
;
+ if (!isHealthy) {
+ console.warn('Server health check failed:', response.data);
+ notifyAdmins(response.data);
+ }
+ },
- return {JSON.stringify(profile)}
;
+ onError(error) {
+ console.error('Health check failed:', error.message);
+ updateHealthIndicator(false);
+ },
+ });
};
+
+// Helper functions (implementation depends on your UI framework)
+function updateProgressBar(progress: number) {
+ // Update progress bar in UI
+ console.log(`Progress: ${progress}%`);
+}
+
+function updateHealthIndicator(isHealthy: boolean) {
+ // Update health indicator in UI
+ console.log(`Server status: ${isHealthy ? 'Healthy' : 'Unhealthy'}`);
+}
+
+function notifyAdmins(healthData: any) {
+ // Send notifications to administrators
+ console.log('Notifying admins about health issue:', healthData);
+}
```
@@ -1886,11 +3574,14 @@ export const ProfileComponent = ({ id }) => {
Integrate `fetchff` with React Query to streamline your data fetching:
+> **Note:** Official support for `useFetcher(url, config)` is here. Check React Integration section above to get an idea how to use it instead of SWR.
+
```tsx
import { createApiFetcher } from 'fetchff';
+import { useQuery } from '@tanstack/react-query';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
getProfile: {
url: '/profile/:id',
@@ -1898,10 +3589,12 @@ const api = createApiFetcher({
},
});
-export const useProfile = ({ id }) => {
- return useQuery(['profile', id], () =>
- api.getProfile({ urlPathParams: { id } }),
- );
+export const useProfile = (id: string) => {
+ return useQuery({
+ queryKey: ['profile', id],
+ queryFn: () => api.getProfile({ urlPathParams: { id } }),
+ enabled: !!id, // Only fetch when id exists
+ });
};
```
@@ -1915,19 +3608,28 @@ export const useProfile = ({ id }) => {
Combine `fetchff` with SWR for efficient data fetching and caching.
+> **Note:** Official support for `useFetcher(url, config)` is here. Check React Integration section above to get an idea how to use it instead of SWR.
+
Single calls:
```typescript
-const fetchProfile = ({ id }) =>
- fetchf('https://example.com/api/profile/:id', { urlPathParams: id });
+import { fetchf } from 'fetchff';
+import useSWR from 'swr';
+
+const fetchProfile = (id: string) =>
+ fetchf(`https://example.com/api/profile/${id}`, {
+ strategy: 'softFail',
+ });
-export const useProfile = ({ id }) => {
- const { data, error } = useSWR(['profile', id], fetchProfile);
+export const useProfile = (id: string) => {
+ const { data, error } = useSWR(id ? ['profile', id] : null, () =>
+ fetchProfile(id),
+ );
return {
- profile: data,
+ profile: data?.data,
isLoading: !error && !data,
- isError: error,
+ isError: error || data?.error,
};
};
```
@@ -1939,7 +3641,7 @@ import { createApiFetcher } from 'fetchff';
import useSWR from 'swr';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
endpoints: {
getProfile: {
url: '/profile/:id',
@@ -1947,15 +3649,15 @@ const api = createApiFetcher({
},
});
-export const useProfile = ({ id }) => {
- const fetcher = () => api.getProfile({ urlPathParams: { id } });
-
- const { data, error } = useSWR(['profile', id], fetcher);
+export const useProfile = (id: string) => {
+ const { data, error } = useSWR(id ? ['profile', id] : null, () =>
+ api.getProfile({ urlPathParams: { id } }),
+ );
return {
- profile: data,
+ profile: data?.data,
isLoading: !error && !data,
- isError: error,
+ isError: error || data?.error,
};
};
```
@@ -1973,7 +3675,7 @@ export const useProfile = ({ id }) => {
import { createApiFetcher } from 'fetchff';
const api = createApiFetcher({
- apiUrl: 'https://example.com/api',
+ baseURL: 'https://example.com/api',
strategy: 'softFail',
endpoints: {
getProfile: { url: '/profile/:id' },
diff --git a/docs/benchmarks/concurrency-react.png b/docs/benchmarks/concurrency-react.png
new file mode 100644
index 00000000..5e9ce46b
Binary files /dev/null and b/docs/benchmarks/concurrency-react.png differ
diff --git a/docs/benchmarks/react-benchmark.png b/docs/benchmarks/react-benchmark.png
new file mode 100644
index 00000000..5d81a888
Binary files /dev/null and b/docs/benchmarks/react-benchmark.png differ
diff --git a/docs/examples/example-basic.ts b/docs/examples/example-basic.ts
new file mode 100644
index 00000000..b1d9c29c
--- /dev/null
+++ b/docs/examples/example-basic.ts
@@ -0,0 +1,27 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ apiUrl: 'https://example.com/api',
+ endpoints: {
+ getUser: {
+ url: '/user-details/:id',
+ method: 'GET',
+ },
+ getBooks: {
+ url: '/books/all',
+ method: 'GET',
+ },
+ },
+});
+
+async function main() {
+ // Basic GET request with path param
+ const { data: user } = await api.getUser({ urlPathParams: { id: 2 } });
+ console.log('User:', user);
+
+ // Basic GET request to fetch all books
+ const { data: books } = await api.getBooks();
+ console.log('Books:', books);
+}
+
+main();
diff --git a/docs/examples/example-custom-headers.ts b/docs/examples/example-custom-headers.ts
new file mode 100644
index 00000000..5fef263a
--- /dev/null
+++ b/docs/examples/example-custom-headers.ts
@@ -0,0 +1,24 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ apiUrl: 'https://api.example.com/',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer YOUR_TOKEN',
+ },
+ endpoints: {
+ getProfile: {
+ url: '/profile/:id',
+ },
+ },
+});
+
+async function main() {
+ // GET request with custom headers and path param
+ const { data: profile } = await api.getProfile({
+ urlPathParams: { id: 123 },
+ });
+ console.log('Profile:', profile);
+}
+
+main();
diff --git a/docs/examples/example-error-strategy-defaultResponse.ts b/docs/examples/example-error-strategy-defaultResponse.ts
new file mode 100644
index 00000000..0748ab7e
--- /dev/null
+++ b/docs/examples/example-error-strategy-defaultResponse.ts
@@ -0,0 +1,34 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ apiUrl: 'https://example.com/api',
+ // strategy: 'defaultResponse',
+ endpoints: {
+ sendMessage: {
+ method: 'post',
+ url: '/send-message/:postId',
+ // strategy: 'defaultResponse',
+ },
+ },
+});
+
+async function sendMessage() {
+ const { data, error } = await api.sendMessage({
+ body: { message: 'Text' },
+ urlPathParams: { postId: 1 },
+ strategy: 'defaultResponse',
+ defaultResponse: { status: 'failed', message: 'Default response' },
+ onError(error) {
+ console.error('API error:', error.message);
+ },
+ });
+
+ if (error) {
+ console.warn('Message failed to send, using default response:', data);
+ return;
+ }
+
+ console.log('Message sent successfully:', data);
+}
+
+sendMessage();
diff --git a/docs/examples/example-error-strategy-reject.ts b/docs/examples/example-error-strategy-reject.ts
new file mode 100644
index 00000000..ce33ea7a
--- /dev/null
+++ b/docs/examples/example-error-strategy-reject.ts
@@ -0,0 +1,27 @@
+import { createApiFetcher } from 'fetchff';
+import type { ResponseError } from 'fetchff';
+
+const api = createApiFetcher({
+ apiUrl: 'https://example.com/api',
+ endpoints: {
+ sendMessage: {
+ method: 'post',
+ url: '/send-message/:postId',
+ strategy: 'reject',
+ },
+ },
+});
+
+async function sendMessage() {
+ try {
+ await api.sendMessage({
+ body: { message: 'Text' },
+ urlPathParams: { postId: 1 },
+ });
+ console.log('Message sent successfully');
+ } catch (error) {
+ console.error('Message failed to send:', (error as ResponseError).message);
+ }
+}
+
+sendMessage();
diff --git a/docs/examples/example-error-strategy-silent.ts b/docs/examples/example-error-strategy-silent.ts
new file mode 100644
index 00000000..14001f06
--- /dev/null
+++ b/docs/examples/example-error-strategy-silent.ts
@@ -0,0 +1,28 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
+ endpoints: {
+ sendMessage: {
+ method: 'post',
+ url: '/send-message/:postId',
+ // strategy: 'silent',
+ },
+ },
+});
+
+async function sendMessage() {
+ await api.sendMessage({
+ body: { message: 'Text' },
+ urlPathParams: { postId: 1 },
+ strategy: 'silent',
+ onError(error) {
+ console.error('Silent error logged:', error.message);
+ },
+ });
+
+ // Because of the strategy, if API call fails, it will never reach this point. Otherwise try/catch would need to be required.
+ console.log('Message sent successfully');
+}
+
+sendMessage();
diff --git a/docs/examples/example-error-strategy-softFail.ts b/docs/examples/example-error-strategy-softFail.ts
new file mode 100644
index 00000000..c12c69cd
--- /dev/null
+++ b/docs/examples/example-error-strategy-softFail.ts
@@ -0,0 +1,30 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ apiUrl: 'https://example.com/api',
+ // You can set default strategy for all endpoints
+ strategy: 'softFail',
+ endpoints: {
+ sendMessage: {
+ method: 'post',
+ url: '/send-message/:postId',
+ // You can override strategy for particular endpoint (we set the same here for demonstration)
+ strategy: 'softFail',
+ },
+ },
+});
+
+async function sendMessage() {
+ const { data, error } = await api.sendMessage({
+ body: { message: 'Text' },
+ urlPathParams: { postId: 1 },
+ });
+
+ if (error) {
+ console.error('Request Error', error.message);
+ } else {
+ console.log('Message sent successfully:', data);
+ }
+}
+
+sendMessage();
diff --git a/docs/examples/example-request-chaining.ts b/docs/examples/example-request-chaining.ts
new file mode 100644
index 00000000..eda7ae9e
--- /dev/null
+++ b/docs/examples/example-request-chaining.ts
@@ -0,0 +1,32 @@
+import { createApiFetcher } from 'fetchff';
+
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
+ endpoints: {
+ getUser: { url: '/user' },
+ createPost: { url: '/post', method: 'POST' },
+ },
+});
+
+interface PostData {
+ title: string;
+ content: string;
+}
+
+async function fetchUserAndCreatePost(userId: number, postData: PostData) {
+ // Fetch user data
+ const { data: userData } = await api.getUser({ params: { userId } });
+
+ // Create a new post with the fetched user data
+ return await api.createPost({
+ body: {
+ ...postData,
+ userId: userData.id,
+ },
+ });
+}
+
+// Example usage
+fetchUserAndCreatePost(1, { title: 'New Post', content: 'This is a new post.' })
+ .then((response) => console.log('Post created:', response))
+ .catch((error) => console.error('Error:', error));
diff --git a/docs/examples/example-typescript.ts b/docs/examples/example-typescript.ts
new file mode 100644
index 00000000..30ded007
--- /dev/null
+++ b/docs/examples/example-typescript.ts
@@ -0,0 +1,51 @@
+import { createApiFetcher } from 'fetchff';
+import type { Endpoint } from 'fetchff';
+
+// Example endpoint interfaces
+type Book = { id: number; title: string; rating: number };
+type Books = { books: Book[]; totalResults: number };
+type BookQueryParams = { newBook?: boolean; category?: string };
+type BookPathParams = { bookId: number };
+
+const endpoints = {
+ fetchBooks: {
+ url: '/books',
+ method: 'GET' as const,
+ },
+ fetchBook: {
+ url: '/books/:bookId',
+ method: 'GET' as const,
+ },
+} as const;
+
+interface EndpointsList {
+ fetchBook: Endpoint<{
+ response: Book;
+ params: BookQueryParams;
+ urlPathParams: BookPathParams;
+ }>;
+ fetchBooks: Endpoint<{ response: Books; params: BookQueryParams }>;
+}
+
+const api = createApiFetcher({
+ baseURL: 'https://example.com/api',
+ endpoints,
+ strategy: 'softFail',
+});
+
+async function main() {
+ // Properly typed request with URL params
+ const { data: book } = await api.fetchBook({
+ params: { newBook: true },
+ urlPathParams: { bookId: 1 },
+ });
+ console.log('Book:', book);
+
+ // Generic type can be passed directly for additional type safety
+ const { data: books } = await api.fetchBooks<{ response: Books }>({
+ params: { category: 'fiction' },
+ });
+ console.log('Books:', books);
+}
+
+main();
diff --git a/jest.config.js b/jest.config.js
index 3f7d373a..467dd394 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -4,4 +4,14 @@ module.exports = {
testEnvironment: 'node',
workerThreads: true,
coverageReporters: ['lcov', 'text', 'html'],
+ coveragePathIgnorePatterns: [
+ '/node_modules/',
+ '/test/utils/',
+ '/test/mocks/',
+ '/dist/',
+ ],
+ moduleNameMapper: {
+ '^fetchff$': '/src/index.ts',
+ '^fetchff/(.*)$': '/src/$1.ts',
+ },
};
diff --git a/package-lock.json b/package-lock.json
index 766c6cab..88b28fa7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,19 +10,27 @@
"license": "UNLICENSED",
"devDependencies": {
"@size-limit/preset-small-lib": "11.2.0",
- "@types/jest": "29.5.14",
- "eslint": "9.28.0",
+ "@testing-library/jest-dom": "6.6.3",
+ "@testing-library/react": "16.3.0",
+ "@types/jest": "30.0.0",
+ "@types/react": "19.1.8",
+ "benchmark": "2.1.4",
+ "eslint": "9.30.1",
"eslint-config-prettier": "10.1.5",
- "eslint-plugin-prettier": "5.4.1",
- "fetch-mock": "12.5.2",
- "jest": "29.7.0",
- "prettier": "3.5.3",
+ "eslint-plugin-prettier": "5.5.1",
+ "fetch-mock": "12.5.3",
+ "globals": "16.3.0",
+ "jest": "30.0.4",
+ "jest-environment-jsdom": "30.0.4",
+ "prettier": "3.6.2",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
"size-limit": "11.2.0",
- "ts-jest": "29.3.4",
+ "ts-jest": "29.4.0",
"tslib": "2.8.1",
"tsup": "8.5.0",
"typescript": "5.8.3",
- "typescript-eslint": "8.33.0"
+ "typescript-eslint": "8.36.0"
},
"engines": {
"node": ">=18"
@@ -31,6 +39,13 @@
"@rollup/rollup-linux-x64-gnu": "4.38.0"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
+ "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -45,25 +60,46 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz",
- "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz",
+ "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -71,22 +107,22 @@
}
},
"node_modules/@babel/core": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
- "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
+ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.24.7",
- "@babel/helper-compilation-targets": "^7.24.7",
- "@babel/helper-module-transforms": "^7.24.7",
- "@babel/helpers": "^7.24.7",
- "@babel/parser": "^7.24.7",
- "@babel/template": "^7.24.7",
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.4",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.27.4",
+ "@babel/types": "^7.27.3",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -102,31 +138,32 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
- "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
+ "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.24.7",
+ "@babel/parser": "^7.27.5",
+ "@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^2.5.1"
+ "jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz",
- "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.24.7",
- "@babel/helper-validator-option": "^7.24.7",
- "browserslist": "^4.22.2",
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
@@ -134,72 +171,30 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-environment-visitor": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
- "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-function-name": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
- "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.24.7",
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-hoist-variables": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
- "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/helper-module-imports": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
- "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7"
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz",
- "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-environment-visitor": "^7.24.7",
- "@babel/helper-module-imports": "^7.24.7",
- "@babel/helper-simple-access": "^7.24.7",
- "@babel/helper-split-export-declaration": "^7.24.7",
- "@babel/helper-validator-identifier": "^7.24.7"
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
@@ -209,46 +204,19 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz",
- "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-simple-access": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
- "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.24.7",
- "@babel/types": "^7.24.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-split-export-declaration": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
- "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/types": "^7.24.7"
- },
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -256,9 +224,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"license": "MIT",
"engines": {
@@ -266,9 +234,9 @@
}
},
"node_modules/@babel/helper-validator-option": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz",
- "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -276,27 +244,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
- "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
+ "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.0",
- "@babel/types": "^7.27.0"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
- "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "version": "7.27.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
+ "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.0"
+ "@babel/types": "^7.27.3"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -344,6 +312,38 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-syntax-import-meta": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
@@ -371,13 +371,13 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
- "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.7"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -464,6 +464,22 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-syntax-top-level-await": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
@@ -481,13 +497,13 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz",
- "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.7"
+ "@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -496,36 +512,43 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
- "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/parser": "^7.27.0",
- "@babel/types": "^7.27.0"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
- "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
+ "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/generator": "^7.24.7",
- "@babel/helper-environment-visitor": "^7.24.7",
- "@babel/helper-function-name": "^7.24.7",
- "@babel/helper-hoist-variables": "^7.24.7",
- "@babel/helper-split-export-declaration": "^7.24.7",
- "@babel/parser": "^7.24.7",
- "@babel/types": "^7.24.7",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -544,14 +567,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
- "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
+ "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -564,140 +587,159 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
+ "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
"license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
- "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
+ "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.0.2",
+ "@csstools/css-calc": "^2.1.4"
+ },
"engines": {
"node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
- "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
"node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
- "node_modules/@esbuild/android-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
- "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
],
+ "license": "MIT",
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/android-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
- "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@emnapi/core": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz",
+ "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==",
"dev": true,
"license": "MIT",
"optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.0.3",
+ "tslib": "^2.4.0"
}
},
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
- "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@emnapi/runtime": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz",
+ "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==",
"dev": true,
"license": "MIT",
"optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
- "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz",
+ "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==",
"dev": true,
"license": "MIT",
"optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
- "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+ "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
@@ -705,335 +747,42 @@
"license": "MIT",
"optional": true,
"os": [
- "freebsd"
+ "darwin"
],
"engines": {
"node": ">=18"
}
},
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
- "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
"engines": {
- "node": ">=18"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
- "node_modules/@esbuild/linux-arm": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
- "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "license": "Apache-2.0",
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
- "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
- "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
- "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
- "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
- "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
- "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
- "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
- "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
- "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
- "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
- "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
- "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
- "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
- "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
- "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
- "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
- "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
@@ -1047,9 +796,9 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
- "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1062,9 +811,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
- "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -1108,10 +857,43 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/@eslint/js": {
- "version": "9.28.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
- "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz",
+ "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1197,121 +979,36 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">=12"
+ "node": ">=18.18"
},
"funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
@@ -1331,283 +1028,289 @@
"node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
"engines": {
"node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "node_modules/@jest/console": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz",
+ "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
+ "@jest/types": "30.0.1",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.0.2",
+ "jest-util": "30.0.2",
+ "slash": "^3.0.0"
},
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "node_modules/@jest/core": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.4.tgz",
+ "integrity": "sha512-MWScSO9GuU5/HoWjpXAOBs6F/iobvK1XlioelgOM9St7S0Z5WTI9kjCQLPeo4eQRRYusyLW25/J7J5lbFkrYXw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-locate": "^4.1.0"
+ "@jest/console": "30.0.4",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.0.4",
+ "@jest/test-result": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.0.2",
+ "jest-config": "30.0.4",
+ "jest-haste-map": "30.0.2",
+ "jest-message-util": "30.0.2",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.0.2",
+ "jest-resolve-dependencies": "30.0.4",
+ "jest-runner": "30.0.4",
+ "jest-runtime": "30.0.4",
+ "jest-snapshot": "30.0.4",
+ "jest-util": "30.0.2",
+ "jest-validate": "30.0.2",
+ "jest-watcher": "30.0.4",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.0.2",
+ "slash": "^3.0.0"
},
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "node_modules/@jest/core/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
"engines": {
- "node": ">=6"
+ "node": ">=10"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "node_modules/@jest/core/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-limit": "^2.2.0"
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "node_modules/@jest/core/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/console": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
- "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "node_modules/@jest/environment": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz",
+ "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
+ "@jest/fake-timers": "30.0.4",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "chalk": "^4.0.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0"
+ "jest-mock": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/core": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
- "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "node_modules/@jest/environment-jsdom-abstract": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.0.4.tgz",
+ "integrity": "sha512-pUKfqgr5Nki9kZ/3iV+ubDsvtPq0a0oNL6zqkKLM1tPQI8FBJeuWskvW1kzc5pOvqlgpzumYZveJ4bxhANY0hg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/reporters": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/environment": "30.0.4",
+ "@jest/fake-timers": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@types/jsdom": "^21.1.7",
"@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-changed-files": "^29.7.0",
- "jest-config": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-resolve-dependencies": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "jest-mock": "30.0.2",
+ "jest-util": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "canvas": "^3.0.0",
+ "jsdom": "*"
},
"peerDependenciesMeta": {
- "node-notifier": {
+ "canvas": {
"optional": true
}
}
},
- "node_modules/@jest/environment": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
- "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "node_modules/@jest/expect": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz",
+ "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0"
+ "expect": "30.0.4",
+ "jest-snapshot": "30.0.4"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "node_modules/@jest/expect-utils": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz",
+ "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "expect": "^29.7.0",
- "jest-snapshot": "^29.7.0"
+ "@jest/get-type": "30.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/expect-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "node_modules/@jest/fake-timers": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz",
+ "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "jest-get-type": "^29.6.3"
+ "@jest/types": "30.0.1",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.0.2",
+ "jest-mock": "30.0.2",
+ "jest-util": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/fake-timers": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
- "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "node_modules/@jest/get-type": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz",
+ "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz",
+ "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "@sinonjs/fake-timers": "^10.0.2",
- "@types/node": "*",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
+ "@jest/environment": "30.0.4",
+ "@jest/expect": "30.0.4",
+ "@jest/types": "30.0.1",
+ "jest-mock": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/@jest/globals": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
- "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/types": "^29.6.3",
- "jest-mock": "^29.7.0"
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/reporters": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
- "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz",
+ "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
+ "@jest/console": "30.0.4",
+ "@jest/test-result": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@jridgewell/trace-mapping": "^0.3.25",
"@types/node": "*",
- "chalk": "^4.0.0",
- "collect-v8-coverage": "^1.0.0",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
"istanbul-lib-coverage": "^3.0.0",
"istanbul-lib-instrument": "^6.0.0",
"istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
"istanbul-reports": "^3.1.3",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
+ "jest-message-util": "30.0.2",
+ "jest-util": "30.0.2",
+ "jest-worker": "30.0.2",
"slash": "^3.0.0",
- "string-length": "^4.0.1",
- "strip-ansi": "^6.0.0",
+ "string-length": "^4.0.2",
"v8-to-istanbul": "^9.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
"node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
@@ -1619,114 +1322,131 @@
}
},
"node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz",
+ "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz",
+ "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@sinclair/typebox": "^0.27.8"
+ "@jest/types": "30.0.1",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/source-map": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
- "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.18",
- "callsites": "^3.0.0",
- "graceful-fs": "^4.2.9"
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/test-result": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
- "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz",
+ "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "collect-v8-coverage": "^1.0.0"
+ "@jest/console": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/test-sequencer": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
- "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.4.tgz",
+ "integrity": "sha512-bj6ePmqi4uxAE8EHE0Slmk5uBYd9Vd/PcVt06CsBxzH4bbA8nGsI1YbXl/NH+eii4XRtyrRx+Cikub0x8H4vDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/test-result": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
+ "@jest/test-result": "30.0.4",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.0.2",
"slash": "^3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/transform": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
- "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz",
+ "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "babel-plugin-istanbul": "^6.1.1",
- "chalk": "^4.0.0",
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.0.1",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.0",
+ "chalk": "^4.1.2",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "micromatch": "^4.0.4",
- "pirates": "^4.0.4",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.0.2",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.0.2",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
"slash": "^3.0.0",
- "write-file-atomic": "^4.0.2"
+ "write-file-atomic": "^5.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jest/types": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
- "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz",
+ "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/schemas": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^3.0.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.1",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
"@types/node": "*",
- "@types/yargs": "^17.0.8",
- "chalk": "^4.0.0"
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1776,6 +1496,19 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
+ "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.9.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1826,9 +1559,9 @@
}
},
"node_modules/@pkgr/core": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
- "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
+ "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1839,9 +1572,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz",
- "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz",
+ "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==",
"cpu": [
"arm"
],
@@ -1853,9 +1586,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz",
- "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz",
+ "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==",
"cpu": [
"arm64"
],
@@ -1867,9 +1600,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz",
- "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz",
+ "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==",
"cpu": [
"arm64"
],
@@ -1881,9 +1614,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz",
- "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz",
+ "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==",
"cpu": [
"x64"
],
@@ -1895,9 +1628,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz",
- "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz",
+ "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==",
"cpu": [
"arm64"
],
@@ -1909,9 +1642,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz",
- "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz",
+ "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==",
"cpu": [
"x64"
],
@@ -1923,9 +1656,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz",
- "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz",
+ "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==",
"cpu": [
"arm"
],
@@ -1937,9 +1670,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz",
- "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz",
+ "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==",
"cpu": [
"arm"
],
@@ -1951,9 +1684,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz",
- "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz",
+ "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==",
"cpu": [
"arm64"
],
@@ -1965,9 +1698,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz",
- "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz",
+ "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==",
"cpu": [
"arm64"
],
@@ -1979,9 +1712,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz",
- "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz",
+ "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==",
"cpu": [
"loong64"
],
@@ -1993,9 +1726,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz",
- "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz",
+ "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==",
"cpu": [
"ppc64"
],
@@ -2007,9 +1740,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz",
- "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz",
+ "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==",
"cpu": [
"riscv64"
],
@@ -2021,9 +1754,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz",
- "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz",
+ "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==",
"cpu": [
"riscv64"
],
@@ -2035,9 +1768,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz",
- "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz",
+ "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==",
"cpu": [
"s390x"
],
@@ -2062,9 +1795,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz",
- "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz",
+ "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==",
"cpu": [
"x64"
],
@@ -2076,9 +1809,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz",
- "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz",
+ "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==",
"cpu": [
"arm64"
],
@@ -2090,9 +1823,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz",
- "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz",
+ "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==",
"cpu": [
"ia32"
],
@@ -2104,9 +1837,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz",
- "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz",
+ "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==",
"cpu": [
"x64"
],
@@ -2118,9 +1851,9 @@
]
},
"node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "version": "0.34.37",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
+ "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==",
"dev": true,
"license": "MIT"
},
@@ -2135,13 +1868,13 @@
}
},
"node_modules/@sinonjs/fake-timers": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
- "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "version": "13.0.5",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
+ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
- "@sinonjs/commons": "^3.0.0"
+ "@sinonjs/commons": "^3.0.1"
}
},
"node_modules/@size-limit/esbuild": {
@@ -2189,132 +1922,114 @@
"size-limit": "11.2.0"
}
},
- "node_modules/@swc/core": {
- "version": "1.7.26",
- "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.26.tgz",
- "integrity": "sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==",
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"dev": true,
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
+ "license": "MIT",
"peer": true,
"dependencies": {
- "@swc/counter": "^0.1.3",
- "@swc/types": "^0.1.12"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/swc"
- },
- "optionalDependencies": {
- "@swc/core-darwin-arm64": "1.7.26",
- "@swc/core-darwin-x64": "1.7.26",
- "@swc/core-linux-arm-gnueabihf": "1.7.26",
- "@swc/core-linux-arm64-gnu": "1.7.26",
- "@swc/core-linux-arm64-musl": "1.7.26",
- "@swc/core-linux-x64-gnu": "1.7.26",
- "@swc/core-linux-x64-musl": "1.7.26",
- "@swc/core-win32-arm64-msvc": "1.7.26",
- "@swc/core-win32-ia32-msvc": "1.7.26",
- "@swc/core-win32-x64-msvc": "1.7.26"
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
},
- "peerDependencies": {
- "@swc/helpers": "*"
- },
- "peerDependenciesMeta": {
- "@swc/helpers": {
- "optional": true
- }
- }
- },
- "node_modules/@swc/core-darwin-arm64": {
- "version": "1.7.26",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.26.tgz",
- "integrity": "sha512-FF3CRYTg6a7ZVW4yT9mesxoVVZTrcSWtmZhxKCYJX9brH4CS/7PRPjAKNk6kzWgWuRoglP7hkjQcd6EpMcZEAw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0 AND MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true,
"engines": {
- "node": ">=10"
+ "node": ">=18"
}
},
- "node_modules/@swc/counter": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
- "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.13",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz",
- "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==",
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
"dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "tslib": "^2.4.0"
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "lodash": "^4.17.21",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
}
},
- "node_modules/@swc/types": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz",
- "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==",
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "@swc/counter": "^0.1.3"
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/@tsconfig/node10": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
+ "license": "MIT"
},
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "node_modules/@testing-library/react": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
+ "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
},
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
+ "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
"dev": true,
"license": "MIT",
"optional": true,
- "peer": true
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
},
- "node_modules/@tsconfig/node16": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT",
- "optional": true,
"peer": true
},
"node_modules/@types/babel__core": {
@@ -2332,9 +2047,9 @@
}
},
"node_modules/@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2353,9 +2068,9 @@
}
},
"node_modules/@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2363,9 +2078,9 @@
}
},
"node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
@@ -2376,16 +2091,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/graceful-fs": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
- "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -2414,14 +2119,61 @@
}
},
"node_modules/@types/jest": {
- "version": "29.5.14",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
- "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@types/jest/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@types/jest/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsdom": {
+ "version": "21.1.7",
+ "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
+ "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "expect": "^29.0.0",
- "pretty-format": "^29.0.0"
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "parse5": "^7.0.0"
}
},
"node_modules/@types/json-schema": {
@@ -2432,13 +2184,23 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "20.14.9",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz",
- "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==",
+ "version": "24.0.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz",
+ "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.8.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.8",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
+ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "csstype": "^3.0.2"
}
},
"node_modules/@types/stack-utils": {
@@ -2448,10 +2210,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/yargs": {
- "version": "17.0.32",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
- "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2466,17 +2235,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
- "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz",
+ "integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.33.0",
- "@typescript-eslint/type-utils": "8.33.0",
- "@typescript-eslint/utils": "8.33.0",
- "@typescript-eslint/visitor-keys": "8.33.0",
+ "@typescript-eslint/scope-manager": "8.36.0",
+ "@typescript-eslint/type-utils": "8.36.0",
+ "@typescript-eslint/utils": "8.36.0",
+ "@typescript-eslint/visitor-keys": "8.36.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -2490,7 +2259,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.33.0",
+ "@typescript-eslint/parser": "^8.36.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0"
}
@@ -2506,16 +2275,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
- "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz",
+ "integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.33.0",
- "@typescript-eslint/types": "8.33.0",
- "@typescript-eslint/typescript-estree": "8.33.0",
- "@typescript-eslint/visitor-keys": "8.33.0",
+ "@typescript-eslint/scope-manager": "8.36.0",
+ "@typescript-eslint/types": "8.36.0",
+ "@typescript-eslint/typescript-estree": "8.36.0",
+ "@typescript-eslint/visitor-keys": "8.36.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2531,14 +2300,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
- "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz",
+ "integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.33.0",
- "@typescript-eslint/types": "^8.33.0",
+ "@typescript-eslint/tsconfig-utils": "^8.36.0",
+ "@typescript-eslint/types": "^8.36.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2547,17 +2316,20 @@
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
- "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz",
+ "integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.33.0",
- "@typescript-eslint/visitor-keys": "8.33.0"
+ "@typescript-eslint/types": "8.36.0",
+ "@typescript-eslint/visitor-keys": "8.36.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2568,9 +2340,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
- "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz",
+ "integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2585,14 +2357,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
- "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz",
+ "integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.33.0",
- "@typescript-eslint/utils": "8.33.0",
+ "@typescript-eslint/typescript-estree": "8.36.0",
+ "@typescript-eslint/utils": "8.36.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2609,146 +2381,409 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
- "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz",
+ "integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz",
+ "integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.36.0",
+ "@typescript-eslint/tsconfig-utils": "8.36.0",
+ "@typescript-eslint/types": "8.36.0",
+ "@typescript-eslint/visitor-keys": "8.36.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz",
+ "integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.36.0",
+ "@typescript-eslint/types": "8.36.0",
+ "@typescript-eslint/typescript-estree": "8.36.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz",
+ "integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.36.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.0.tgz",
+ "integrity": "sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.0.tgz",
+ "integrity": "sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.0.tgz",
+ "integrity": "sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.0.tgz",
+ "integrity": "sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.0.tgz",
+ "integrity": "sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.0.tgz",
+ "integrity": "sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.0.tgz",
+ "integrity": "sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.0.tgz",
+ "integrity": "sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.0.tgz",
+ "integrity": "sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.0.tgz",
+ "integrity": "sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.0.tgz",
+ "integrity": "sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
- "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.0.tgz",
+ "integrity": "sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.33.0",
- "@typescript-eslint/tsconfig-utils": "8.33.0",
- "@typescript-eslint/types": "8.33.0",
- "@typescript-eslint/visitor-keys": "8.33.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.0.tgz",
+ "integrity": "sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.0.tgz",
+ "integrity": "sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.0.tgz",
+ "integrity": "sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@typescript-eslint/utils": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
- "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.0.tgz",
+ "integrity": "sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw==",
+ "cpu": [
+ "wasm32"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.33.0",
- "@typescript-eslint/types": "8.33.0",
- "@typescript-eslint/typescript-estree": "8.33.0"
+ "@napi-rs/wasm-runtime": "^0.2.11"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
+ "node": ">=14.0.0"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
- "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.0.tgz",
+ "integrity": "sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.33.0",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.0.tgz",
+ "integrity": "sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.0.tgz",
+ "integrity": "sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
},
"node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -2768,19 +2803,14 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.3",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
- "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "acorn": "^8.11.0"
- },
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 14"
}
},
"node_modules/ajv": {
@@ -2816,19 +2846,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/ansi-escapes/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2876,140 +2893,129 @@
"node": ">= 8"
}
},
- "node_modules/arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
- "license": "Python-2.0"
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
},
"node_modules/async": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
- "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"dev": true,
"license": "MIT"
},
"node_modules/babel-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
- "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz",
+ "integrity": "sha512-UjG2j7sAOqsp2Xua1mS/e+ekddkSu3wpf4nZUSvXNHuVWdaOUXQ77+uyjJLDE9i0atm5x4kds8K9yb5lRsRtcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/transform": "^29.7.0",
- "@types/babel__core": "^7.1.14",
- "babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.6.3",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
+ "@jest/transform": "30.0.4",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.0",
+ "babel-preset-jest": "30.0.1",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
"slash": "^3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
- "@babel/core": "^7.8.0"
+ "@babel/core": "^7.11.0"
}
},
"node_modules/babel-plugin-istanbul": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
- "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz",
+ "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-instrument": "^5.0.4",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
"test-exclude": "^6.0.0"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
- "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.12.3",
- "@babel/parser": "^7.14.7",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^6.3.0"
- },
- "engines": {
- "node": ">=8"
+ "node": ">=12"
}
},
"node_modules/babel-plugin-jest-hoist": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz",
+ "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.3.3",
- "@babel/types": "^7.3.3",
- "@types/babel__core": "^7.1.14",
- "@types/babel__traverse": "^7.0.6"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
+ "@types/babel__core": "^7.20.5"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/babel-preset-current-node-syntax": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
- "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
+ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-bigint": "^7.8.3",
- "@babel/plugin-syntax-class-properties": "^7.8.3",
- "@babel/plugin-syntax-import-meta": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-json-strings": "^7.8.3",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
- "@babel/plugin-syntax-top-level-await": "^7.8.3"
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/babel-preset-jest": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
- "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz",
+ "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "babel-plugin-jest-hoist": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0"
+ "babel-plugin-jest-hoist": "30.0.1",
+ "babel-preset-current-node-syntax": "^1.1.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
- "@babel/core": "^7.0.0"
+ "@babel/core": "^7.11.0"
}
},
"node_modules/balanced-match": {
@@ -3019,10 +3025,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/benchmark": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
+ "integrity": "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.4",
+ "platform": "^1.3.3"
+ }
+ },
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3044,9 +3061,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.23.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
- "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
+ "version": "4.25.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
+ "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"dev": true,
"funding": [
{
@@ -3064,10 +3081,10 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001629",
- "electron-to-chromium": "^1.4.796",
- "node-releases": "^2.0.14",
- "update-browserslist-db": "^1.0.16"
+ "caniuse-lite": "^1.0.30001718",
+ "electron-to-chromium": "^1.5.160",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
@@ -3163,9 +3180,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001640",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
- "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
+ "version": "1.0.30001721",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz",
+ "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==",
"dev": true,
"funding": [
{
@@ -3227,9 +3244,9 @@
}
},
"node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz",
+ "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==",
"dev": true,
"funding": [
{
@@ -3243,9 +3260,9 @@
}
},
"node_modules/cjs-module-lexer": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
- "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==",
"dev": true,
"license": "MIT"
},
@@ -3254,14 +3271,67 @@
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
- "license": "ISC",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/co": {
@@ -3343,56 +3413,67 @@
"dev": true,
"license": "MIT"
},
- "node_modules/create-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
- "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "prompts": "^2.0.1"
- },
- "bin": {
- "create-jest": "bin/create-jest.js"
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 8"
}
},
- "node_modules/create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
},
"engines": {
- "node": ">= 8"
+ "node": ">=18"
}
},
"node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3407,10 +3488,17 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/dedent": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
- "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -3459,27 +3547,13 @@
"node": ">=8"
}
},
- "node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
- "license": "BSD-3-Clause",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
+ "peer": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
@@ -3505,9 +3579,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.816",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.816.tgz",
- "integrity": "sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw==",
+ "version": "1.5.166",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz",
+ "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==",
"dev": true,
"license": "ISC"
},
@@ -3525,12 +3599,25 @@
}
},
"node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3542,9 +3629,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
- "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -3555,37 +3642,37 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.2",
- "@esbuild/android-arm": "0.25.2",
- "@esbuild/android-arm64": "0.25.2",
- "@esbuild/android-x64": "0.25.2",
- "@esbuild/darwin-arm64": "0.25.2",
- "@esbuild/darwin-x64": "0.25.2",
- "@esbuild/freebsd-arm64": "0.25.2",
- "@esbuild/freebsd-x64": "0.25.2",
- "@esbuild/linux-arm": "0.25.2",
- "@esbuild/linux-arm64": "0.25.2",
- "@esbuild/linux-ia32": "0.25.2",
- "@esbuild/linux-loong64": "0.25.2",
- "@esbuild/linux-mips64el": "0.25.2",
- "@esbuild/linux-ppc64": "0.25.2",
- "@esbuild/linux-riscv64": "0.25.2",
- "@esbuild/linux-s390x": "0.25.2",
- "@esbuild/linux-x64": "0.25.2",
- "@esbuild/netbsd-arm64": "0.25.2",
- "@esbuild/netbsd-x64": "0.25.2",
- "@esbuild/openbsd-arm64": "0.25.2",
- "@esbuild/openbsd-x64": "0.25.2",
- "@esbuild/sunos-x64": "0.25.2",
- "@esbuild/win32-arm64": "0.25.2",
- "@esbuild/win32-ia32": "0.25.2",
- "@esbuild/win32-x64": "0.25.2"
+ "@esbuild/aix-ppc64": "0.25.5",
+ "@esbuild/android-arm": "0.25.5",
+ "@esbuild/android-arm64": "0.25.5",
+ "@esbuild/android-x64": "0.25.5",
+ "@esbuild/darwin-arm64": "0.25.5",
+ "@esbuild/darwin-x64": "0.25.5",
+ "@esbuild/freebsd-arm64": "0.25.5",
+ "@esbuild/freebsd-x64": "0.25.5",
+ "@esbuild/linux-arm": "0.25.5",
+ "@esbuild/linux-arm64": "0.25.5",
+ "@esbuild/linux-ia32": "0.25.5",
+ "@esbuild/linux-loong64": "0.25.5",
+ "@esbuild/linux-mips64el": "0.25.5",
+ "@esbuild/linux-ppc64": "0.25.5",
+ "@esbuild/linux-riscv64": "0.25.5",
+ "@esbuild/linux-s390x": "0.25.5",
+ "@esbuild/linux-x64": "0.25.5",
+ "@esbuild/netbsd-arm64": "0.25.5",
+ "@esbuild/netbsd-x64": "0.25.5",
+ "@esbuild/openbsd-arm64": "0.25.5",
+ "@esbuild/openbsd-x64": "0.25.5",
+ "@esbuild/sunos-x64": "0.25.5",
+ "@esbuild/win32-arm64": "0.25.5",
+ "@esbuild/win32-ia32": "0.25.5",
+ "@esbuild/win32-x64": "0.25.5"
}
},
"node_modules/escalade": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
- "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3606,19 +3693,19 @@
}
},
"node_modules/eslint": {
- "version": "9.28.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
- "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
+ "version": "9.30.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz",
+ "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.0",
- "@eslint/config-helpers": "^0.2.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.28.0",
+ "@eslint/js": "9.30.1",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -3630,9 +3717,9 @@
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.3.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -3683,9 +3770,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz",
- "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==",
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
+ "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3714,9 +3801,9 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -3731,55 +3818,78 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "node_modules/eslint/node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=10"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "node_modules/eslint/node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
+ "p-locate": "^5.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=10"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "node_modules/eslint/node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -3802,9 +3912,9 @@
}
},
"node_modules/esquery": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
- "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -3871,30 +3981,39 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/exit": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz",
+ "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/expect-utils": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0"
+ "@jest/expect-utils": "30.0.4",
+ "@jest/get-type": "30.0.1",
+ "jest-matcher-utils": "30.0.4",
+ "jest-message-util": "30.0.2",
+ "jest-mock": "30.0.2",
+ "jest-util": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/fast-deep-equal": {
@@ -3976,9 +4095,9 @@
}
},
"node_modules/fetch-mock": {
- "version": "12.5.2",
- "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.5.2.tgz",
- "integrity": "sha512-b5KGDFmdmado2MPQjZl6ix3dAG3iwCitb0XQwN72y2s9VnWZ3ObaGNy+bkpm1390foiLDybdJ7yjRGKD36kATw==",
+ "version": "12.5.3",
+ "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.5.3.tgz",
+ "integrity": "sha512-SiqPv1IXvDjNjLWCvfFUltba3VeiYucxjyynoVW8Ft07GLFQRitlzjYZI/f5wZpeQFRIVZ84fmMgJfjwb/dAEA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4015,9 +4134,9 @@
}
},
"node_modules/filelist/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4051,20 +4170,17 @@
}
},
"node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "locate-path": "^6.0.0",
+ "locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
"node_modules/fix-dts-default-cjs-exports": {
@@ -4094,20 +4210,20 @@
}
},
"node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC"
},
"node_modules/foreground-child": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
- "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
- "cross-spawn": "^7.0.0",
+ "cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -4117,19 +4233,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -4152,16 +4255,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -4206,22 +4299,21 @@
}
},
"node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
},
- "engines": {
- "node": "*"
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -4247,10 +4339,36 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4284,17 +4402,17 @@
"node": ">=8"
}
},
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "function-bind": "^1.1.2"
+ "whatwg-encoding": "^3.1.1"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=18"
}
},
"node_modules/html-escaper": {
@@ -4304,6 +4422,34 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -4314,10 +4460,23 @@
"node": ">=10.17.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
- "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4341,10 +4500,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-fresh/node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/import-local": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
- "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4371,6 +4540,16 @@
"node": ">=0.8.19"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -4397,22 +4576,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/is-core-module": {
- "version": "2.14.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
- "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4466,6 +4629,13 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -4514,9 +4684,9 @@
}
},
"node_modules/istanbul-lib-instrument/node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -4542,15 +4712,15 @@
}
},
"node_modules/istanbul-lib-source-maps": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
"debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0",
- "source-map": "^0.6.1"
+ "istanbul-lib-coverage": "^3.0.0"
},
"engines": {
"node": ">=10"
@@ -4571,17 +4741,14 @@
}
},
"node_modules/jackspeak": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz",
- "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
- "engines": {
- "node": ">=14"
- },
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
@@ -4609,22 +4776,22 @@
}
},
"node_modules/jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
- "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz",
+ "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/types": "^29.6.3",
- "import-local": "^3.0.2",
- "jest-cli": "^29.7.0"
+ "@jest/core": "30.0.4",
+ "@jest/types": "30.0.1",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.0.4"
},
"bin": {
"jest": "bin/jest.js"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
"node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
@@ -4636,76 +4803,110 @@
}
},
"node_modules/jest-changed-files": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
- "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz",
+ "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "execa": "^5.0.0",
- "jest-util": "^29.7.0",
+ "execa": "^5.1.1",
+ "jest-util": "30.0.2",
"p-limit": "^3.1.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-circus": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
- "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.4.tgz",
+ "integrity": "sha512-o6UNVfbXbmzjYgmVPtSQrr5xFZCtkDZGdTlptYvGFSN80RuOOlTe73djvMrs+QAuSERZWcHBNIOMH+OEqvjWuw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/environment": "30.0.4",
+ "@jest/expect": "30.0.4",
+ "@jest/test-result": "30.0.4",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "chalk": "^4.0.0",
+ "chalk": "^4.1.2",
"co": "^4.6.0",
- "dedent": "^1.0.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^29.7.0",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.0.2",
+ "jest-matcher-utils": "30.0.4",
+ "jest-message-util": "30.0.2",
+ "jest-runtime": "30.0.4",
+ "jest-snapshot": "30.0.4",
+ "jest-util": "30.0.2",
"p-limit": "^3.1.0",
- "pretty-format": "^29.7.0",
- "pure-rand": "^6.0.0",
+ "pretty-format": "30.0.2",
+ "pure-rand": "^7.0.0",
"slash": "^3.0.0",
- "stack-utils": "^2.0.3"
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-circus/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-circus/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-cli": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
- "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.4.tgz",
+ "integrity": "sha512-3dOrP3zqCWBkjoVG1zjYJpD9143N9GUCbwaF2pFF5brnIgRLHmKcCIw+83BvF1LxggfMWBA0gxkn6RuQVuRhIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "create-jest": "^29.7.0",
- "exit": "^0.1.2",
- "import-local": "^3.0.2",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "yargs": "^17.3.1"
+ "@jest/core": "30.0.4",
+ "@jest/test-result": "30.0.4",
+ "@jest/types": "30.0.1",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.0.4",
+ "jest-util": "30.0.2",
+ "jest-validate": "30.0.2",
+ "yargs": "^17.7.2"
},
"bin": {
"jest": "bin/jest.js"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
"node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
@@ -4717,215 +4918,446 @@
}
},
"node_modules/jest-config": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
- "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.4.tgz",
+ "integrity": "sha512-3dzbO6sh34thAGEjJIW0fgT0GA0EVlkski6ZzMcbW6dzhenylXAE/Mj2MI4HonroWbkKc6wU6bLVQ8dvBSZ9lA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/test-sequencer": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-jest": "^29.7.0",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "deepmerge": "^4.2.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-circus": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "micromatch": "^4.0.4",
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.0.1",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.0.4",
+ "@jest/types": "30.0.1",
+ "babel-jest": "30.0.4",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.0.4",
+ "jest-docblock": "30.0.1",
+ "jest-environment-node": "30.0.4",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.0.2",
+ "jest-runner": "30.0.4",
+ "jest-util": "30.0.2",
+ "jest-validate": "30.0.2",
+ "micromatch": "^4.0.8",
"parse-json": "^5.2.0",
- "pretty-format": "^29.7.0",
+ "pretty-format": "30.0.2",
"slash": "^3.0.0",
"strip-json-comments": "^3.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
"@types/node": "*",
+ "esbuild-register": ">=3.4.0",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
+ "esbuild-register": {
+ "optional": true
+ },
"ts-node": {
"optional": true
}
}
},
+ "node_modules/jest-config/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-config/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-config/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-diff": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz",
+ "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "diff-sequences": "^29.6.3",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.0.1",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-diff/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-diff/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-diff/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-docblock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
- "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz",
+ "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "detect-newline": "^3.0.0"
+ "detect-newline": "^3.1.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-each": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
- "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.2.tgz",
+ "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "jest-util": "^29.7.0",
- "pretty-format": "^29.7.0"
+ "@jest/get-type": "30.0.1",
+ "@jest/types": "30.0.1",
+ "chalk": "^4.1.2",
+ "jest-util": "30.0.2",
+ "pretty-format": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
- "node_modules/jest-environment-node": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
- "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "node_modules/jest-each/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-each/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-environment-jsdom": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.0.4.tgz",
+ "integrity": "sha512-9WmS3oyCLFgs6DUJSoMpVb+AbH62Y2Xecw3XClbRgj6/Z+VjNeSLjrhBgVvTZ40njZTWeDHv8unp+6M/z8ADDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/environment": "30.0.4",
+ "@jest/environment-jsdom-abstract": "30.0.4",
+ "@types/jsdom": "^21.1.7",
"@types/node": "*",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
+ "jsdom": "^26.1.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
}
},
- "node_modules/jest-get-type": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "node_modules/jest-environment-node": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.4.tgz",
+ "integrity": "sha512-p+rLEzC2eThXqiNh9GHHTC0OW5Ca4ZfcURp7scPjYBcmgpR9HG6750716GuUipYf2AcThU3k20B31USuiaaIEg==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.0.4",
+ "@jest/fake-timers": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@types/node": "*",
+ "jest-mock": "30.0.2",
+ "jest-util": "30.0.2",
+ "jest-validate": "30.0.2"
+ },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-haste-map": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
- "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz",
+ "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "@types/graceful-fs": "^4.1.3",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "anymatch": "^3.0.3",
- "fb-watchman": "^2.0.0",
- "graceful-fs": "^4.2.9",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "micromatch": "^4.0.4",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.0.2",
+ "jest-worker": "30.0.2",
+ "micromatch": "^4.0.8",
"walker": "^1.0.8"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"optionalDependencies": {
- "fsevents": "^2.3.2"
+ "fsevents": "^2.3.3"
}
},
"node_modules/jest-leak-detector": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
- "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz",
+ "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "@jest/get-type": "30.0.1",
+ "pretty-format": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-leak-detector/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-leak-detector/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-matcher-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz",
+ "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "@jest/get-type": "30.0.1",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.0.4",
+ "pretty-format": "30.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-matcher-utils/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-matcher-utils/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-message-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
- "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz",
+ "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.12.13",
- "@jest/types": "^29.6.3",
- "@types/stack-utils": "^2.0.0",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.0.1",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.0.2",
"slash": "^3.0.0",
- "stack-utils": "^2.0.3"
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-message-util/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-message-util/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-mock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
- "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz",
+ "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "jest-util": "^29.7.0"
+ "jest-util": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-pnp-resolver": {
@@ -4947,153 +5379,189 @@
}
},
"node_modules/jest-regex-util": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
- "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-resolve": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
- "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.2.tgz",
+ "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "resolve": "^1.20.0",
- "resolve.exports": "^2.0.0",
- "slash": "^3.0.0"
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.0.2",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.0.2",
+ "jest-validate": "30.0.2",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-resolve-dependencies": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
- "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.4.tgz",
+ "integrity": "sha512-EQBYow19B/hKr4gUTn+l8Z+YLlP2X0IoPyp0UydOtrcPbIOYzJ8LKdFd+yrbwztPQvmlBFUwGPPEzHH1bAvFAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "jest-regex-util": "^29.6.3",
- "jest-snapshot": "^29.7.0"
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.0.4"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-runner": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
- "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.4.tgz",
+ "integrity": "sha512-mxY0vTAEsowJwvFJo5pVivbCpuu6dgdXRmt3v3MXjBxFly7/lTk3Td0PaMyGOeNQUFmSuGEsGYqhbn7PA9OekQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/environment": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/console": "30.0.4",
+ "@jest/environment": "30.0.4",
+ "@jest/test-result": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "chalk": "^4.0.0",
+ "chalk": "^4.1.2",
"emittery": "^0.13.1",
- "graceful-fs": "^4.2.9",
- "jest-docblock": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-leak-detector": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-resolve": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "jest-worker": "^29.7.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.0.1",
+ "jest-environment-node": "30.0.4",
+ "jest-haste-map": "30.0.2",
+ "jest-leak-detector": "30.0.2",
+ "jest-message-util": "30.0.2",
+ "jest-resolve": "30.0.2",
+ "jest-runtime": "30.0.4",
+ "jest-util": "30.0.2",
+ "jest-watcher": "30.0.4",
+ "jest-worker": "30.0.2",
"p-limit": "^3.1.0",
"source-map-support": "0.5.13"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-runtime": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
- "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.4.tgz",
+ "integrity": "sha512-tUQrZ8+IzoZYIHoPDQEB4jZoPyzBjLjq7sk0KVyd5UPRjRDOsN7o6UlvaGF8ddpGsjznl9PW+KRgWqCNO+Hn7w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/globals": "^29.7.0",
- "@jest/source-map": "^29.6.3",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/environment": "30.0.4",
+ "@jest/fake-timers": "30.0.4",
+ "@jest/globals": "30.0.4",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "chalk": "^4.0.0",
- "cjs-module-lexer": "^1.0.0",
- "collect-v8-coverage": "^1.0.0",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.0.2",
+ "jest-message-util": "30.0.2",
+ "jest-mock": "30.0.2",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.0.2",
+ "jest-snapshot": "30.0.4",
+ "jest-util": "30.0.2",
"slash": "^3.0.0",
"strip-bom": "^4.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz",
+ "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.0.4",
+ "@jest/get-type": "30.0.1",
+ "@jest/snapshot-utils": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
+ "babel-preset-current-node-syntax": "^1.1.0",
+ "chalk": "^4.1.2",
+ "expect": "30.0.4",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.0.4",
+ "jest-matcher-utils": "30.0.4",
+ "jest-message-util": "30.0.2",
+ "jest-util": "30.0.2",
+ "pretty-format": "30.0.2",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/jest-snapshot": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "node_modules/jest-snapshot/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@babel/generator": "^7.7.2",
- "@babel/plugin-syntax-jsx": "^7.7.2",
- "@babel/plugin-syntax-typescript": "^7.7.2",
- "@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0",
- "chalk": "^4.0.0",
- "expect": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "natural-compare": "^1.4.0",
- "pretty-format": "^29.7.0",
- "semver": "^7.5.3"
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-snapshot/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-snapshot/node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -5104,39 +5572,65 @@
}
},
"node_modules/jest-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz",
+ "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "graceful-fs": "^4.2.9",
- "picomatch": "^2.2.3"
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/jest-validate": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
- "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.2.tgz",
+ "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/types": "^29.6.3",
- "camelcase": "^6.2.0",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
+ "@jest/get-type": "30.0.1",
+ "@jest/types": "30.0.1",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
"leven": "^3.1.0",
- "pretty-format": "^29.7.0"
+ "pretty-format": "30.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/jest-validate/node_modules/camelcase": {
@@ -5152,40 +5646,63 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/jest-validate/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/jest-watcher": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
- "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.4.tgz",
+ "integrity": "sha512-YESbdHDs7aQOCSSKffG8jXqOKFqw4q4YqR+wHYpR5GWEQioGvL0BfbcjvKIvPEM0XGfsfJrka7jJz3Cc3gI4VQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
+ "@jest/test-result": "30.0.4",
+ "@jest/types": "30.0.1",
"@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
"emittery": "^0.13.1",
- "jest-util": "^29.7.0",
- "string-length": "^4.0.1"
+ "jest-util": "30.0.2",
+ "string-length": "^4.0.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-worker": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz",
+ "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
- "jest-util": "^29.7.0",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.0.2",
"merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
+ "supports-color": "^8.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-worker/node_modules/supports-color": {
@@ -5232,29 +5749,70 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "argparse": "^2.0.1"
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jsesc": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
- "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
- "node": ">=4"
+ "node": ">=6"
}
},
"node_modules/json-buffer": {
@@ -5308,16 +5866,6 @@
"json-buffer": "3.0.1"
}
},
- "node_modules/kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -5373,21 +5921,25 @@
}
},
"node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-locate": "^5.0.0"
+ "p-locate": "^4.1.0"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -5419,6 +5971,17 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -5446,9 +6009,9 @@
}
},
"node_modules/make-dir/node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -5516,6 +6079,16 @@
"node": ">=6"
}
},
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5600,6 +6173,22 @@
"picocolors": "^1.1.1"
}
},
+ "node_modules/napi-postinstall": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz",
+ "integrity": "sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5615,9 +6204,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
- "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
"license": "MIT"
},
@@ -5644,6 +6233,13 @@
"node": ">=8"
}
},
+ "node_modules/nwsapi": {
+ "version": "2.2.20",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+ "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5715,16 +6311,29 @@
}
},
"node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "p-limit": "^3.0.2"
+ "p-limit": "^2.2.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -5741,9 +6350,9 @@
}
},
"node_modules/package-json-from-dist": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
- "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
@@ -5779,6 +6388,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5809,13 +6431,6 @@
"node": ">=8"
}
},
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
@@ -5834,14 +6449,11 @@
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz",
- "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==",
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": "14 || >=16.14"
- }
+ "license": "ISC"
},
"node_modules/pathe": {
"version": "2.0.3",
@@ -5871,9 +6483,9 @@
}
},
"node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5893,62 +6505,6 @@
"node": ">=8"
}
},
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
@@ -5961,6 +6517,13 @@
"pathe": "^2.0.1"
}
},
+ "node_modules/platform": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/postcss-load-config": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
@@ -6015,9 +6578,9 @@
}
},
"node_modules/prettier": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
- "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -6044,18 +6607,19 @@
}
},
"node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@jest/schemas": "^29.6.3",
+ "ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
+ "react-is": "^17.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
@@ -6064,6 +6628,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -6071,20 +6636,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/prompts": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kleur": "^3.0.3",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6096,9 +6647,9 @@
}
},
"node_modules/pure-rand": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
"dev": true,
"funding": [
{
@@ -6133,12 +6684,36 @@
],
"license": "MIT"
},
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
"node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/readdirp": {
"version": "4.1.2",
@@ -6154,6 +6729,20 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/regexparam": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz",
@@ -6174,24 +6763,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
- "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -6205,7 +6776,7 @@
"node": ">=8"
}
},
- "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
@@ -6215,26 +6786,6 @@
"node": ">=8"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/resolve.exports": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
- "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -6247,9 +6798,9 @@
}
},
"node_modules/rollup": {
- "version": "4.38.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz",
- "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==",
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz",
+ "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6263,29 +6814,57 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.38.0",
- "@rollup/rollup-android-arm64": "4.38.0",
- "@rollup/rollup-darwin-arm64": "4.38.0",
- "@rollup/rollup-darwin-x64": "4.38.0",
- "@rollup/rollup-freebsd-arm64": "4.38.0",
- "@rollup/rollup-freebsd-x64": "4.38.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.38.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.38.0",
- "@rollup/rollup-linux-arm64-gnu": "4.38.0",
- "@rollup/rollup-linux-arm64-musl": "4.38.0",
- "@rollup/rollup-linux-loongarch64-gnu": "4.38.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.38.0",
- "@rollup/rollup-linux-riscv64-musl": "4.38.0",
- "@rollup/rollup-linux-s390x-gnu": "4.38.0",
- "@rollup/rollup-linux-x64-gnu": "4.38.0",
- "@rollup/rollup-linux-x64-musl": "4.38.0",
- "@rollup/rollup-win32-arm64-msvc": "4.38.0",
- "@rollup/rollup-win32-ia32-msvc": "4.38.0",
- "@rollup/rollup-win32-x64-msvc": "4.38.0",
+ "@rollup/rollup-android-arm-eabi": "4.42.0",
+ "@rollup/rollup-android-arm64": "4.42.0",
+ "@rollup/rollup-darwin-arm64": "4.42.0",
+ "@rollup/rollup-darwin-x64": "4.42.0",
+ "@rollup/rollup-freebsd-arm64": "4.42.0",
+ "@rollup/rollup-freebsd-x64": "4.42.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.42.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.42.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.42.0",
+ "@rollup/rollup-linux-arm64-musl": "4.42.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.42.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.42.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.42.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.42.0",
+ "@rollup/rollup-linux-x64-gnu": "4.42.0",
+ "@rollup/rollup-linux-x64-musl": "4.42.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.42.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.42.0",
+ "@rollup/rollup-win32-x64-msvc": "4.42.0",
"fsevents": "~2.3.2"
}
},
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.42.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz",
+ "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/rollup/node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6310,6 +6889,33 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -6344,18 +6950,17 @@
}
},
"node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/sisteransi": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
- "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
- "license": "MIT"
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/size-limit": {
"version": "11.2.0",
@@ -6454,21 +7059,37 @@
"node": ">=10"
}
},
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/string-length/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
@@ -6485,7 +7106,14 @@
"node": ">=8"
}
},
- "node_modules/strip-ansi": {
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@@ -6498,6 +7126,22 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
@@ -6512,6 +7156,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-ansi/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
"node_modules/strip-bom": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
@@ -6532,6 +7189,19 @@
"node": ">=6"
}
},
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -6568,56 +7238,6 @@
"node": ">=16 || 14 >=14.17"
}
},
- "node_modules/sucrase/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/sucrase/node_modules/glob": {
- "version": "10.4.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz",
- "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sucrase/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -6631,18 +7251,12 @@
"node": ">=8"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "license": "MIT"
},
"node_modules/synckit": {
"version": "0.11.8",
@@ -6675,6 +7289,28 @@
"node": ">=8"
}
},
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -6706,13 +7342,13 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
- "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fdir": "^6.4.3",
+ "fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
@@ -6723,9 +7359,9 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
- "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -6750,6 +7386,26 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -6767,17 +7423,33 @@
"is-number": "^7.0.0"
},
"engines": {
- "node": ">=8.0"
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
}
},
"node_modules/tr46": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
- "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "punycode": "^2.1.0"
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/tree-kill": {
@@ -6811,16 +7483,15 @@
"license": "Apache-2.0"
},
"node_modules/ts-jest": {
- "version": "29.3.4",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz",
- "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==",
+ "version": "29.4.0",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz",
+ "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"bs-logger": "^0.2.6",
"ejs": "^3.1.10",
"fast-json-stable-stringify": "^2.1.0",
- "jest-util": "^29.0.0",
"json5": "^2.2.3",
"lodash.memoize": "^4.1.2",
"make-error": "^1.3.6",
@@ -6836,10 +7507,11 @@
},
"peerDependencies": {
"@babel/core": ">=7.0.0-beta.0 <8",
- "@jest/transform": "^29.0.0",
- "@jest/types": "^29.0.0",
- "babel-jest": "^29.0.0",
- "jest": "^29.0.0",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
"typescript": ">=4.3 <6"
},
"peerDependenciesMeta": {
@@ -6857,6 +7529,9 @@
},
"esbuild": {
"optional": true
+ },
+ "jest-util": {
+ "optional": true
}
}
},
@@ -6873,50 +7548,17 @@
"node": ">=10"
}
},
- "node_modules/ts-node": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
},
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tslib": {
@@ -6979,16 +7621,6 @@
}
}
},
- "node_modules/tsup/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/tsup/node_modules/source-map": {
"version": "0.8.0-beta.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
@@ -7002,6 +7634,35 @@
"node": ">= 8"
}
},
+ "node_modules/tsup/node_modules/tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/tsup/node_modules/webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/tsup/node_modules/whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -7026,13 +7687,13 @@
}
},
"node_modules/type-fest": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
- "node": ">=16"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -7053,15 +7714,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.33.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz",
- "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==",
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz",
+ "integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.33.0",
- "@typescript-eslint/parser": "8.33.0",
- "@typescript-eslint/utils": "8.33.0"
+ "@typescript-eslint/eslint-plugin": "8.36.0",
+ "@typescript-eslint/parser": "8.36.0",
+ "@typescript-eslint/utils": "8.36.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7083,16 +7744,51 @@
"license": "MIT"
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true,
"license": "MIT"
},
+ "node_modules/unrs-resolver": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.0.tgz",
+ "integrity": "sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.0",
+ "@unrs/resolver-binding-android-arm64": "1.11.0",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.0",
+ "@unrs/resolver-binding-darwin-x64": "1.11.0",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.0",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.0",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.0",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.0",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.0",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.0",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.0",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.0",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.0",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.0",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.0",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.0",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.0",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.0",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.0"
+ }
+ },
"node_modules/update-browserslist-db": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
- "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [
{
@@ -7110,8 +7806,8 @@
],
"license": "MIT",
"dependencies": {
- "escalade": "^3.1.2",
- "picocolors": "^1.0.1"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -7130,15 +7826,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true
- },
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
@@ -7154,6 +7841,19 @@
"node": ">=10.12.0"
}
},
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -7165,22 +7865,50 @@
}
},
"node_modules/webidl-conversions": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
- "license": "BSD-2-Clause"
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/whatwg-url": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
- "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/which": {
@@ -7210,18 +7938,18 @@
}
},
"node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -7246,6 +7974,54 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -7254,19 +8030,58 @@
"license": "ISC"
},
"node_modules/write-file-atomic": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
- "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.7"
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
},
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ "node": ">=18"
}
},
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -7313,16 +8128,39 @@
"node": ">=12"
}
},
- "node_modules/yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
- "optional": true,
- "peer": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/yocto-queue": {
diff --git a/package.json b/package.json
index 2952ef0d..255c79bb 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,22 @@
"module": "dist/browser/index.mjs",
"types": "dist/index.d.ts",
"unpkg": "./dist/browser/index.mjs",
+ "exports": {
+ ".": {
+ "import": {
+ "node": "./dist/node/index.js",
+ "default": "./dist/browser/index.mjs"
+ },
+ "require": {
+ "node": "./dist/node/index.js",
+ "default": "./dist/browser/index.global.js"
+ }
+ },
+ "./react": {
+ "import": "./dist/react/index.mjs",
+ "require": "./dist/react/index.cjs"
+ }
+ },
"keywords": [
"fetch",
"fetchff",
@@ -34,9 +50,7 @@
},
"sideEffects": false,
"scripts": {
- "build": "npm run build:node && npm run build:browser && npm run build:cleanup",
- "build:browser": "tsup --format esm,iife --out-dir dist/browser --env.NODE_ENV production",
- "build:node": "tsup --format cjs --out-dir dist/node --env.NODE_ENV production --target node18",
+ "build": "tsup --config tsup.config.ts && npm run build:cleanup",
"build:cleanup": "rm -f dist/browser/index.d.mts dist/node/index.d.ts && mv dist/browser/index.d.ts dist/index.d.ts",
"type-check": "tsc --noEmit",
"test": "jest --forceExit --coverage --detectOpenHandles",
@@ -55,32 +69,48 @@
"size-limit": [
{
"path": "dist/browser/index.mjs",
- "limit": "5 KB"
+ "limit": "5.5 KB"
},
{
"path": "dist/browser/index.global.js",
- "limit": "5 KB"
+ "limit": "5.6 KB"
},
{
"path": "dist/node/index.js",
- "limit": "5 KB"
+ "limit": "5.5 KB"
+ },
+ {
+ "path": "dist/react/index.mjs",
+ "limit": "9.5 KB"
+ },
+ {
+ "path": "dist/react/index.js",
+ "limit": "9.5 KB"
}
],
"devDependencies": {
"@size-limit/preset-small-lib": "11.2.0",
- "@types/jest": "29.5.14",
- "eslint": "9.28.0",
+ "@testing-library/jest-dom": "6.6.3",
+ "@testing-library/react": "16.3.0",
+ "@types/jest": "30.0.0",
+ "@types/react": "19.1.8",
+ "benchmark": "2.1.4",
+ "eslint": "9.30.1",
"eslint-config-prettier": "10.1.5",
- "eslint-plugin-prettier": "5.4.1",
- "fetch-mock": "12.5.2",
- "jest": "29.7.0",
- "prettier": "3.5.3",
+ "eslint-plugin-prettier": "5.5.1",
+ "fetch-mock": "12.5.3",
+ "globals": "16.3.0",
+ "jest": "30.0.4",
+ "jest-environment-jsdom": "30.0.4",
+ "prettier": "3.6.2",
+ "react": "19.1.0",
+ "react-dom": "19.1.0",
"size-limit": "11.2.0",
- "ts-jest": "29.3.4",
+ "ts-jest": "29.4.0",
"tslib": "2.8.1",
"tsup": "8.5.0",
"typescript": "5.8.3",
- "typescript-eslint": "8.33.0"
+ "typescript-eslint": "8.36.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.38.0"
diff --git a/src/api-handler.ts b/src/api-handler.ts
index 9e932db5..1310e4a2 100644
--- a/src/api-handler.ts
+++ b/src/api-handler.ts
@@ -1,54 +1,23 @@
-import type {
- RequestConfig,
- FetchResponse,
- DefaultResponse,
- CreatedCustomFetcherInstance,
-} from './types/request-handler';
import type {
ApiHandlerConfig,
ApiHandlerDefaultMethods,
ApiHandlerMethods,
- DefaultPayload,
- FallbackValue,
- FinalParams,
- FinalResponse,
- QueryParams,
RequestConfigUrlRequired,
- UrlPathParams,
} from './types/api-handler';
-import { createRequestHandler } from './request-handler';
import { fetchf } from '.';
-import { mergeConfig } from './config-handler';
+import { mergeConfigs } from './config-handler';
+import { isAbsoluteUrl } from './utils';
/**
* Creates an instance of API Handler.
- * It creates an API fetcher function using native fetch() or a custom fetcher if it is passed as "fetcher".
- * @url https://github.com/MattCCC/fetchff
+ * It creates an API fetcher function using native fetch() or a custom fetcher if passed as "fetcher".
+ * @see https://github.com/MattCCC/fetchff#configuration
*
- * @param {Object} config - Configuration object for the API fetcher.
- * @param {string} config.apiUrl - The base URL for the API.
+ * @param {Object} config - Configuration object for the API fetcher (see link above for full options).
* @param {Object} config.endpoints - An object containing endpoint definitions.
- * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.
- * @param {number} config.cancellable - If true, the ongoing previous requests will be automatically cancelled.
- * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.
- * @param {number} config.timeout - Request timeout
- * @param {number} config.dedupeTime - Time window, in milliseconds, during which identical requests are deduplicated (treated as single request).
- * @param {string} config.strategy - Error Handling Strategy
- * @param {string} config.flattenResponse - Whether to flatten response "data" object within "data". It works only if the response structure includes a single data property.
- * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's "null" by default
- * @param {Object} [config.retry] - Options for retrying requests.
- * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.
- * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.
- * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.
- * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.
- * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.
- * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.
- * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.
- * These functions will be called with the response object after the response is received. an be used to modify or log the response data.
- * @param {Function} [config.onError] - Optional callback function for handling errors.
+ * @param {string} [config.baseURL] - The base URL for the API.
* @param {Object} [config.headers] - Optional default headers to include in every request.
- * @param {Object} config.fetcher - The Custom Fetcher instance to use for making requests. It should expose create() and request() functions.
- * @param {*} config.logger - Instance of custom logger. Either class or an object similar to "console". Console is used by default.
+ * @param {Function} [config.onError] - Optional callback function for handling errors.
* @returns API handler functions and endpoints to call
*
* @example
@@ -74,20 +43,10 @@ import { mergeConfig } from './config-handler';
* const response = await api.getUser({ userId: 1, ratings: [1, 2] })
*/
function createApiFetcher<
- EndpointsMethods extends object,
+ EndpointTypes extends object,
EndpointsSettings = never,
->(config: ApiHandlerConfig) {
+>(config: ApiHandlerConfig) {
const endpoints = config.endpoints;
- const requestHandler = createRequestHandler(config);
-
- /**
- * Get Custom Fetcher Provider Instance
- *
- * @returns {CreatedCustomFetcherInstance | null} Request Handler's Custom Fetcher Instance
- */
- function getInstance(): CreatedCustomFetcherInstance | null {
- return requestHandler.getInstance();
- }
/**
* Triggered when trying to use non-existent endpoints
@@ -101,71 +60,42 @@ function createApiFetcher<
return Promise.resolve(null);
}
- /**
- * Handle Single API Request
- * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.
- *
- * @param {keyof EndpointsMethods | string} endpointName - The name of the API endpoint to call.
- * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.
- * @returns {Promise>} - A promise that resolves with the response from the API provider.
- */
- async function request<
- ResponseData = never,
- QueryParams_ = never,
- UrlParams = never,
- RequestBody = never,
- >(
- endpointName: keyof EndpointsMethods | string,
- requestConfig: RequestConfig<
- FinalResponse,
- FinalParams,
- FinalParams,
- FallbackValue
- > = {},
- ): Promise>> {
- // Use global per-endpoint settings
- const endpointConfig =
- endpoints[endpointName] ||
- ({ url: String(endpointName) } as RequestConfigUrlRequired);
- const url = endpointConfig.url;
-
- // Block Protocol-relative URLs as they could lead to SSRF (Server-Side Request Forgery)
- if (url.startsWith('//')) {
- throw new Error('Protocol-relative URLs are not allowed.');
- }
-
- // Prevent potential Server-Side Request Forgery attack and leakage of credentials when same instance is used for external requests
- const isAbsoluteUrl = url.includes('://');
-
- if (isAbsoluteUrl) {
- // Retrigger fetch to ensure completely new instance of handler being triggered for external URLs
- return await fetchf(url, requestConfig);
- }
-
- const mergedConfig = {
- ...endpointConfig,
- ...requestConfig,
- };
-
- mergeConfig('retry', mergedConfig, endpointConfig, requestConfig);
- mergeConfig('headers', mergedConfig, endpointConfig, requestConfig);
-
- const responseData = await requestHandler.request<
- FinalResponse,
- FinalParams,
- FinalParams,
- FallbackValue
- >(url, mergedConfig);
-
- return responseData;
- }
-
- const apiHandler: ApiHandlerDefaultMethods = {
+ const apiHandler: ApiHandlerDefaultMethods = {
config,
endpoints,
- requestHandler,
- getInstance,
- request,
+ /**
+ * Handle Single API Request
+ * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.
+ *
+ * @param endpointName - The name of the API endpoint to call.
+ * @param requestConfig - Additional configuration for the request.
+ * @returns A promise that resolves with the response from the API provider.
+ */
+ async request(endpointName, requestConfig = {}) {
+ // Use global and per-endpoint settings
+ const endpointConfig = endpoints[endpointName];
+ const _endpointConfig =
+ endpointConfig ||
+ ({ url: String(endpointName) } as RequestConfigUrlRequired);
+ const url = _endpointConfig.url;
+
+ // Block Protocol-relative URLs as they could lead to SSRF (Server-Side Request Forgery)
+ if (url.startsWith('//')) {
+ throw new Error('Protocol-relative URLs are not allowed.');
+ }
+
+ // Prevent potential Server-Side Request Forgery attack and leakage of credentials when same instance is used for external requests
+ const mergedConfig = isAbsoluteUrl(url)
+ ? // Merge endpoints configs for absolute URLs only if urls match
+ endpointConfig?.url === url
+ ? mergeConfigs(_endpointConfig, requestConfig)
+ : requestConfig
+ : mergeConfigs(mergeConfigs(config, _endpointConfig), requestConfig);
+
+ // We prevent potential Server-Side Request Forgery attack and leakage of credentials as the same instance is not used for external requests
+ // Retrigger fetch to ensure completely new instance of handler being triggered for external URLs
+ return fetchf(url, mergedConfig);
+ },
};
/**
@@ -173,8 +103,8 @@ function createApiFetcher<
*
* @param {*} prop Caller
*/
- return new Proxy>(
- apiHandler as ApiHandlerMethods,
+ return new Proxy>(
+ apiHandler as ApiHandlerMethods,
{
get(_target, prop: string) {
if (prop in apiHandler) {
diff --git a/src/cache-manager.ts b/src/cache-manager.ts
index 31d68cba..02b0bbdd 100644
--- a/src/cache-manager.ts
+++ b/src/cache-manager.ts
@@ -1,31 +1,82 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { hash } from './hash';
-import { fetchf } from './index';
-import type { FetcherConfig, FetchResponse } from './types/request-handler';
+import type {
+ CacheKeyFunction,
+ DefaultResponse,
+ FetchResponse,
+ MutationSettings,
+ RequestConfig,
+} from './types/request-handler';
import type { CacheEntry } from './types/cache-manager';
-import { GET, OBJECT, UNDEFINED } from './constants';
-import { shallowSerialize, sortObject } from './utils';
+import { GET, STRING, UNDEFINED } from './constants';
+import { isObject, sanitizeObject, sortObject, timeNow } from './utils';
+import { revalidate } from './revalidator-manager';
+import { notifySubscribers } from './pubsub-manager';
+import type { DefaultPayload, DefaultParams, DefaultUrlParams } from './types';
+import { removeInFlight } from './inflight-manager';
+import { addTimeout } from './timeout-wheel';
+import { defaultConfig } from './config-handler';
+import { processHeaders } from './utils';
-const cache = new Map>();
+export const IMMEDIATE_DISCARD_CACHE_TIME = 0; // Use it for cache entries that need to be persistent until unused by components or manually deleted
+
+const _cache = new Map>();
const DELIMITER = '|';
const MIN_LENGTH_TO_HASH = 64;
+const CACHE_KEY_SANITIZE_PATTERN = new RegExp('[^\\w\\-_|]', 'g');
+
+/**
+ * Headers that may affect HTTP response content and should be included in cache key generation.
+ * All header names must be lowercase to match normalized request headers.
+ */
+const CACHE_KEY_HEADER_WHITELIST = new Set([
+ // Content negotiation
+ 'accept', // Affects response format (e.g. JSON, HTML)
+ 'accept-language', // Affects localization of the response
+ 'accept-encoding', // Affects response compression (e.g. gzip, br)
+
+ // Authentication
+ 'authorization', // Affects access to protected resources
+
+ // Request body metadata
+ 'content-type', // Affects how the request body is interpreted
+
+ // Optional headers
+ 'referer', // May influence behavior in some APIs
+ 'origin', // Relevant in CORS or tenant-specific APIs
+ 'user-agent', // Included only for reason if server returns client-specific content
+
+ // Cookies â only if server uses session-based responses
+ 'cookie', // Can fragment cache heavily; use only if necessary
+
+ // Custom headers that may affect response content
+ 'x-api-key', // Token-based access, often affects authorization
+ 'x-requested-with', // AJAX requests (used historically for distinguishing frontend calls)
+ 'x-client-id', // Per-client/partner identity; often used in multi-tenant APIs
+ 'x-tenant-id', // Multi-tenant segmentation; often changes response per tenant
+ 'x-user-id', // Explicit user context (less common, but may exist)
+
+ 'x-app-version', // Used for version-specific behavior (e.g. mobile apps)
+ 'x-feature-flag', // Controls feature rollout behavior server-side
+ 'x-device-id', // Used when response varies per device/app instance
+ 'x-platform', // e.g. 'ios', 'android', 'web' â used in apps that serve different content
+
+ 'x-session-id', // Only if backend uses it to affect the response directly (rare)
+ 'x-locale', // Sometimes used in addition to or instead of `accept-language`
+]);
/**
* Generates a unique cache key for a given URL and fetch options, ensuring that key factors
* like method, headers, body, and other options are included in the cache key.
* Headers and other objects are sorted by key to ensure consistent cache keys.
*
- * @param options - The fetch options that may affect the request. The most important are:
+ * @param {RequestConfig} config - The fetch options that may affect the request. The most important are:
* @property {string} [method="GET"] - The HTTP method (GET, POST, etc.).
* @property {HeadersInit} [headers={}] - The request headers.
* @property {BodyInit | null} [body=""] - The body of the request (only for methods like POST, PUT).
- * @property {RequestMode} [mode="cors"] - The mode for the request (e.g., cors, no-cors, include).
- * @property {RequestCredentials} [credentials="include"] - Whether to include credentials like cookies.
+ * @property {RequestCredentials} [credentials="same-origin"] - Whether to include credentials (include, same-origin, omit).
* @property {RequestCache} [cache="default"] - The cache mode (e.g., default, no-store, reload).
- * @property {RequestRedirect} [redirect="follow"] - How to handle redirects (e.g., follow, error, manual).
- * @property {string} [referrer=""] - The referrer URL to send with the request.
- * @property {string} [integrity=""] - Subresource integrity value (a cryptographic hash for resource validation).
- * @returns {string} - A unique cache key based on the URL and request options. Empty if cache is to be burst.
+ * @returns {string} - A unique cache key string based on the provided options.
*
* @example
* const cacheKey = generateCacheKey({
@@ -38,42 +89,76 @@ const MIN_LENGTH_TO_HASH = 64;
* });
* console.log(cacheKey);
*/
-export function generateCacheKey(options: FetcherConfig): string {
+export function generateCacheKey(
+ config: RequestConfig,
+ cacheKeyCheck = true,
+): string {
+ // This is super fast. Effectively a no-op if cacheKey is
+ // a string or a function that returns a string.
+ const key = config.cacheKey;
+
+ if (key && cacheKeyCheck) {
+ return typeof key === STRING
+ ? (key as string)
+ : (key as CacheKeyFunction)(config);
+ }
+
const {
url = '',
method = GET,
headers = null,
- body = undefined,
- mode = 'cors',
+ body = null,
credentials = 'same-origin',
- cache = 'default',
- redirect = 'follow',
- referrer = 'about:client',
- integrity = '',
- } = options;
-
- // Bail early if cache should be burst
- if (cache === 'reload') {
- return '';
- }
+ } = config;
// Sort headers and body + convert sorted to strings for hashing purposes
// Native serializer is on avg. 3.5x faster than a Fast Hash or FNV-1a
let headersString = '';
if (headers) {
- const obj =
- headers instanceof Headers
- ? Object.fromEntries((headers as any).entries())
- : headers;
- headersString = shallowSerialize(sortObject(obj));
- if (headersString.length > MIN_LENGTH_TO_HASH) {
- headersString = hash(headersString);
+ let obj: Record;
+
+ if (headers instanceof Headers) {
+ obj = processHeaders(headers);
+ } else {
+ obj = headers as Record;
+ }
+
+ // Filter headers to only include those that affect request identity
+ // Include only headers that affect request identity, not execution behavior
+ const keys = Object.keys(obj);
+ const len = keys.length;
+
+ // Sort keys manually for fastest deterministic output
+ if (len > 1) {
+ keys.sort();
}
+
+ let str = '';
+ for (let i = 0; i < len; ++i) {
+ if (CACHE_KEY_HEADER_WHITELIST.has(keys[i].toLowerCase())) {
+ str += keys[i] + ':' + obj[keys[i]] + ';';
+ }
+ }
+
+ headersString = hash(str);
+ }
+
+ // For GET requests, return early with shorter cache key
+ if (method === GET) {
+ return (
+ method +
+ DELIMITER +
+ url +
+ DELIMITER +
+ credentials +
+ DELIMITER +
+ headersString
+ ).replace(CACHE_KEY_SANITIZE_PATTERN, '');
}
let bodyString = '';
if (body) {
- if (typeof body === 'string') {
+ if (typeof body === STRING) {
bodyString = body.length < MIN_LENGTH_TO_HASH ? body : hash(body); // hash only if large
} else if (body instanceof FormData) {
body.forEach((value, key) => {
@@ -92,10 +177,9 @@ export function generateCacheKey(options: FetcherConfig): string {
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
bodyString = 'AB' + body.byteLength;
} else {
- const o =
- typeof body === OBJECT
- ? JSON.stringify(sortObject(body))
- : String(body);
+ const o = isObject(body)
+ ? JSON.stringify(sortObject(body))
+ : String(body);
bodyString = o.length > MIN_LENGTH_TO_HASH ? hash(o) : o;
}
@@ -108,101 +192,120 @@ export function generateCacheKey(options: FetcherConfig): string {
DELIMITER +
url +
DELIMITER +
- mode +
credentials +
- cache +
- redirect +
- referrer +
- integrity +
DELIMITER +
headersString +
DELIMITER +
bodyString
- ).replace(/[^\w\-_|]/g, ''); // Prevent cache poisoning by removal of anything that isn't letters, numbers, -, _, or |
+ ).replace(CACHE_KEY_SANITIZE_PATTERN, ''); // Prevent cache poisoning by removal of anything that isn't letters, numbers, -, _, or |
}
/**
- * Checks if the cache entry is expired based on its timestamp and the maximum stale time.
+ * Checks if the cache entry is expired based on its timestamp and the expiry time.
*
- * @param {number} timestamp - The timestamp of the cache entry.
- * @param {number} maxStaleTime - The maximum stale time in seconds.
+ * @param {CacheEntry} entry - The cache entry to check.
* @returns {boolean} - Returns true if the cache entry is expired, false otherwise.
*/
-function isCacheExpired(timestamp: number, maxStaleTime: number): boolean {
- if (!maxStaleTime) {
+function isCacheExpired(entry: CacheEntry): boolean {
+ // No expiry time means the entry never expires
+ if (!entry.expiry) {
return false;
}
- return Date.now() - timestamp > maxStaleTime * 1000;
+ return timeNow() > entry.expiry;
}
/**
- * Retrieves a cache entry if it exists and is not expired.
+ * Checks if the cache entry is stale based on its timestamp and the stale time.
*
- * @param {string} key Cache key to utilize
- * @param {number} cacheTime - Maximum time to cache entry in seconds.
- * @returns {CacheEntry | null} - The cache entry if it exists and is not expired, null otherwise.
+ * @param {CacheEntry} entry - The cache entry to check.
+ * @returns {boolean} - Returns true if the cache entry is stale, false otherwise.
*/
-export function getCache(
- key: string,
- cacheTime: number,
-): CacheEntry | null {
- const entry = cache.get(key);
+function isCacheStale(entry: CacheEntry): boolean {
+ if (!entry.stale) {
+ return false;
+ }
- if (entry) {
- if (!isCacheExpired(entry.timestamp, cacheTime)) {
- return entry;
- }
+ return timeNow() > entry.stale;
+}
- deleteCache(key);
+/**
+ * Retrieves a cached response from the internal cache using the provided key.
+ *
+ * @param key - The unique key identifying the cached entry. If null, returns null.
+ * @returns The cached {@link FetchResponse} if found, otherwise null.
+ */
+export function getCacheData<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams,
+>(
+ key: string | null,
+): FetchResponse | null {
+ if (!key) {
+ return null;
}
- return null;
+ const entry = _cache.get(key);
+
+ return entry ? entry.data : null;
}
/**
- * Sets a new cache entry or updates an existing one.
+ * Retrieves a cache entry if it exists and is not expired.
+ *
+ * @param {string} key Cache key to utilize
+ * @returns {CacheEntry | null} - The cache entry if it exists and is not expired, null otherwise.
+ */
+export function getCache(
+ key: string | null,
+):
+ | CacheEntry<
+ FetchResponse
+ >
+ | null
+ | undefined {
+ return _cache.get(key as string);
+}
+
+/**
+ * Sets a new cache entry or updates an existing one, with optional TTL (time-to-live).
*
* @param {string} key Cache key to utilize
* @param {T} data - The data to be cached.
- * @param {boolean} isLoading - Indicates if the data is currently being fetched.
+ * @param {number} [ttl] - Optional TTL in seconds. If not provided, the cache entry will not expire.
+ * @param {number} [staleTime] - Optional stale time in seconds. If provided, the cache entry will be considered stale after this time.
*/
export function setCache(
key: string,
data: T,
- isLoading: boolean = false,
+ ttl?: number,
+ staleTime?: number,
): void {
- cache.set(key, {
+ if (ttl === 0) {
+ deleteCache(key);
+ return;
+ }
+
+ const time = timeNow();
+ const ttlMs = ttl ? ttl * 1000 : 0;
+
+ _cache.set(key, {
data,
- isLoading,
- timestamp: Date.now(),
+ time,
+ stale: staleTime && staleTime > 0 ? time + staleTime * 1000 : staleTime,
+ expiry: ttl === -1 ? undefined : time + ttlMs,
});
-}
-/**
- * Revalidates a cache entry by fetching fresh data and updating the cache.
- *
- * @param {string} key Cache key to utilize
- * @param {FetcherConfig} config - The request configuration object.
- * @returns {Promise} - A promise that resolves when the revalidation is complete.
- */
-export async function revalidate(
- key: string,
- config: FetcherConfig,
-): Promise {
- try {
- // Fetch fresh data
- const newData = await fetchf(config.url, {
- ...config,
- cache: 'reload',
- });
-
- setCache(key, newData);
- } catch (error) {
- console.error(`Error revalidating ${config.url}:`, error);
-
- // Rethrow the error to forward it
- throw error;
+ if (ttlMs > 0) {
+ addTimeout(
+ 'c:' + key,
+ () => {
+ deleteCache(key, true);
+ },
+ ttlMs,
+ );
}
}
@@ -210,42 +313,83 @@ export async function revalidate(
* Invalidates (deletes) a cache entry.
*
* @param {string} key Cache key to utilize
+ * @param {boolean} [removeExpired=false] - If true, only deletes the cache entry if it is expired or stale.
*/
-export function deleteCache(key: string): void {
- cache.delete(key);
+export function deleteCache(key: string, removeExpired: boolean = false): void {
+ if (removeExpired) {
+ const entry = getCache(key);
+
+ // If the entry does not exist, or it is neither expired nor stale, do not delete
+ if (!entry || !isCacheExpired(entry)) {
+ return;
+ }
+ }
+
+ _cache.delete(key);
}
/**
* Prunes the cache by removing entries that have expired based on the provided cache time.
- * @param cacheTime - The maximum time to cache entry.
*/
-export function pruneCache(cacheTime: number) {
- cache.forEach((entry, key) => {
- if (isCacheExpired(entry.timestamp, cacheTime)) {
- cache.delete(key);
- }
- });
+export function pruneCache(): void {
+ _cache.clear();
}
/**
* Mutates a cache entry with new data and optionally revalidates it.
*
- * @param {string} key Cache key to utilize
- * @param {FetcherConfig} config - The request configuration object.
- * @param {T} newData - The new data to be cached.
- * @param {boolean} revalidateAfter - If true, triggers revalidation after mutation.
+ * @param {string | null} key Cache key to utilize. If null, no mutation occurs.
+ * @param {ResponseData} newData - The new data to be cached.
+ * @param {MutationSettings|undefined} settings - Mutation settings.
*/
-export function mutate(
- key: string,
- config: FetcherConfig,
- newData: T,
- revalidateAfter: boolean = false,
-): void {
- setCache(key, newData);
+export async function mutate<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ key: string | null,
+ newData: ResponseData,
+ settings?: MutationSettings,
+): Promise | null> {
+ // If no key is provided, do nothing
+ if (!key) {
+ return null;
+ }
+
+ const entry = getCache(
+ key,
+ );
- if (revalidateAfter) {
- revalidate(key, config);
+ if (!entry) {
+ return null;
}
+
+ const updatedData = isObject(newData) ? sanitizeObject(newData) : newData;
+
+ const updatedResponse = {
+ ...entry.data,
+ data: updatedData,
+ };
+
+ const updatedEntry = {
+ ...entry,
+ data: updatedResponse,
+ };
+
+ _cache.set(key, updatedEntry);
+ notifySubscribers(key, updatedResponse);
+
+ if (settings && settings.refetch) {
+ return await revalidate(key);
+ }
+
+ return null;
}
/**
@@ -257,8 +401,7 @@ export function mutate(
* @template PathParams - The type of the path parameters.
* @param {string | null} cacheKey - The cache key to look up.
* @param {number | undefined} cacheTime - The maximum time to cache entry.
- * @param {(cfg: any) => boolean | undefined} cacheBuster - Optional function to determine if cache should be bypassed.
- * @param {FetcherConfig} fetcherConfig - The fetcher configuration.
+ * @param {RequestConfig} requestConfig - The fetcher configuration.
* @returns {FetchResponse | null} - The cached response or null.
*/
export function getCachedResponse<
@@ -269,8 +412,7 @@ export function getCachedResponse<
>(
cacheKey: string | null,
cacheTime: number | undefined,
- cacheBuster: ((cfg: any) => boolean) | undefined,
- fetcherConfig: FetcherConfig<
+ requestConfig: RequestConfig<
ResponseData,
QueryParams,
PathParams,
@@ -278,20 +420,98 @@ export function getCachedResponse<
>,
): FetchResponse | null {
// If cache key or time is not provided, return null
- if (!cacheTime || !cacheKey) {
+ if (!cacheKey || cacheTime === undefined || cacheTime === null) {
return null;
}
// Check if cache should be bypassed
- if (cacheBuster?.(fetcherConfig)) {
+ const buster = requestConfig.cacheBuster || defaultConfig.cacheBuster;
+ if (buster && buster(requestConfig)) {
return null;
}
+ if (requestConfig.cache && requestConfig.cache === 'reload') {
+ return null; // Skip cache lookup entirely
+ }
+
// Retrieve the cached entry
- const cachedEntry = getCache<
- FetchResponse
- >(cacheKey, cacheTime);
+ const entry = getCache(
+ cacheKey,
+ );
+
+ if (!entry) {
+ return null;
+ }
+
+ const isExpired = isCacheExpired(entry);
+ const isStale = isCacheStale(entry);
+
+ // If completely expired, delete and return null
+ if (isExpired) {
+ deleteCache(cacheKey);
+ return null;
+ }
+
+ // If fresh (not stale), return immediately
+ if (!isStale) {
+ return entry.data;
+ }
+
+ // SWR: Data is stale but not expired
+ if (isStale && !isExpired) {
+ // Triggering background revalidation here could cause race conditions
+ // So we return stale data immediately and leave it up to implementers to handle revalidation
+ return entry.data;
+ }
+
+ return null;
+}
- // If no cached entry or it is expired, return null
- return cachedEntry ? cachedEntry.data : null;
+/**
+ * Sets or deletes the response cache based on cache settings and notifies subscribers.
+ *
+ * @param {FetchResponse} output - The response to cache.
+ * @param {RequestConfig} requestConfig - The request configuration.
+ * @param {boolean} [isError=false] - Whether the response is an error.
+ */
+export function handleResponseCache<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ output: FetchResponse,
+ requestConfig: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ >,
+ isError: boolean = false,
+): void {
+ // It is string as it is called once request is made
+ const cacheKey = requestConfig.cacheKey as string;
+
+ if (cacheKey) {
+ const cacheTime = requestConfig.cacheTime;
+ const skipCache = requestConfig.skipCache;
+
+ // Fast path: only set cache if cacheTime is positive and not skipping cache
+ if (
+ cacheTime &&
+ (!isError || requestConfig.cacheErrors) &&
+ !(skipCache && skipCache(output, requestConfig))
+ ) {
+ setCache(cacheKey, output, cacheTime, requestConfig.staleTime);
+ }
+
+ notifySubscribers(cacheKey, output);
+ removeInFlight(cacheKey);
+
+ const prevCacheKey = requestConfig._prevKey;
+
+ if (prevCacheKey) {
+ removeInFlight(prevCacheKey);
+ }
+ }
}
diff --git a/src/config-handler.ts b/src/config-handler.ts
index 6479af8b..f2534cdd 100644
--- a/src/config-handler.ts
+++ b/src/config-handler.ts
@@ -5,35 +5,38 @@ import {
STRING,
CHARSET_UTF_8,
CONTENT_TYPE,
- OBJECT,
+ REJECT,
+ UNDEFINED,
+ APPLICATION_CONTENT_TYPE,
} from './constants';
import type {
- FetcherConfig,
HeadersObject,
Method,
RequestConfig,
- RequestHandlerConfig,
} from './types/request-handler';
import {
replaceUrlPathParams,
appendQueryParams,
isSearchParams,
isJSONSerializable,
+ isSlowConnection,
+ isAbsoluteUrl,
+ sanitizeObject,
+ isObject,
} from './utils';
-export const defaultConfig: RequestHandlerConfig = {
- method: GET,
- strategy: 'reject',
- timeout: 30000, // 30 seconds
- dedupeTime: 0,
- defaultResponse: null,
+const defaultTimeoutMs = (isSlowConnection() ? 60 : 30) * 1000;
+
+export const defaultConfig: RequestConfig = {
+ strategy: REJECT,
+ timeout: defaultTimeoutMs, // 30 seconds (60 on slow connections)
headers: {
Accept: APPLICATION_JSON + ', text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br',
},
retry: {
- delay: 1000,
- maxDelay: 30000,
+ delay: defaultTimeoutMs / 30, // 1 second (2 on slow connections)
+ maxDelay: defaultTimeoutMs, // 30 seconds (60 on slow connections)
resetTimeout: true,
backoff: 1.5,
@@ -52,28 +55,83 @@ export const defaultConfig: RequestHandlerConfig = {
};
/**
- * Build request configuration
+ * Overwrites the default configuration with the provided custom configuration.
+ *
+ * @param {Partial} customConfig - The custom configuration to merge into the default config.
+ * @returns {Partial} - The updated default configuration object.
+ */
+export function setDefaultConfig(
+ customConfig: Partial,
+): Partial {
+ const sanitized = sanitizeObject(customConfig);
+
+ Object.assign(defaultConfig, sanitized);
+
+ return defaultConfig;
+}
+
+/**
+ * Returns a shallow copy of the current default configuration.
*
+ * @returns {RequestConfig} - The current default configuration.
+ */
+export function getDefaultConfig(): RequestConfig {
+ return { ...defaultConfig };
+}
+
+/**
+ * Build request configuration from defaults and overrides.
+ * This function merges the default configuration with the provided request configuration,
* @param {string} url - Request url
- * @param {RequestConfig} requestConfig - Request config passed when making the request
- * @returns {FetcherConfig} - Provider's instance
+ * @param {RequestConfig | null | undefined} reqConfig - Request configuration
+ * @return {RequestConfig} - Merged request configuration
*/
-export const buildConfig = (
+export function buildConfig(
+ url: string,
+ reqConfig?: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ > | null,
+): RequestConfig {
+ if (!reqConfig) {
+ return buildFetcherConfig(url, getDefaultConfig());
+ }
+
+ const sanitized = sanitizeObject(reqConfig);
+ const merged = mergeConfigs(defaultConfig, sanitized);
+
+ return buildFetcherConfig(url, merged);
+}
+
+/**
+ * Builds the fetcher configuration by setting the method, body, headers, and URL.
+ * It also handles query parameters and path parameters. This fn mutates the passed `requestConfig` object.
+ * @param {string} url - The endpoint URL to which the request will be sent.
+ * @param {RequestConfig} requestConfig - The request configuration object containing method, body, headers, and other options.
+ * @return {RequestConfig} - The modified request configuration object with the URL, method, body, and headers set appropriately.
+ **/
+export function buildFetcherConfig(
url: string,
requestConfig: RequestConfig,
-): FetcherConfig => {
- const method = (requestConfig.method ?? GET).toUpperCase() as Method;
- const isGetAlikeMethod = method === GET || method === HEAD;
- const dynamicUrl = replaceUrlPathParams(url, requestConfig.urlPathParams);
+): RequestConfig {
+ let method = requestConfig.method as Method;
+ method = method ? (method.toUpperCase() as Method) : GET;
let body: RequestConfig['data'] | undefined;
// Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'
- if (!isGetAlikeMethod) {
+ if (method !== GET && method !== HEAD) {
body = requestConfig.body ?? requestConfig.data;
+
+ // Automatically stringify request body, if possible and when not dealing with strings
+ if (body && typeof body !== STRING && isJSONSerializable(body)) {
+ body = JSON.stringify(body);
+ }
}
- setContentTypeIfNeeded(method, requestConfig.headers, body);
+ setContentTypeIfNeeded(requestConfig.headers, body);
// Native fetch compatible settings
const credentials = requestConfig.withCredentials
@@ -81,34 +139,20 @@ export const buildConfig = (
: requestConfig.credentials;
// The explicitly passed query params
- const explicitParams = requestConfig.params;
-
- const urlPath = explicitParams
- ? appendQueryParams(dynamicUrl, explicitParams)
- : dynamicUrl;
- const isFullUrl = urlPath.includes('://');
+ const dynamicUrl = replaceUrlPathParams(url, requestConfig.urlPathParams);
+ const urlPath = appendQueryParams(dynamicUrl, requestConfig.params);
+ const isFullUrl = isAbsoluteUrl(url);
const baseURL = isFullUrl
? ''
- : (requestConfig.baseURL ?? requestConfig.apiUrl);
+ : requestConfig.baseURL || requestConfig.apiUrl || '';
- // Automatically stringify request body, if possible and when not dealing with strings
- if (
- body &&
- typeof body !== STRING &&
- !isSearchParams(body) &&
- isJSONSerializable(body)
- ) {
- body = JSON.stringify(body);
- }
+ requestConfig.url = baseURL + urlPath;
+ requestConfig.method = method;
+ requestConfig.credentials = credentials;
+ requestConfig.body = body;
- return {
- ...requestConfig,
- url: baseURL + urlPath,
- method,
- credentials,
- body,
- };
-};
+ return requestConfig;
+}
/**
* Ensures the `Content-Type` header is set to `application/json; charset=utf-8`
@@ -116,52 +160,134 @@ export const buildConfig = (
*
* @param headers - The headers object to modify. Can be an instance of `Headers`
* or a plain object conforming to `HeadersInit`.
- * @param method - The HTTP method of the request (e.g., 'PUT', 'DELETE', etc.).
* @param body - The optional body of the request. If no body is provided and the
- * method is 'PUT' or 'DELETE', the function exits without modifying headers.
+ * method is 'GET' or 'HEAD', the function exits without modifying headers.
*/
-const setContentTypeIfNeeded = (
- method: string,
+function setContentTypeIfNeeded(
headers?: HeadersInit | HeadersObject,
body?: unknown,
-): void => {
- if (!headers || (!body && ['PUT', 'DELETE'].includes(method))) {
+): void {
+ // If no headers are provided, or if the body is not set and the method is PUT or DELETE, do nothing
+ if (!headers || !body) {
+ return;
+ }
+
+ // Types that should not have Content-Type set (browser handles these)
+ if (
+ body instanceof FormData || // Browser automatically sets multipart/form-data with boundary
+ (typeof Blob !== UNDEFINED && body instanceof Blob) || // Blob/File already have their own MIME types, don't override
+ (typeof File !== UNDEFINED && body instanceof File) ||
+ (typeof ReadableStream !== UNDEFINED && body instanceof ReadableStream) // Stream type should be determined by the stream source
+ ) {
return;
}
- const contentTypeValue = APPLICATION_JSON + ';' + CHARSET_UTF_8;
+ let contentTypeValue: string;
+
+ if (isSearchParams(body)) {
+ contentTypeValue = APPLICATION_CONTENT_TYPE + 'x-www-form-urlencoded';
+ } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
+ contentTypeValue = APPLICATION_CONTENT_TYPE + 'octet-stream';
+ } else if (isJSONSerializable(body)) {
+ contentTypeValue = APPLICATION_JSON + ';' + CHARSET_UTF_8;
+ } else {
+ // Do not set Content-Type if content is not recognizable
+ return;
+ }
if (headers instanceof Headers) {
if (!headers.has(CONTENT_TYPE)) {
headers.set(CONTENT_TYPE, contentTypeValue);
}
} else if (
- typeof headers === OBJECT &&
+ isObject(headers) &&
!Array.isArray(headers) &&
!headers[CONTENT_TYPE]
) {
headers[CONTENT_TYPE] = contentTypeValue;
}
-};
+}
+
+export function mergeConfigs(
+ baseConfig: RequestConfig,
+ overrideConfig: RequestConfig,
+): RequestConfig {
+ const mergedConfig: RequestConfig = Object.assign(
+ {},
+ baseConfig,
+ overrideConfig,
+ );
+
+ // Ensure that retry and headers are merged correctly
+ mergeConfig('retry', mergedConfig, baseConfig, overrideConfig);
+ mergeConfig('headers', mergedConfig, baseConfig, overrideConfig);
+
+ // Merge interceptors efficiently
+ mergeInterceptors('onRequest', mergedConfig, baseConfig, overrideConfig);
+ mergeInterceptors('onResponse', mergedConfig, baseConfig, overrideConfig);
+ mergeInterceptors('onError', mergedConfig, baseConfig, overrideConfig);
+
+ return mergedConfig;
+}
/**
- * Merges the specified property from the base configuration and the new configuration into the target configuration.
+ * Efficiently merges interceptor functions from base and new configs
+ */
+function mergeInterceptors<
+ K extends 'onRequest' | 'onResponse' | 'onError' | 'onRetry',
+>(
+ property: K,
+ targetConfig: RequestConfig,
+ baseConfig: RequestConfig,
+ overrideConfig: RequestConfig,
+): void {
+ const baseInterceptor = baseConfig[property];
+ const newInterceptor = overrideConfig[property];
+
+ if (!baseInterceptor && !newInterceptor) {
+ return;
+ }
+
+ if (!baseInterceptor) {
+ targetConfig[property] = newInterceptor;
+ return;
+ }
+
+ if (!newInterceptor) {
+ targetConfig[property] = baseInterceptor;
+ return;
+ }
+
+ const baseArr = Array.isArray(baseInterceptor)
+ ? baseInterceptor
+ : [baseInterceptor];
+ const newArr = Array.isArray(newInterceptor)
+ ? newInterceptor
+ : [newInterceptor];
+
+ // This is the only LIFO interceptor, so we apply it after the response is prepared
+ targetConfig[property] =
+ property === 'onResponse' ? newArr.concat(baseArr) : baseArr.concat(newArr);
+}
+
+/**
+ * Merges the specified property from the base configuration and the override configuration into the target configuration.
*
- * @param {K} property - The property key to merge from the base and new configurations. Must be a key of RequestHandlerConfig.
- * @param {RequestHandlerConfig} targetConfig - The configuration object that will receive the merged properties.
- * @param {RequestHandlerConfig} baseConfig - The base configuration object that provides default values.
- * @param {RequestHandlerConfig} newConfig - The new configuration object that contains user-specific settings to merge.
+ * @param {K} property - The property key to merge from the base and override configurations. Must be a key of RequestConfig.
+ * @param {RequestConfig} targetConfig - The configuration object that will receive the merged properties.
+ * @param {RequestConfig} baseConfig - The base configuration object that provides default values.
+ * @param {RequestConfig} overrideConfig - The override configuration object that contains user-specific settings to merge.
*/
-export const mergeConfig = (
+export function mergeConfig(
property: K,
- targetConfig: RequestHandlerConfig,
- baseConfig: RequestHandlerConfig,
- newConfig: RequestHandlerConfig,
-) => {
- if (newConfig[property]) {
+ targetConfig: RequestConfig,
+ baseConfig: RequestConfig,
+ overrideConfig: RequestConfig,
+): void {
+ if (overrideConfig[property]) {
targetConfig[property] = {
...baseConfig[property],
- ...newConfig[property],
+ ...overrideConfig[property],
};
}
-};
+}
diff --git a/src/constants.ts b/src/constants.ts
index 5fe8afa0..98081e26 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -11,7 +11,8 @@ export const FUNCTION = 'function';
export const ABORT_ERROR = 'AbortError';
export const TIMEOUT_ERROR = 'TimeoutError';
-export const CANCELLED_ERROR = 'CanceledError';
export const GET = 'GET';
export const HEAD = 'HEAD';
+
+export const REJECT = 'reject';
diff --git a/src/error-handler.ts b/src/error-handler.ts
new file mode 100644
index 00000000..d44ce81e
--- /dev/null
+++ b/src/error-handler.ts
@@ -0,0 +1,124 @@
+import type { ResponseError } from './errors/response-error';
+import type {
+ DefaultResponse,
+ FetchResponse,
+ RequestConfig,
+} from './types/request-handler';
+import { applyInterceptors } from './interceptor-manager';
+import { handleResponseCache } from './cache-manager';
+import { ABORT_ERROR, REJECT } from './constants';
+import { DefaultParams, DefaultUrlParams, DefaultPayload } from './types';
+
+/**
+ * Handles final processing for both success and error responses
+ * Applies error interceptors, caching, notifications, and error strategy
+ */
+export async function withErrorHandling<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ isStaleRevalidation: boolean,
+ requestFn: (
+ isStaleRevalidation: boolean,
+ ) => Promise<
+ FetchResponse
+ >,
+ requestConfig: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ >,
+): Promise> {
+ const output = await requestFn(isStaleRevalidation);
+ const error = output.error;
+
+ if (!error) {
+ // SUCCESS PATH
+ handleResponseCache(output, requestConfig);
+
+ return output;
+ }
+
+ // ERROR PATH
+
+ if (requestConfig.onError) {
+ await applyInterceptors(requestConfig.onError, error);
+ }
+
+ // Timeouts and request cancellations using AbortController do not throw any errors unless rejectCancelled is true.
+ // Only handle the error if the request was not cancelled, or if it was cancelled and rejectCancelled is true.
+ const isCancelled = error.isCancelled;
+
+ if (!isCancelled && requestConfig.logger) {
+ logger(requestConfig, 'FETCH ERROR', error as ResponseError);
+ }
+
+ // Handle cache and notifications FIRST (before strategy)
+ handleResponseCache(output, requestConfig, true);
+
+ // handle error strategy as the last part
+ const shouldHandleError = !isCancelled || requestConfig.rejectCancelled;
+
+ if (shouldHandleError) {
+ const strategy = requestConfig.strategy;
+ // Reject the promise
+ if (strategy === REJECT) {
+ return Promise.reject(error);
+ }
+
+ // Hang the promise
+ if (strategy === 'silent') {
+ await new Promise(() => null);
+ }
+ }
+
+ return output;
+}
+
+export function enhanceError<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ error: any,
+ response: FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ > | null,
+ requestConfig: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ >,
+): void {
+ error.status = error.status || response?.status || 0;
+ error.statusText = error.statusText || response?.statusText || '';
+ error.config = error.request = requestConfig;
+ error.response = response;
+ error.isCancelled = error.name === ABORT_ERROR;
+}
+
+/**
+ * Logs messages or errors using the configured logger's `warn` method.
+ *
+ * @param {RequestConfig} reqConfig - Request config passed when making the request
+ * @param {...(string | ResponseError)} args - Messages or errors to log.
+ */
+function logger(
+ reqConfig: RequestConfig,
+ ...args: (string | ResponseError)[]
+): void {
+ const logger = reqConfig.logger;
+
+ if (logger && logger.warn) {
+ logger.warn(...args);
+ }
+}
diff --git a/src/errors/fetch-error.ts b/src/errors/fetch-error.ts
index 41858b92..bea4d873 100644
--- a/src/errors/fetch-error.ts
+++ b/src/errors/fetch-error.ts
@@ -12,13 +12,14 @@ import type {
*/
export class FetchError<
ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
QueryParams = DefaultParams,
PathParams = DefaultUrlParams,
- RequestBody = DefaultPayload,
> extends Error {
status: number;
statusText: string;
config: RequestConfig;
+ isCancelled: boolean;
constructor(
message: string,
@@ -38,8 +39,9 @@ export class FetchError<
super(message);
this.name = 'FetchError';
- this.status = response?.status || 0;
- this.statusText = response?.statusText || '';
+ this.status = response ? response.status : 0;
+ this.statusText = response ? response.statusText : '';
this.config = request;
+ this.isCancelled = false;
}
}
diff --git a/src/errors/network-error.ts b/src/errors/network-error.ts
index 5039303c..294493de 100644
--- a/src/errors/network-error.ts
+++ b/src/errors/network-error.ts
@@ -9,10 +9,10 @@ import type {
export class NetworkError<
ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
QueryParams = DefaultParams,
PathParams = DefaultUrlParams,
- RequestBody = DefaultPayload,
-> extends FetchError {
+> extends FetchError {
constructor(
message: string,
request: RequestConfig,
diff --git a/src/errors/response-error.ts b/src/errors/response-error.ts
index 526ea475..2ad0db9b 100644
--- a/src/errors/response-error.ts
+++ b/src/errors/response-error.ts
@@ -10,14 +10,19 @@ import type {
export class ResponseError<
ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
QueryParams = DefaultParams,
PathParams = DefaultUrlParams,
- RequestBody = DefaultPayload,
-> extends FetchError {
+> extends FetchError {
constructor(
message: string,
request: RequestConfig,
- response: FetchResponse | null,
+ response: FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ > | null,
) {
super(message, request, response);
diff --git a/src/index.ts b/src/index.ts
index 57065320..b3c39351 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,25 +1,44 @@
-import { createRequestHandler } from './request-handler';
-import type {
- DefaultResponse,
- FetchResponse,
- RequestHandlerConfig,
-} from './types';
-
-/**
- * Simple wrapper for request fetching.
- * It abstracts the creation of RequestHandler, making it easy to perform API requests.
- *
- * @param {string | URL | globalThis.Request} url - Request URL.
- * @param {RequestHandlerConfig} config - Configuration object for the request handler.
- * @returns {Promise>} Response Data.
- */
-export async function fetchf(
- url: string,
- config: RequestHandlerConfig = {},
-): Promise> {
- return createRequestHandler(config).request(url);
-}
+/** All TypeScript types and interfaces */
+export * from './types';
+
+/** Core fetch function with caching, retries, and revalidation */
+export { fetchf, fetchf as fetchff } from './request-handler';
+/** Create a configured API fetcher instance */
export { createApiFetcher } from './api-handler';
-export * from './types';
+/** Build and merge request configurations */
+export {
+ buildConfig, // Build request configuration from defaults and overrides
+ setDefaultConfig, // Set global default configuration for requests
+ getDefaultConfig, // Get the current global default configuration
+} from './config-handler';
+
+/** Cache management utilities */
+export {
+ generateCacheKey, // Generate cache key from URL and config
+ getCache, // Get cached response for a key
+ getCachedResponse, // Get cached response with revalidation
+ mutate, // Update cache and notify subscribers
+ setCache, // Set cache entry directly
+ deleteCache, // Delete cache entry
+} from './cache-manager';
+
+/** Request Revalidation management for cache freshness */
+export {
+ revalidate, // Revalidate specific cache entry
+ revalidateAll, // Revalidate all entries by event type
+ removeRevalidators, // Clean up all revalidators by type
+} from './revalidator-manager';
+
+/** Subscribe to cache updates via pub/sub */
+export { subscribe } from './pubsub-manager';
+
+/** Abort in-flight requests and check request status */
+export { abortRequest, getInFlightPromise } from './inflight-manager';
+
+/** Network and environment utilities (Browser Only) */
+export { isSlowConnection } from './utils';
+
+/** Timeout management for delayed operations */
+export { addTimeout } from './timeout-wheel';
diff --git a/src/inflight-manager.ts b/src/inflight-manager.ts
new file mode 100644
index 00000000..dc70cbfa
--- /dev/null
+++ b/src/inflight-manager.ts
@@ -0,0 +1,215 @@
+/**
+ * @module inflight-manager
+ *
+ * Manages in-flight asynchronous requests using unique keys to enable deduplication and cancellation.
+ *
+ * Provides utilities for:
+ * - Deduplication of requests within a configurable time window (`dedupeTime`)
+ * - Timeout management and automatic request abortion
+ * - AbortController lifecycle and cancellation logic
+ * - Concurrency control and request state tracking
+ * - In-flight promise deduplication to prevent duplicate network calls
+ *
+ * @remarks
+ * - Requests with the same key within the deduplication interval share the same AbortController and in-flight promise.
+ * - Supports cancellation of previous requests when a new one with the same key is issued, if `isCancellable` is enabled.
+ * - Timeout logic ensures requests are aborted after a specified duration, if enabled.
+ * - Internal queue state is managed via a Map, keyed by request identifier.
+ * - Polled requests are also marked as "in-flight" to prevent duplicate requests.
+ */
+
+import { ABORT_ERROR, TIMEOUT_ERROR } from './constants';
+import { addTimeout, removeTimeout } from './timeout-wheel';
+import { timeNow } from './utils';
+
+export type InFlightItem = [
+ AbortController, // AbortController for the request
+ boolean, // Whether timeout is enabled for the request
+ number, // Timestamp when the request was marked in-flight
+ boolean, // isCancellable - whether the request can be cancelled
+ Promise | null, // Optional in-flight promise for deduplication
+];
+
+const inFlight: Map = new Map();
+
+/**
+ * Adds a request to the queue if it's not already being processed within the dedupeTime interval.
+ *
+ * @param {string | null} key - Unique key for the request (e.g. cache key).
+ * @param {string} url - The request URL (for error messages/timeouts).
+ * @param {number} timeout - Timeout in milliseconds for the request.
+ * @param {number} dedupeTime - Deduplication time in milliseconds.
+ * @param {boolean} isCancellable - If true, then the previous request with same configuration should be aborted.
+ * @param {boolean} isTimeoutEnabled - Whether timeout is enabled.
+ * @returns {AbortController} - A promise that resolves to an AbortController.
+ */
+export function markInFlight(
+ key: string | null,
+ url: string,
+ timeout: number | undefined,
+ dedupeTime: number,
+ isCancellable: boolean,
+ isTimeoutEnabled: boolean,
+): AbortController {
+ if (!key) {
+ return new AbortController();
+ }
+
+ const item = inFlight.get(key);
+ let prevPromise: Promise | null = null;
+
+ // Previous request is in-flight, check if we can reuse it
+ if (item) {
+ const prevController = item[0];
+ const prevIsCancellable = item[3];
+
+ // If the request is already in the queue and within the dedupeTime, reuse the existing controller
+ if (
+ !prevIsCancellable &&
+ timeNow() - item[2] < dedupeTime &&
+ !prevController.signal.aborted
+ ) {
+ return prevController;
+ }
+
+ // If the request is too old, remove it and proceed to add a new one
+ // Abort previous request, if applicable, and continue as usual
+ if (prevIsCancellable) {
+ prevController.abort(
+ new DOMException('Aborted due to new request', ABORT_ERROR),
+ );
+ }
+
+ removeTimeout(key);
+ prevPromise = item[4];
+ }
+
+ const controller = new AbortController();
+
+ inFlight.set(key, [
+ controller,
+ isTimeoutEnabled,
+ timeNow(),
+ isCancellable,
+ prevPromise,
+ ]);
+
+ if (isTimeoutEnabled) {
+ addTimeout(
+ key,
+ () => {
+ abortRequest(
+ key,
+ new DOMException(url + ' aborted due to timeout', TIMEOUT_ERROR),
+ );
+ },
+ timeout as number,
+ );
+ }
+
+ return controller;
+}
+
+/**
+ * Removes a request from the queue and clears its timeout.
+ *
+ * @param key - Unique key for the request.
+ * @param {boolean} error - Optional error to abort the request with. If null, the request is simply removed but no abort sent.
+ * @returns {Promise} - A promise that resolves when the request is aborted and removed.
+ */
+export async function abortRequest(
+ key: string | null,
+ error: DOMException | null | string = null,
+): Promise {
+ // If the key is not in the queue, there's nothing to remove
+ if (key) {
+ const item = inFlight.get(key);
+
+ if (item) {
+ // If the request is not yet aborted, abort it with the provided error
+ if (error) {
+ const controller = item[0];
+ controller.abort(error);
+ }
+
+ removeInFlight(key);
+ }
+ }
+}
+
+/**
+ * Removes a request from the in-flight queue without aborting or clearing timeout.
+ *
+ * @param key - Unique key for the request.
+ */
+export function removeInFlight(key: string | null): void {
+ removeTimeout(key!);
+ inFlight.delete(key!);
+}
+
+/**
+ * Gets the AbortController for a request key.
+ *
+ * @param key - Unique key for the request.
+ * @returns {AbortController | undefined} - The AbortController or undefined.
+ */
+export async function getController(
+ key: string,
+): Promise {
+ const item = inFlight.get(key);
+
+ return item?.[0];
+}
+
+/**
+ * Adds helpers for in-flight promise deduplication.
+ *
+ * @param key - Unique key for the request.
+ * @param promise - The promise to store.
+ */
+export function setInFlightPromise(
+ key: string,
+ promise: Promise,
+): void {
+ const item = inFlight.get(key);
+ if (item) {
+ // store the promise at index 4
+ item[4] = promise;
+
+ inFlight.set(key, item);
+ }
+}
+
+/**
+ * Retrieves the in-flight promise for a request key if it exists and is within the dedupeTime interval.
+ *
+ * @param key - Unique key for the request.
+ * @param dedupeTime - Deduplication time in milliseconds.
+ * @returns {Promise | null} - The in-flight promise or null.
+ */
+export function getInFlightPromise(
+ key: string | null,
+ dedupeTime: number,
+): Promise | null {
+ if (!key) {
+ return null;
+ }
+
+ const prevReq = inFlight.get(key);
+
+ if (
+ prevReq &&
+ // If the request is in-flight and has a promise
+ prevReq[4] &&
+ // If the request is cancellable, we will not reuse it
+ !prevReq[3] &&
+ // If the request is within the dedupeTime
+ timeNow() - prevReq[2] < dedupeTime &&
+ // If one request is cancelled, ALL deduped requests get cancelled
+ !prevReq[0].signal.aborted
+ ) {
+ return prevReq[4] as Promise;
+ }
+
+ return null;
+}
diff --git a/src/interceptor-manager.ts b/src/interceptor-manager.ts
index be2d918d..dcc979c6 100644
--- a/src/interceptor-manager.ts
+++ b/src/interceptor-manager.ts
@@ -1,36 +1,45 @@
-type InterceptorFunction = (object: T) => Promise;
+import { FUNCTION } from './constants';
+import type { InterceptorFunction } from './types/interceptor-manager';
+import { isObject } from './utils';
/**
* Applies interceptors to the object. Interceptors can be a single function or an array of functions.
*
* @template T - Type of the object.
+ * @template Args - Type of additional arguments.
* @template I - Type of interceptors.
*
- * @param {T} object - The object to process.
- * @param {InterceptorFunction | InterceptorFunction[]} [interceptors] - Interceptor function(s).
+ * @param {InterceptorFunction | InterceptorFunction[]} [interceptors] - Interceptor function(s).
+ * @param {T} data - The data object to process.
+ * @param {...Args} args - Additional arguments to pass to interceptors.
*
* @returns {Promise} - Nothing as the function is non-idempotent.
*/
-export async function applyInterceptor<
+export async function applyInterceptors<
T extends object,
- I = InterceptorFunction | InterceptorFunction[],
->(object: T, interceptors?: I): Promise {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ Args extends any[] = any[],
+ I = InterceptorFunction | InterceptorFunction[],
+>(interceptors: I | undefined, data: T, ...args: Args): Promise {
if (!interceptors) {
return;
}
- if (typeof interceptors === 'function') {
- const value = await interceptors(object);
+ if (typeof interceptors === FUNCTION) {
+ const value = await (interceptors as InterceptorFunction)(
+ data,
+ ...args,
+ );
- if (value) {
- Object.assign(object, value);
+ if (value && isObject(data) && isObject(value)) {
+ Object.assign(data, value);
}
} else if (Array.isArray(interceptors)) {
for (const interceptor of interceptors) {
- const value = await interceptor(object);
+ const value = await interceptor(data, ...args);
- if (value) {
- Object.assign(object, value);
+ if (value && isObject(data) && isObject(value)) {
+ Object.assign(data, value);
}
}
}
diff --git a/src/polling-handler.ts b/src/polling-handler.ts
index 9903e073..c073136b 100644
--- a/src/polling-handler.ts
+++ b/src/polling-handler.ts
@@ -1,4 +1,4 @@
-import type { ExtendedRequestConfig, FetchResponse } from './types';
+import type { RequestConfig, FetchResponse } from './types';
import { delayInvocation } from './utils';
/**
@@ -6,7 +6,7 @@ import { delayInvocation } from './utils';
* pollingInterval is not set, or maxAttempts is reached.
*
* @template Output The type of the output returned by the request function.
- * @param doRequestOnce - The function that performs a single request (with retries).
+ * @param requestFn - The function that performs a single request (with retries).
* @param pollingInterval - Interval in ms between polling attempts.
* @param shouldStopPolling - Function to determine if polling should stop.
* @param maxAttempts - Maximum number of polling attempts, default: 0 (unlimited).
@@ -14,28 +14,35 @@ import { delayInvocation } from './utils';
* @returns The final output from the last request.
*/
export async function withPolling<
- Output extends FetchResponse<
- unknown,
- unknown,
- unknown,
- unknown
- > = FetchResponse,
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams,
>(
- doRequestOnce: () => Promise,
- pollingInterval?: ExtendedRequestConfig['pollingInterval'],
- shouldStopPolling?: ExtendedRequestConfig['shouldStopPolling'],
+ requestFn: (
+ isStaleRevalidation?: boolean,
+ attempt?: number,
+ ) => Promise<
+ FetchResponse
+ >,
+ pollingInterval?: RequestConfig['pollingInterval'],
+ shouldStopPolling?: RequestConfig['shouldStopPolling'],
maxAttempts = 0,
pollingDelay = 0,
-): Promise {
+): Promise> {
+ if (!pollingInterval) {
+ return requestFn();
+ }
+
let pollingAttempt = 0;
- let output: Output;
+ let output: FetchResponse;
while (maxAttempts === 0 || pollingAttempt < maxAttempts) {
if (pollingDelay > 0) {
await delayInvocation(pollingDelay);
}
- output = await doRequestOnce();
+ output = await requestFn();
pollingAttempt++;
diff --git a/src/pubsub-manager.ts b/src/pubsub-manager.ts
new file mode 100644
index 00000000..bbf0a0ec
--- /dev/null
+++ b/src/pubsub-manager.ts
@@ -0,0 +1,82 @@
+/**
+ * Manages a set of listeners (subscribers) for arbitrary string keys, allowing cross-context or cross-component
+ * cache updates and synchronization. Provides functions to add, remove, and notify listeners, as well as a
+ * convenient subscribe/unsubscribe API.
+ *
+ * @template T - The type of the response object passed to listeners.
+ *
+ * @remarks
+ * - Listeners are grouped by a string key, which typically represents a cache key or resource identifier.
+ * - When `notifySubscribers` is called for a key, all listeners registered for that key are invoked with the provided response.
+ * - The `subscribe` function returns an unsubscribe function for convenient cleanup.
+ *
+ * @example
+ * ```ts
+ * const unsubscribe = subscribe('user:123', (response) => {
+ * // handle updated data
+ * });
+ * // Later, to stop listening:
+ * unsubscribe();
+ * ```
+ */
+
+import { noop } from './utils';
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type Listener = (response: T) => void;
+
+const listeners = new Map>();
+
+function ensureListenerSet(key: string) {
+ if (!listeners.has(key)) {
+ listeners.set(key, new Set());
+ }
+
+ return listeners.get(key)!;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function addListener(key: string, fn: Listener): void {
+ ensureListenerSet(key).add(fn);
+}
+
+export function removeListener(key: string, fn: Listener) {
+ const set = listeners.get(key);
+
+ if (set) {
+ set.delete(fn);
+
+ // If the set is empty, remove the key from the listeners map
+ if (set.size === 0) {
+ listeners.delete(key);
+ }
+ }
+}
+
+export function notifySubscribers(key: string, response: T) {
+ const fns = listeners.get(key);
+
+ if (fns) {
+ if (fns.size === 1) {
+ // If there's only one listener, call it directly
+ const fn = fns.values().next().value;
+ fn!(response);
+ } else {
+ fns.forEach((fn) => fn(response));
+ }
+ }
+}
+
+export function subscribe(key: string | null, fn: (response: T) => void) {
+ if (!key) {
+ // No op if no key is provided
+ return noop;
+ }
+
+ addListener(key, fn);
+
+ // Return an unsubscribe function
+ return () => {
+ removeListener(key, fn);
+ };
+}
diff --git a/src/queue-manager.ts b/src/queue-manager.ts
deleted file mode 100644
index ba9a154d..00000000
--- a/src/queue-manager.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { ABORT_ERROR, TIMEOUT_ERROR } from './constants';
-import type { QueueItem } from './types/queue-manager';
-
-/**
- * Queue Manager is responsible for managing and controlling the flow of concurrent or sequential requests. It handles:
- * - Request Queueing and Deduplication
- * - Request Timeout Handling
- * - Abort Controller Management and Request Cancellation
- * - Concurrency Control and Locking
- * - Request Lifecycle Management
- */
-const queue: Map = new Map();
-
-/**
- * Adds a request to the queue if it's not already being processed within the dedupeTime interval.
- *
- * @param {string | null} key - Unique key for the request (e.g. cache key).
- * @param {string} url - The request URL (for error messages/timeouts).
- * @param {number} timeout - Timeout in milliseconds for the request.
- * @param {number} dedupeTime - Deduplication time in milliseconds.
- * @param {boolean} isCancellable - If true, then the previous request with same configuration should be aborted.
- * @param {boolean} isTimeoutEnabled - Whether timeout is enabled.
- * @returns {Promise} - A promise that resolves to an AbortController.
- */
-export async function queueRequest(
- key: string | null,
- url: string,
- timeout: number | undefined,
- dedupeTime: number = 0,
- isCancellable: boolean = false,
- isTimeoutEnabled: boolean = true,
-): Promise {
- if (!key) {
- return new AbortController();
- }
-
- const now = Date.now();
- const item = queue.get(key);
-
- if (item) {
- const prevIsCancellable = item[3];
- const previousController = item[0];
- const timeoutId = item[1];
-
- // If the request is already in the queue and within the dedupeTime, reuse the existing controller
- if (!prevIsCancellable && now - item[2] < dedupeTime) {
- return previousController;
- }
-
- // If the request is too old, remove it and proceed to add a new one
- // Abort previous request, if applicable, and continue as usual
- if (prevIsCancellable) {
- previousController.abort(
- new DOMException('Aborted due to new request', ABORT_ERROR),
- );
- }
-
- if (timeoutId !== null) {
- clearTimeout(timeoutId);
- }
-
- queue.delete(key);
- }
-
- const controller = new AbortController();
-
- const timeoutId = isTimeoutEnabled
- ? setTimeout(() => {
- const error = new DOMException(
- `${url} aborted due to timeout`,
- TIMEOUT_ERROR,
- );
-
- removeRequestFromQueue(key, error);
- }, timeout)
- : null;
-
- queue.set(key, [controller, timeoutId, now, isCancellable]);
-
- return controller;
-}
-
-/**
- * Removes a request from the queue and clears its timeout.
- *
- * @param key - Unique key for the request.
- * @param {boolean} error - Error payload so to force the request to abort.
- */
-export async function removeRequestFromQueue(
- key: string | null,
- error: DOMException | null | string = null,
-): Promise {
- // If the key is not in the queue, there's nothing to remove
- if (!key) {
- return;
- }
-
- const item = queue.get(key);
-
- if (item) {
- const controller = item[0];
- const timeoutId = item[1];
-
- // If the request is not yet aborted, abort it with the provided error
- if (error && !controller.signal.aborted) {
- controller.abort(error);
- }
-
- if (timeoutId !== null) {
- clearTimeout(timeoutId);
- }
-
- queue.delete(key);
- }
-}
-
-/**
- * Gets the AbortController for a request key.
- *
- * @param key - Unique key for the request.
- * @returns {AbortController | undefined} - The AbortController or undefined.
- */
-export async function getController(
- key: string,
-): Promise {
- const item = queue.get(key);
-
- return item?.[0];
-}
-
-/**
- * Adds helpers for in-flight promise deduplication.
- *
- * @param key - Unique key for the request.
- * @param promise - The promise to store.
- */
-export function setInFlightPromise(key: string, promise: Promise) {
- const item = queue.get(key);
- if (item) {
- // store the promise at index 4
- item[4] = promise;
-
- queue.set(key, item);
- }
-}
-
-/**
- * Retrieves the in-flight promise for a request key if it exists and is within the dedupeTime interval.
- *
- * @param key - Unique key for the request.
- * @param dedupeTime - Deduplication time in milliseconds.
- * @returns {Promise | null} - The in-flight promise or null.
- */
-export function getInFlightPromise(
- key: string,
- dedupeTime: number,
-): Promise | null {
- const item = queue.get(key);
-
- if (item && item[4] && Date.now() - item[2] < dedupeTime) {
- return item[4];
- }
-
- return null;
-}
diff --git a/src/react/cache-ref.ts b/src/react/cache-ref.ts
new file mode 100644
index 00000000..1dacf322
--- /dev/null
+++ b/src/react/cache-ref.ts
@@ -0,0 +1,93 @@
+/**
+ * @module cache-ref
+ *
+ * Provides reference counting utilities for cache management in React applications.
+ *
+ * This module maintains an internal reference count for cache keys, allowing for
+ * precise control over when cache entries should be deleted. It exports functions
+ * to increment and decrement reference counts, retrieve the current count, and clear
+ * all reference counts. When a reference count drops to zero and certain conditions
+ * are met, the corresponding cache entry is scheduled for deletion.
+ *
+ * @see deleteCache
+ */
+
+import { addTimeout, abortRequest, deleteCache } from 'fetchff';
+
+export const INFINITE_CACHE_TIME = -1;
+export const DEFAULT_DEDUPE_TIME_MS = 2000;
+
+const refs = new Map();
+
+export const incrementRef = (key: string | null) => {
+ if (key) {
+ refs.set(key, (refs.get(key) || 0) + 1);
+ }
+};
+
+export const decrementRef = (
+ key: string | null,
+ cacheTime?: number,
+ dedupeTime?: number,
+ url?: string | null,
+) => {
+ if (!key) {
+ return;
+ }
+
+ const current = getRefCount(key);
+
+ if (!current) {
+ return;
+ }
+
+ const newCount = current - 1;
+
+ // If the current reference count is less than 2, we can consider deleting the global cache entry
+ // The infinite cache time is a special case where we never delete the cache entry unless the reference count drops to zero.
+ // This allows for long-lived cache entries that are only deleted when explicitly no longer needed.
+ if (newCount <= 0) {
+ refs.delete(key);
+
+ if (cacheTime && cacheTime === INFINITE_CACHE_TIME) {
+ // Delay to ensure all operations are complete before deletion
+ addTimeout(
+ 'r:' + key,
+ () => {
+ // Abort any ongoing requests associated with this cache key
+ abortRequest(
+ key,
+ new DOMException('Request to ' + url + ' aborted', 'AbortError'),
+ );
+
+ // Check if the reference count is still zero before deleting the cache as it might have been incremented again
+ // This is to ensure that if another increment happens during the timeout, we don't delete the cache prematurely
+ // This is particularly useful in scenarios where multiple components might be using the same cache
+ // entry and we want to avoid unnecessary cache deletions.
+ if (!getRefCount(key)) {
+ deleteCache(key, true);
+ }
+ },
+ dedupeTime ?? DEFAULT_DEDUPE_TIME_MS,
+ );
+ }
+ } else {
+ refs.set(key, newCount);
+ }
+};
+
+export const getRefCount = (key: string | null): number => {
+ if (!key) {
+ return 0;
+ }
+
+ return refs.get(key) || 0;
+};
+
+export const getRefs = (): Map => {
+ return refs;
+};
+
+export const clearRefCache = () => {
+ refs.clear();
+};
diff --git a/src/react/index.ts b/src/react/index.ts
new file mode 100644
index 00000000..6dbc946c
--- /dev/null
+++ b/src/react/index.ts
@@ -0,0 +1,291 @@
+import { useCallback, useSyncExternalStore, useMemo, useRef } from 'react';
+import {
+ fetchf,
+ subscribe,
+ buildConfig,
+ generateCacheKey,
+ getCachedResponse,
+ getInFlightPromise,
+ getCache,
+} from 'fetchff';
+import type {
+ DefaultParams,
+ DefaultPayload,
+ DefaultResponse,
+ DefaultUrlParams,
+ FetchResponse,
+ RequestConfig,
+} from '..';
+import type { UseFetcherResult } from '../types/react-hooks';
+
+import {
+ decrementRef,
+ DEFAULT_DEDUPE_TIME_MS,
+ getRefCount,
+ incrementRef,
+ INFINITE_CACHE_TIME,
+} from './cache-ref';
+
+// In React, we use a default stale time of 5 minutes (SWR)
+const DEFAULT_STALE_TIME = 300; // 5 minutes
+
+// Pre-allocate objects to avoid GC pressure
+const DEFAULT_RESULT = Object.freeze({
+ data: null,
+ error: null,
+ isFetching: false,
+ mutate: () => Promise.resolve(null),
+ config: {},
+ headers: {},
+});
+
+const FETCHING_RESULT = Object.freeze({
+ ...DEFAULT_RESULT,
+ isFetching: true,
+});
+
+const DEFAULT_REF = [null, {}, null] as [
+ string | null,
+ RequestConfig,
+ string | null,
+];
+
+// RFC 7231: GET and HEAD are "safe methods" with no side effects
+const SAFE_METHODS = new Set(['GET', 'HEAD', 'get', 'head']);
+
+/**
+ * High-performance React hook for fetching data with caching, deduplication, revalidation etc.
+ *
+ * @template ResponseData - The expected response data type.
+ * @template RequestBody - The request payload type.
+ * @template QueryParams - The query parameters type.
+ * @template PathParams - The URL path parameters type.
+ *
+ * @param {string|null} url - The endpoint URL to fetch data from. Pass null to skip fetching.
+ * If the URL is null, the hook will not perform any fetch operation.
+ * If the URL is an empty string, it will default to the base URL configured in fetchff.
+ * If the URL is a full URL, it will be used as is.
+ * @param {RequestConfig} [config={}] - fetchff and native fetch compatible configuration.
+ *
+ * @returns {UseFetcherResult} An object containing:
+ * - `data`: The fetched data or `null` if not yet available.
+ * - `error`: Any error encountered during fetching or `null`.
+ * - `isLoading`: Boolean indicating if the request is in progress.
+ * - `mutate`: Function to update the cached data and optionally trigger revalidation.
+ *
+ * @remarks
+ * - Designed for high performance: minimizes unnecessary re-renders and leverages fast cache key generation.
+ * - Integrates with a global cache and pub/sub system for efficient state updates across contexts.
+ * - Handles automatic revalidation, deduplication, retries, and cache management out of the box.
+ *
+ * @example
+ * ```tsx
+ * const { data, error, isLoading, mutate } = useFetcher('/api/data', {
+ * refetchOnFocus: true,
+ * cacheTime: 5,
+ * dedupeTime: 2000,
+ * cacheKey: (config) => `custom-cache-key-${config.url}`,
+ * });
+ * ```
+ */
+export function useFetcher<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ url: string | null,
+ config: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ > = {},
+): UseFetcherResult {
+ // Efficient cache key generation based on URL and request parameters.
+ // Optimized for speed: minimizes unnecessary function calls when possible
+ const cacheKey = useMemo(
+ () => (url === null ? null : generateCacheKey(buildConfig(url, config))),
+ [
+ config.cacheKey,
+ url,
+ config.url,
+ config.method,
+ config.headers,
+ config.body,
+ config.params,
+ config.urlPathParams,
+ config.apiUrl,
+ config.baseURL,
+ config.withCredentials,
+ config.credentials,
+ ],
+ );
+ const dedupeTime = config.dedupeTime ?? DEFAULT_DEDUPE_TIME_MS;
+ const cacheTime = config.cacheTime || INFINITE_CACHE_TIME;
+ const staleTime = config.staleTime ?? DEFAULT_STALE_TIME;
+
+ // Determine if the fetch should be triggered immediately on mount
+ const shouldTriggerOnMount =
+ config.immediate ?? SAFE_METHODS.has(config.method || 'GET');
+
+ const currentValuesRef = useRef(DEFAULT_REF);
+ currentValuesRef.current = [url, config, cacheKey];
+
+ // Attempt to get the cached response immediately and if not available, return null
+ const getSnapshot = useCallback(() => {
+ const cached = getCache(
+ cacheKey,
+ );
+
+ // Only throw for Suspense if we're in 'reject' mode and have no data
+ if (
+ config.strategy === 'reject' &&
+ cacheKey &&
+ (!cached || (!cached.data.data && !cached.data.error))
+ ) {
+ const pendingPromise = getInFlightPromise(cacheKey, dedupeTime);
+
+ if (pendingPromise) {
+ throw pendingPromise;
+ }
+
+ // If no pending promise but we need to fetch, start fetch and throw the promise
+ if (!cached) {
+ const [currUrl, currConfig, currCacheKey] = currentValuesRef.current;
+
+ if (currUrl) {
+ const fetchPromise = fetchf(currUrl, {
+ ...currConfig,
+ cacheKey: currCacheKey,
+ dedupeTime,
+ cacheTime,
+ staleTime,
+ strategy: 'softFail',
+ cacheErrors: true,
+ _isAutoKey: !currConfig.cacheKey,
+ });
+
+ throw fetchPromise;
+ }
+ }
+ }
+
+ if (cached) {
+ return cached.data.isFetching && !config.keepPreviousData
+ ? (FETCHING_RESULT as unknown as FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >)
+ : cached.data;
+ }
+
+ return (shouldTriggerOnMount
+ ? FETCHING_RESULT
+ : DEFAULT_RESULT) as unknown as FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >;
+ }, [cacheKey]);
+
+ // Subscribe to cache updates for the specific cache key
+ const doSubscribe = useCallback(
+ (cb: () => void) => {
+ incrementRef(cacheKey);
+
+ // When the component mounts, we want to fetch data if:
+ // 1. URL is provided
+ // 2. shouldTriggerOnMount is true (so the "immediate" isn't specified or is true)
+ // 3. There is no cached data
+ // 4. There is no error
+ // 5. There is no ongoing fetch operation
+ const shouldFetch =
+ shouldTriggerOnMount && url && cacheKey && getRefCount(cacheKey) === 1; // Check if no existing refs
+
+ // Initial fetch logic
+ if (shouldFetch) {
+ // Stale-While-Revalidate Pattern: Check for both fresh and stale data
+ const cached = getCachedResponse(cacheKey, cacheTime, config);
+
+ if (!cached) {
+ refetch(false);
+ }
+ }
+
+ const unsubscribe = subscribe(cacheKey, cb);
+
+ return () => {
+ decrementRef(cacheKey, cacheTime, dedupeTime, url);
+ unsubscribe();
+ };
+ },
+ [cacheKey, shouldTriggerOnMount, url, dedupeTime, cacheTime],
+ );
+
+ const state = useSyncExternalStore<
+ FetchResponse
+ >(doSubscribe, getSnapshot, getSnapshot);
+
+ const refetch = useCallback(
+ async (forceRefresh = true) => {
+ const [currUrl, currConfig, currCacheKey] = currentValuesRef.current;
+
+ if (!currUrl) {
+ return Promise.resolve(null);
+ }
+
+ // Truthy check for forceRefresh to ensure it's a boolean. It is useful in onClick handlers so to avoid additional annonymous function calls.
+ const shouldRefresh = !!forceRefresh;
+
+ // Fast path: check cache first if not forcing refresh
+ if (!shouldRefresh && currCacheKey) {
+ const cached = getCachedResponse(currCacheKey, cacheTime, currConfig);
+
+ if (cached) {
+ return Promise.resolve(cached);
+ }
+ }
+
+ // When manual refetch is triggered, we want to ensure that the cache is busted
+ // This can be disabled by passing `refetch(false)`
+ const cacheBuster = shouldRefresh ? () => true : currConfig.cacheBuster;
+
+ return fetchf(currUrl, {
+ ...currConfig,
+ cacheKey: currCacheKey,
+ dedupeTime,
+ cacheTime,
+ staleTime,
+ cacheBuster,
+ // Ensure that errors are handled gracefully and not thrown by default
+ strategy: 'softFail',
+ cacheErrors: true,
+ _isAutoKey: !currConfig.cacheKey,
+ });
+ },
+ [cacheTime, dedupeTime],
+ );
+
+ const data = state.data;
+ const isUnresolved = !data && !state.error;
+ const isFetching = state.isFetching;
+ const isLoading =
+ !!url && (isFetching || (isUnresolved && shouldTriggerOnMount));
+
+ // Consumers always destructure the return value and use the fields directly, so
+ // memoizing the object doesn't change rerender behavior nor improve any performance here
+ return {
+ data,
+ error: state.error,
+ config: state.config,
+ headers: state.headers,
+ isFetching,
+ isLoading,
+ mutate: state.mutate,
+ refetch,
+ };
+}
diff --git a/src/request-handler.ts b/src/request-handler.ts
index 1b277642..a4965f1f 100644
--- a/src/request-handler.ts
+++ b/src/request-handler.ts
@@ -1,423 +1,327 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
DefaultResponse,
- RequestHandlerConfig,
RequestConfig,
- RetryOptions,
FetchResponse,
- RequestHandlerReturnType,
- CreatedCustomFetcherInstance,
} from './types/request-handler';
import type {
DefaultParams,
DefaultPayload,
DefaultUrlParams,
} from './types/api-handler';
-import { applyInterceptor } from './interceptor-manager';
+import { applyInterceptors } from './interceptor-manager';
import { ResponseError } from './errors/response-error';
-import { delayInvocation, sanitizeObject } from './utils';
+import { isObject } from './utils';
import {
- queueRequest,
- removeRequestFromQueue,
+ markInFlight,
setInFlightPromise,
getInFlightPromise,
-} from './queue-manager';
-import { ABORT_ERROR, CANCELLED_ERROR } from './constants';
-import { prepareResponse, parseResponseData } from './response-parser';
+} from './inflight-manager';
+import { parseResponseData, prepareResponse } from './response-parser';
import { generateCacheKey, getCachedResponse, setCache } from './cache-manager';
-import { buildConfig, defaultConfig, mergeConfig } from './config-handler';
-import { getRetryAfterMs } from './retry-handler';
+import { withRetry } from './retry-handler';
import { withPolling } from './polling-handler';
+import { notifySubscribers } from './pubsub-manager';
+import { addRevalidator } from './revalidator-manager';
+import { enhanceError, withErrorHandling } from './error-handler';
+import { FUNCTION } from './constants';
+import { buildConfig } from './config-handler';
+
+const inFlightResponse = {
+ isFetching: true,
+};
/**
- * Create Request Handler
+ * Sends an HTTP request to the specified URL using the provided configuration and returns a typed response.
+ *
+ * @typeParam ResponseData - The expected shape of the response data. Defaults to `DefaultResponse`.
+ * @typeParam RequestBody - The type of the request payload/body. Defaults to `DefaultPayload`.
+ * @typeParam QueryParams - The type of the query parameters. Defaults to `DefaultParams`.
+ * @typeParam PathParams - The type of the path parameters. Defaults to `DefaultUrlParams`.
+ *
+ * @param url - The endpoint URL to which the request will be sent.
+ * @param config - Optional configuration object for the request, including headers, method, body, query, and path parameters.
*
- * @param {RequestHandlerConfig} config - Configuration object for the request handler
- * @returns {Object} An object with methods for handling requests
+ * @returns A promise that resolves to a `FetchResponse` containing the typed response data and request metadata.
+ *
+ * @example
+ * ```typescript
+ * const { data } = await fetchf('/api/user', { method: 'GET' });
+ * console.log(data);
+ * ```
*/
-export function createRequestHandler(
- config: RequestHandlerConfig | null,
-): RequestHandlerReturnType {
- const sanitizedConfig = config ? sanitizeObject(config) : {};
- const handlerConfig: RequestHandlerConfig = {
- ...defaultConfig,
- ...sanitizedConfig,
- };
-
- mergeConfig('retry', handlerConfig, defaultConfig, sanitizedConfig);
- mergeConfig('headers', handlerConfig, defaultConfig, sanitizedConfig);
-
- /**
- * Immediately create instance of custom fetcher if it is defined
- */
- const requestInstance = sanitizedConfig.fetcher?.create?.(handlerConfig);
-
- /**
- * Get Provider Instance
- *
- * @returns {CreatedCustomFetcherInstance | null} Provider's instance
- */
- const getInstance = (): CreatedCustomFetcherInstance | null => {
- return requestInstance || null;
- };
-
- /**
- * Request function to make HTTP requests with the provided URL and configuration.
- *
- * @param {string} url - Request URL
- * @param {RequestConfig} reqConfig - Request config passed when making the request
- * @throws {ResponseError} If the request fails or is cancelled
- * @returns {Promise>} Response Data
- */
- const request = async <
- ResponseData = DefaultResponse,
- QueryParams = DefaultParams,
- PathParams = DefaultUrlParams,
- RequestBody = DefaultPayload,
- >(
- url: string,
- reqConfig: RequestConfig<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- > | null = null,
- ): Promise<
- FetchResponse
- > => {
- const _reqConfig = reqConfig ? sanitizeObject(reqConfig) : {};
-
- // Ensure immutability
- const mergedConfig = {
- ...handlerConfig,
- ..._reqConfig,
- };
-
- mergeConfig('retry', mergedConfig, handlerConfig, _reqConfig);
- mergeConfig('headers', mergedConfig, handlerConfig, _reqConfig);
+export async function fetchf<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ url: string,
+ reqConfig: RequestConfig<
+ ResponseData,
+ QueryParams,
+ PathParams,
+ RequestBody
+ > | null = null,
+): Promise> {
+ const fetcherConfig = buildConfig<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >(url, reqConfig);
+
+ const {
+ timeout,
+ cancellable,
+ cacheKey,
+ dedupeTime,
+ cacheTime,
+ staleTime,
+ refetchOnFocus,
+ refetchOnReconnect,
+ pollingInterval = 0,
+ } = fetcherConfig;
+ const isCacheEnabled = cacheTime !== undefined || staleTime !== undefined;
+
+ const needsCacheKey = !!(
+ cacheKey ||
+ timeout ||
+ dedupeTime ||
+ isCacheEnabled ||
+ cancellable ||
+ refetchOnFocus ||
+ refetchOnReconnect
+ );
+
+ let _cacheKey: string | null = null;
+
+ // Generate cache key if required
+ if (needsCacheKey) {
+ _cacheKey = generateCacheKey(fetcherConfig);
+ }
- let response: FetchResponse<
+ // Cache handling logic
+ if (_cacheKey && isCacheEnabled) {
+ const cached = getCachedResponse<
ResponseData,
RequestBody,
QueryParams,
PathParams
- > | null = null;
- const fetcherConfig = buildConfig(url, mergedConfig);
+ >(_cacheKey, cacheTime, fetcherConfig);
- const {
- timeout,
- cancellable,
- dedupeTime,
- cacheTime,
- cacheKey,
- pollingInterval = 0,
- } = mergedConfig;
-
- // Prevent performance overhead of cache access
- let _cacheKey: string | null = null;
-
- // Generate cache key if required
- if (cacheTime || dedupeTime || cancellable || timeout) {
- _cacheKey = cacheKey
- ? cacheKey(fetcherConfig)
- : generateCacheKey(fetcherConfig);
+ if (cached) {
+ return cached;
}
+ }
- // Cache handling logic
- if (cacheTime) {
- const cached = getCachedResponse<
- ResponseData,
- RequestBody,
- QueryParams,
- PathParams
- >(_cacheKey, cacheTime, mergedConfig.cacheBuster, fetcherConfig);
-
- if (cached) {
- return cached;
- }
- }
+ // Deduplication logic
+ if (_cacheKey && dedupeTime) {
+ const inflight = getInFlightPromise<
+ FetchResponse
+ >(_cacheKey, dedupeTime);
- // Deduplication logic
- if (_cacheKey && dedupeTime) {
- const inflight = getInFlightPromise(_cacheKey, dedupeTime);
-
- if (inflight) {
- return (await inflight) as FetchResponse<
- ResponseData,
- RequestBody,
- QueryParams,
- PathParams
- >;
- }
+ if (inflight) {
+ return inflight;
}
+ }
- // The actual request logic as a function (one poll attempt, with retries)
- const doRequestOnce = async () => {
- const {
- retries = 0,
- delay,
- backoff,
- retryOn,
- shouldRetry,
- maxDelay,
- resetTimeout,
- } = mergedConfig.retry as RetryOptions<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- >;
- let attempt = 0;
- let waitTime: number = delay || 0;
- const _retries = retries > 0 ? retries : 0;
-
- while (attempt <= _retries) {
- try {
- // Add the request to the queue. Make sure to handle deduplication, cancellation, timeouts in accordance to retry settings
- const controller = await queueRequest(
+ const retryConfig = fetcherConfig.retry || {};
+ const { retries = 0, resetTimeout } = retryConfig;
+
+ // The actual request logic as a function (one poll attempt, with retries)
+ const doRequestOnce = async (isStaleRevalidation = false, attempt = 0) => {
+ // If cache key is specified, we will handle optimistic updates
+ // and mark the request as in-flight, so to catch "fetching" state.
+ // This is useful for Optimistic UI updates (e.g., showing loading spinners).
+ if (!attempt) {
+ if (_cacheKey && !isStaleRevalidation) {
+ if (staleTime) {
+ const existingCache = getCachedResponse(
_cacheKey,
- fetcherConfig.url as string,
- timeout,
- dedupeTime,
- cancellable,
- // Reset timeouts by default or when retries are ON
- !!(timeout && (!_retries || resetTimeout)),
+ cacheTime,
+ fetcherConfig,
);
- // Shallow copy to ensure basic idempotency
- // Note that the refrence of the main object does not change here so it is safe in context of queue management and interceptors
- const requestConfig: RequestConfig = {
- signal: controller.signal,
- ...fetcherConfig,
- };
-
- // Local interceptors
- await applyInterceptor(requestConfig, _reqConfig.onRequest);
-
- // Global interceptors
- await applyInterceptor(requestConfig, handlerConfig.onRequest);
-
- response = requestInstance?.request
- ? await requestInstance.request(requestConfig)
- : ((await fetch(
- requestConfig.url as string,
- requestConfig as RequestInit,
- )) as unknown as FetchResponse<
- ResponseData,
- RequestBody,
- QueryParams,
- PathParams
- >);
-
- // Add more information to response object
- if (response instanceof Response) {
- response.config = requestConfig;
- response.data = await parseResponseData(response);
-
- // Check if the response status is not outside the range 200-299 and if so, output error
- if (!response.ok) {
- throw new ResponseError(
- `${requestConfig.method} to ${requestConfig.url} failed! Status: ${response.status || null}`,
- requestConfig,
- response,
- );
- }
+ // Don't notify subscribers when cache exists
+ // Let them continue showing stale data during background revalidation
+ if (!existingCache) {
+ setCache(_cacheKey, inFlightResponse, cacheTime, staleTime);
+ notifySubscribers(_cacheKey, inFlightResponse);
}
+ } else {
+ notifySubscribers(_cacheKey, inFlightResponse);
+ }
+ }
- // Local interceptors
- await applyInterceptor(response, _reqConfig.onResponse);
-
- // Global interceptors
- await applyInterceptor(response, handlerConfig.onResponse);
-
- removeRequestFromQueue(_cacheKey);
+ // Attach cache key so that it can be reused in interceptors or in the final response
+ fetcherConfig.cacheKey = _cacheKey;
+ }
- const output = prepareResponse<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- >(response, requestConfig);
+ const url = fetcherConfig.url as string;
- // Retry on response logic
- if (
- shouldRetry &&
- attempt < _retries &&
- (await shouldRetry(output, attempt))
- ) {
- logger(
- mergedConfig,
- `Attempt ${attempt + 1} failed response data check. Retry in ${waitTime}ms.`,
- );
+ // Add the request to the queue. Make sure to handle deduplication, cancellation, timeouts in accordance to retry settings
+ const controller = markInFlight(
+ _cacheKey,
+ url,
+ timeout,
+ dedupeTime || 0,
+ !!cancellable,
+ // Enable timeout either by default or when retries & resetTimeout are enabled
+ !!(timeout && (!attempt || resetTimeout)),
+ );
- await delayInvocation(waitTime);
+ // Do not create a shallow copy to maintain idempotency here.
+ // This ensures the original object is mutated by interceptors whenever needed, including retry logic.
+ const requestConfig = fetcherConfig;
- waitTime *= backoff || 1;
- waitTime = Math.min(waitTime, maxDelay || waitTime);
- attempt++;
+ requestConfig.signal = controller.signal;
- continue; // Retry the request
- }
+ let output: FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >;
+ let response: FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ > | null = null;
- if (
- cacheTime &&
- _cacheKey &&
- (!requestConfig.skipCache ||
- !requestConfig.skipCache(output, requestConfig))
- ) {
- setCache(_cacheKey, output);
- }
+ try {
+ if (fetcherConfig.onRequest) {
+ await applyInterceptors(fetcherConfig.onRequest, requestConfig);
+ }
- return output;
- } catch (err) {
- const error = err as ResponseError<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- >;
-
- // Append additional information to Network, CORS or any other fetch() errors
- error.status = error?.status || response?.status || 0;
- error.statusText = error?.statusText || response?.statusText || '';
- error.config = fetcherConfig;
- error.request = fetcherConfig;
- error.response = response;
-
- // Prepare Extended Response
- const output = prepareResponse<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- >(response, fetcherConfig, error);
-
- if (
- // We check retries provided regardless of the shouldRetry being provided so to avoid infinite loops.
- // It is a fail-safe so to prevent excessive retry attempts even if custom retry logic suggests a retry.
- attempt === _retries || // Stop if the maximum retries have been reached
- !retryOn?.includes(error.status) || // Check if the error status is retryable
- !shouldRetry ||
- !(await shouldRetry(output, attempt)) // If shouldRetry is defined, evaluate it
- ) {
- if (!isRequestCancelled(error as ResponseError)) {
- logger(mergedConfig, 'FETCH ERROR', error as ResponseError);
- }
-
- // Local interceptors
- await applyInterceptor(error, _reqConfig.onError);
-
- // Global interceptors
- await applyInterceptor(error, handlerConfig.onError);
-
- // Remove the request from the queue
- removeRequestFromQueue(_cacheKey);
-
- // Timeouts and request cancellations using AbortController do not throw any errors unless rejectCancelled is true.
- // Only handle the error if the request was not cancelled, or if it was cancelled and rejectCancelled is true.
- const isCancelled = isRequestCancelled(error as ResponseError);
- const shouldHandleError =
- !isCancelled || mergedConfig.rejectCancelled;
-
- if (shouldHandleError) {
- const errorHandlingStrategy = mergedConfig.strategy;
-
- // Reject the promise
- if (errorHandlingStrategy === 'reject') {
- return Promise.reject(error);
- } // Hang the promise
- else if (errorHandlingStrategy === 'silent') {
- await new Promise(() => null);
- }
- }
-
- return output;
- }
+ // Custom fetcher
+ const fn = fetcherConfig.fetcher;
- // If the error status is 429 (Too Many Requests), handle rate limiting
- if (error.status === 429) {
- // Try to extract the "Retry-After" value from the response headers
- const retryAfterMs = getRetryAfterMs(output);
+ response = (fn
+ ? await fn(
+ url,
+ requestConfig,
+ )
+ : await fetch(
+ url,
+ requestConfig as RequestInit,
+ )) as unknown as FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >;
- // If a valid retry-after value is found, override the wait time before next retry
- if (retryAfterMs !== null) {
- waitTime = retryAfterMs;
- }
+ if (isObject(response)) {
+ // Case 1: Native Response instance
+ if (typeof Response === FUNCTION && response instanceof Response) {
+ response.data = await parseResponseData(response);
+ } else if (fn) {
+ // Case 2: Custom fetcher that returns a response object
+ if (!('data' in response && 'body' in response)) {
+ // Case 3: Raw data, wrap it
+ response = { data: response } as unknown as FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >;
}
+ }
- logger(
- mergedConfig,
- `Attempt ${attempt + 1} failed. Retry in ${waitTime}ms.`,
+ // Attach config and data to the response
+ // This is useful for custom fetchers that do not return a Response instance
+ // and for interceptors that may need to access the request config
+ response.config = requestConfig;
+
+ // Check if the response status is not outside the range 200-299 and if so, output error
+ // This is the pattern for fetch responses as per spec, but custom fetchers may not follow it so we check for `ok` property
+ if (response.ok !== undefined && !response.ok) {
+ throw new ResponseError(
+ `${requestConfig.method} to ${url} failed! Status: ${response.status || null}`,
+ requestConfig,
+ response,
);
-
- await delayInvocation(waitTime);
-
- waitTime *= backoff || 1;
- waitTime = Math.min(waitTime, maxDelay || waitTime);
- attempt++;
}
}
- return prepareResponse<
+ output = prepareResponse<
ResponseData,
+ RequestBody,
QueryParams,
- PathParams,
- RequestBody
- >(response, fetcherConfig);
- };
-
- // If polling is enabled, use withPolling to handle the request
- const doRequestPromise =
- pollingInterval > 0
- ? withPolling<
- FetchResponse
- >(
- doRequestOnce,
- pollingInterval,
- mergedConfig.shouldStopPolling,
- mergedConfig.maxPollingAttempts,
- mergedConfig.pollingDelay,
- )
- : doRequestOnce();
+ PathParams
+ >(response, requestConfig);
- // If deduplication is enabled, store the in-flight promise immediately
- if (_cacheKey && dedupeTime) {
- setInFlightPromise(_cacheKey, doRequestPromise);
- }
+ const onResponse = fetcherConfig.onResponse;
- return doRequestPromise;
- };
+ if (onResponse) {
+ await applyInterceptors(onResponse, output);
+ }
+ } catch (_error) {
+ const error = _error as ResponseError<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >;
+
+ // Append additional information to Network, CORS or any other fetch() errors
+ enhanceError(
+ error,
+ response,
+ requestConfig,
+ );
- return {
- getInstance,
- config: handlerConfig,
- request,
+ // Prepare Extended Response
+ output = prepareResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >(response, requestConfig, error);
+ }
+
+ return output;
};
-}
-/**
- * Output error response depending on chosen strategy
- *
- * @param {ResponseError} error Error instance
- * @returns {boolean} True if request is aborted
- */
-const isRequestCancelled = (error: ResponseError): boolean => {
- return error.name === ABORT_ERROR || error.name === CANCELLED_ERROR;
-};
+ // Inline and minimize function wrappers for performance
+ const baseRequest =
+ retries > 0 ? () => withRetry(doRequestOnce, retryConfig) : doRequestOnce;
+
+ const requestWithErrorHandling = (isStaleRevalidation = false) =>
+ withErrorHandling(
+ isStaleRevalidation,
+ baseRequest,
+ fetcherConfig,
+ );
+
+ // Avoid unnecessary function wrapping if polling is not enabled
+ const doRequestPromise = pollingInterval
+ ? withPolling(
+ requestWithErrorHandling,
+ pollingInterval,
+ fetcherConfig.shouldStopPolling,
+ fetcherConfig.maxPollingAttempts,
+ fetcherConfig.pollingDelay,
+ )
+ : requestWithErrorHandling();
+
+ // If deduplication is enabled, store the in-flight promise immediately
+ if (_cacheKey) {
+ if (dedupeTime) {
+ setInFlightPromise(_cacheKey, doRequestPromise);
+ }
-/**
- * Logs messages or errors using the configured logger's `warn` method.
- *
- * @param {RequestConfig} reqConfig - Request config passed when making the request
- * @param {...(string | ResponseError)} args - Messages or errors to log.
- */
-const logger = (
- reqConfig: RequestConfig,
- ...args: (string | ResponseError)[]
-): void => {
- const logger = reqConfig.logger;
-
- if (logger && logger.warn) {
- logger.warn(...args);
+ addRevalidator(
+ _cacheKey,
+ requestWithErrorHandling,
+ undefined,
+ staleTime,
+ requestWithErrorHandling,
+ !!refetchOnFocus,
+ !!refetchOnReconnect,
+ );
}
-};
+
+ return doRequestPromise;
+}
diff --git a/src/response-parser.ts b/src/response-parser.ts
index 8dd8e3ee..d7513191 100644
--- a/src/response-parser.ts
+++ b/src/response-parser.ts
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
+import { mutate } from './cache-manager';
import {
APPLICATION_CONTENT_TYPE,
APPLICATION_JSON,
CONTENT_TYPE,
+ FUNCTION,
OBJECT,
} from './constants';
import {
@@ -14,7 +16,7 @@ import {
DefaultUrlParams,
DefaultPayload,
} from './types';
-import { flattenData, processHeaders } from './utils';
+import { flattenData, isObject, processHeaders } from './utils';
/**
* Parses the response data based on the Content-Type header.
@@ -22,11 +24,16 @@ import { flattenData, processHeaders } from './utils';
* @param response - The Response object to parse.
* @returns A Promise that resolves to the parsed data.
*/
-export async function parseResponseData(
- response: FetchResponse,
+export async function parseResponseData<
+ ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
+ QueryParams = DefaultParams,
+ PathParams = DefaultUrlParams,
+>(
+ response: FetchResponse,
): Promise {
// Bail early for HEAD requests or status codes, or any requests that never have a body
- if (!response?.body) {
+ if (!response || !response.body) {
return null;
}
@@ -83,15 +90,15 @@ export async function parseResponseData(
* Prepare response object with additional information.
*
* @param Response. It may be "null" in case of request being aborted.
- * @param {RequestConfig} requestConfig - Request config
+ * @param {RequestConfig} config - Request config
* @param error - whether the response is erroneous
- * @returns {FetchResponse} Response data
+ * @returns {FetchResponse} Response data
*/
export const prepareResponse = <
ResponseData = DefaultResponse,
+ RequestBody = DefaultPayload,
QueryParams = DefaultParams,
PathParams = DefaultUrlParams,
- RequestBody = DefaultPayload,
>(
response: FetchResponse<
ResponseData,
@@ -99,20 +106,22 @@ export const prepareResponse = <
QueryParams,
PathParams
> | null,
- requestConfig: RequestConfig<
- ResponseData,
- QueryParams,
- PathParams,
- RequestBody
- >,
+ config: RequestConfig,
error: ResponseError<
ResponseData,
+ RequestBody,
QueryParams,
- PathParams,
- RequestBody
+ PathParams
> | null = null,
): FetchResponse => {
- const defaultResponse = requestConfig.defaultResponse ?? null;
+ const defaultResponse = config.defaultResponse;
+ const cacheKey = config.cacheKey;
+ const mutatator = mutate.bind(null, cacheKey as string) as FetchResponse<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ >['mutate'];
// This may happen when request is cancelled.
if (!response) {
@@ -120,9 +129,11 @@ export const prepareResponse = <
ok: false,
// Enhance the response with extra information
error,
- data: defaultResponse,
+ data: defaultResponse ?? null,
headers: null,
- config: requestConfig,
+ config,
+ mutate: mutatator,
+ isFetching: false,
} as unknown as FetchResponse<
ResponseData,
RequestBody,
@@ -131,50 +142,69 @@ export const prepareResponse = <
>;
}
+ const isNativeResponse =
+ typeof Response === FUNCTION && response instanceof Response;
+
let data = response.data;
// Set the default response if the provided data is an empty object
if (
- data === undefined ||
- data === null ||
- (typeof data === OBJECT && Object.keys(data).length === 0)
+ defaultResponse !== undefined &&
+ (data === undefined ||
+ data === null ||
+ (typeof data === OBJECT && Object.keys(data).length === 0))
) {
- data = defaultResponse;
+ response.data = data = defaultResponse;
}
- if (requestConfig.flattenResponse) {
- response.data = flattenData(data);
+ if (config.flattenResponse) {
+ response.data = data = flattenData(data);
}
- // If it's a custom fetcher, and it does not return any Response instance, it may have its own internal handler
- if (!(response instanceof Response)) {
- return response;
+ if (config.select) {
+ response.data = data = config.select(data);
}
+ const headers = processHeaders(response.headers);
+
// Native fetch Response extended by extra information
- return {
- body: response.body,
- bodyUsed: response.bodyUsed,
- ok: response.ok,
- redirected: response.redirected,
- type: response.type,
- url: response.url,
- status: response.status,
- statusText: response.statusText,
-
- // Convert methods to use arrow functions to preserve correct return types
- blob: () => response.blob(),
- json: () => response.json(),
- text: () => response.text(),
- clone: () => response.clone(),
- arrayBuffer: () => response.arrayBuffer(),
- formData: () => response.formData(),
- bytes: () => response.bytes(),
-
- // Enhance the response with extra information
- error,
- data,
- headers: processHeaders(response.headers),
- config: requestConfig,
- };
+ if (isNativeResponse) {
+ return {
+ body: response.body,
+ bodyUsed: response.bodyUsed,
+ ok: response.ok,
+ redirected: response.redirected,
+ type: response.type,
+ url: response.url,
+ status: response.status,
+ statusText: response.statusText,
+
+ // Convert methods to use arrow functions to preserve correct return types
+ blob: () => response.blob(),
+ json: () => response.json(),
+ text: () => response.text(),
+ clone: () => response.clone(),
+ arrayBuffer: () => response.arrayBuffer(),
+ formData: () => response.formData(),
+ bytes: () => response.bytes(),
+
+ // Enhance the response with extra information
+ error,
+ data,
+ headers,
+ config,
+ mutate: mutatator,
+ isFetching: false,
+ };
+ }
+
+ // If it's a custom fetcher, and it does not return any Response instance, it may have its own internal handler
+ if (isObject(response)) {
+ response.error = error;
+ response.headers = headers;
+ response.isFetching = false;
+ response.mutate = mutatator;
+ }
+
+ return response;
};
diff --git a/src/retry-handler.ts b/src/retry-handler.ts
index bf2ec15a..c6b3096b 100644
--- a/src/retry-handler.ts
+++ b/src/retry-handler.ts
@@ -1,4 +1,7 @@
-import type { FetchResponse } from './types';
+import { applyInterceptors } from './interceptor-manager';
+import type { FetchResponse, RetryConfig, RetryFunction } from './types';
+import { delayInvocation, timeNow } from './utils';
+import { generateCacheKey } from './cache-manager';
/**
* Calculates the number of milliseconds to wait before retrying a request,
@@ -31,10 +34,186 @@ export function getRetryAfterMs(
const date = new Date(retryAfter);
if (!isNaN(date.getTime())) {
- const ms = date.getTime() - Date.now();
+ const ms = date.getTime() - timeNow();
return ms > 0 ? ms : 0;
}
return null;
}
+
+/**
+ * Executes a request function with retry logic according to the provided configuration.
+ *
+ * The function attempts the request up to the specified number of retries, applying delay and backoff strategies.
+ * Retries can be triggered based on response status codes, custom logic, or the presence of a `Retry-After` header.
+ * Optionally, an `onRetry` interceptor can be invoked before each retry attempt.
+ *
+ * @typeParam ResponseData - The type of the response data.
+ * @typeParam RequestBody - The type of the request body.
+ * @typeParam QueryParams - The type of the query parameters.
+ * @typeParam PathParams - The type of the path parameters.
+ * @param requestFn - The function that performs the request. Receives `isStaleRevalidation` and `attempt` as arguments.
+ * @param config - The retry configuration, including retry count, delay, backoff, retry conditions, and hooks.
+ * @returns A promise resolving to the fetch response, or rejecting if all retries are exhausted.
+ * @throws Error if the maximum number of retries is exceeded or a non-retriable error occurs.
+ */
+export async function withRetry<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams,
+>(
+ requestFn: (
+ isStaleRevalidation: boolean,
+ attempt: number,
+ ) => Promise<
+ FetchResponse
+ >,
+ config: RetryConfig,
+): Promise> {
+ const {
+ retries = 0,
+ delay = 0,
+ backoff = 1,
+ maxDelay,
+ retryOn = [],
+ shouldRetry,
+ } = config;
+
+ let attempt = 0;
+ let waitTime = delay;
+ const maxRetries = retries > 0 ? retries : 0;
+ let output: FetchResponse;
+
+ while (attempt <= maxRetries) {
+ // Subsequent attempts will have output defined, but the first attempt may not.
+ // Let's apply onRetry interceptor and regenerate cache key if ot really changes.
+ if (attempt > 0 && output!) {
+ const cfg = output.config;
+ const onRetry = cfg.onRetry;
+
+ if (onRetry) {
+ await applyInterceptors(onRetry, output, attempt);
+
+ // If the key was automatically generated, we need to regenerate it as config may change.
+ // We don't detect whether config changed for performance reasons.
+ if (cfg._isAutoKey) {
+ cfg._prevKey = cfg.cacheKey as string;
+ cfg.cacheKey = generateCacheKey(cfg, false);
+ }
+ }
+ }
+
+ output = await requestFn(true, attempt); // isStaleRevalidation=false, isFirstAttempt=attempt===0
+ const error = output.error;
+
+ // Check if we should retry based on successful response
+ if (!error) {
+ if (shouldRetry && attempt < maxRetries) {
+ const shouldRetryResult = await shouldRetry(output, attempt);
+
+ if (shouldRetryResult) {
+ await delayInvocation(waitTime);
+ waitTime *= backoff || 1;
+ waitTime = Math.min(waitTime, maxDelay || waitTime);
+ attempt++;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ // Determine if we should stop retrying
+ const shouldStopRetrying = await getShouldStopRetrying(
+ output,
+ attempt,
+ maxRetries,
+ shouldRetry,
+ retryOn,
+ );
+
+ if (shouldStopRetrying) {
+ break;
+ }
+
+ // If we should not stop retrying, continue to the next attempt
+ // If the error status is 429 (Too Many Requests), handle rate limiting
+ if (error.status === 429) {
+ // Try to extract the "Retry-After" value from the response headers
+ const retryAfterMs = getRetryAfterMs(output);
+
+ // If a valid retry-after value is found, override the wait time before next retry
+ if (retryAfterMs !== null) {
+ waitTime = retryAfterMs;
+ }
+ }
+
+ await delayInvocation(waitTime);
+ waitTime *= backoff || 1;
+ waitTime = Math.min(waitTime, maxDelay || waitTime);
+ attempt++;
+ }
+
+ return output!;
+}
+
+/**
+ * Determines whether to stop retrying based on the error, current attempt count, and retry configuration.
+ *
+ * This function checks:
+ * - If the maximum number of retries has been reached.
+ * - If a custom `shouldRetry` callback is provided, its result is used to decide.
+ * - If no custom logic is provided, falls back to checking if the error status is included in the `retryOn` list.
+ *
+ * @typeParam ResponseData - The type of the response data.
+ * @typeParam RequestBody - The type of the request body.
+ * @typeParam QueryParams - The type of the query parameters.
+ * @typeParam PathParams - The type of the path parameters.
+ * @param output - The response object containing the error and request configuration.
+ * @param attempt - The current retry attempt number.
+ * @param maxRetries - The maximum number of retry attempts allowed.
+ * @param shouldRetry - Optional custom function to determine if a retry should occur.
+ * @param retryOn - Optional list of HTTP status codes that should trigger a retry.
+ * @returns A promise resolving to `true` if retrying should stop, or `false` to continue retrying.
+ */
+export async function getShouldStopRetrying<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams,
+>(
+ output: FetchResponse,
+ attempt: number,
+ maxRetries: number,
+ shouldRetry?: RetryFunction<
+ ResponseData,
+ RequestBody,
+ QueryParams,
+ PathParams
+ > | null,
+ retryOn: number[] = [],
+): Promise {
+ // Safety first: always respect max retries
+ // We check retries provided regardless of the shouldRetry being provided so to avoid infinite loops.
+ // It is a fail-safe so to prevent excessive retry attempts even if custom retry logic suggests a retry.
+ if (attempt === maxRetries) {
+ return true;
+ }
+
+ let customDecision: boolean | null = null;
+
+ // Get custom decision if shouldRetry is provided
+ if (shouldRetry) {
+ const result = await shouldRetry(output, attempt);
+ customDecision = result;
+
+ // Decision cascade:
+ if (customDecision !== null) {
+ return !customDecision;
+ }
+ }
+
+ return !(retryOn || []).includes(output.error?.status ?? 0);
+}
diff --git a/src/revalidator-manager.ts b/src/revalidator-manager.ts
new file mode 100644
index 00000000..18781647
--- /dev/null
+++ b/src/revalidator-manager.ts
@@ -0,0 +1,250 @@
+/**
+ * @module revalidator-manager
+ *
+ * Provides utilities for managing cache revalidation functions, including:
+ * - Registering and unregistering revalidators for specific cache keys.
+ * - Triggering revalidation for a given key.
+ * - Enabling or disabling automatic revalidation on window focus and if user comes back online for specific keys.
+ * - Attaching and removing global focus and online event handlers to trigger revalidation.
+ *
+ * Revalidators are functions that can be registered to revalidate cache entries when needed.
+ * They are typically used to refresh data in the cache when the window gains focus or when specific actions occur.
+ * @performance O(1) lookup by key makes it blazing fast to register, unregister, and revalidate cache entries.
+ * - Designed for high performance: minimizes unnecessary re-renders and leverages fast cache key generation.
+ * - Integrates with a global cache and pub/sub system for efficient state updates across contexts.
+ * - Handles automatic revalidation, deduplication, retries, and cache management out of the box.
+ * @remarks
+ * - Designed to be used in various environments (Deno, Node.js, Bun, Browser, etc.) to ensure cache consistency and freshness.
+ */
+import { addTimeout, removeTimeout } from './timeout-wheel';
+import { FetchResponse } from './types';
+import { isBrowser, noop, timeNow } from './utils';
+
+export type RevalidatorFn = (
+ isStaleRevalidation?: boolean,
+) => Promise;
+
+type EventType = 'focus' | 'online';
+
+type RevalidatorEntry = [
+ RevalidatorFn, // main revalidator
+ number, // lastUsed
+ number, // ttl
+ number?, // staleTime
+ RevalidatorFn?, // bgRevalidator
+ boolean?, // refetchOnFocus
+ boolean?, // refetchOnReconnect
+];
+
+const DEFAULT_TTL = 3 * 60 * 1000; // Default TTL of 3 minutes
+const revalidators = new Map();
+
+/**
+ * Stores global event handlers for cache revalidation events (e.g., focus, online).
+ * This avoids attaching multiple event listeners by maintaining a single handler per event type.
+ * Event handlers are registered as needed when revalidators are registered with the corresponding flags.
+ * @remarks
+ * - Improves performance by reducing the number of event listeners.
+ * - Enables efficient O(1) lookup and management of event handlers for revalidation.
+ */
+const eventHandlers = new Map void>();
+
+/**
+ * Triggers revalidation for all registered entries based on the given event type.
+ * For example, if it's a 'focus' event, it will revalidate entries that have the `refetchOnFocus` flag set.
+ * Updates the timestamp and invokes the revalidator function for each applicable entry.
+ *
+ * @param type - The type of event that caused the revalidation (e.g., 'focus' or 'online').
+ * @param isStaleRevalidation - If `true`, uses background revalidator and doesn't mark as in-flight.
+ */
+export function revalidateAll(
+ type: EventType,
+ isStaleRevalidation: boolean = true,
+) {
+ const flagIndex = type === 'focus' ? 5 : 6;
+ const now = timeNow();
+
+ revalidators.forEach((entry) => {
+ if (!entry[flagIndex]) {
+ return;
+ }
+
+ entry[1] = now;
+
+ // If it's a stale revalidation, use the background revalidator function
+ const revalidator = isStaleRevalidation ? entry[4] : entry[0];
+
+ if (revalidator) {
+ Promise.resolve(revalidator(isStaleRevalidation)).catch(noop);
+ }
+ });
+}
+
+/**
+ * Revalidates an entry by executing the registered revalidation function.
+ *
+ * @param key The unique identifier for the cache entry to revalidate. If `null`, no revalidation occurs.
+ * @param isStaleRevalidation - If `true`, it does not mark revalidated requests as in-flight.
+ * @returns A promise that resolves to the result of the revalidator function, or
+ * `null` if no key or revalidator is found, or a `FetchResponse` if applicable.
+ */
+export async function revalidate(
+ key: string | null,
+ isStaleRevalidation: boolean = false,
+): Promise {
+ // If no key is provided, no revalidation occurs
+ if (!key) {
+ return null;
+ }
+
+ const entry = revalidators.get(key);
+
+ if (entry) {
+ // Update only the lastUsed timestamp without resetting the whole array
+ entry[1] = timeNow();
+
+ const revalidator = isStaleRevalidation ? entry[4] : entry[0];
+
+ // If no revalidator function is registered, return null
+ if (revalidator) {
+ return await revalidator(isStaleRevalidation);
+ }
+ }
+
+ // If no revalidator is registered for the key, return null
+ return null;
+}
+
+/**
+ * Removes all revalidators associated with the specified event type.
+ *
+ * @param type - The event type whose revalidators should be removed.
+ */
+export function removeRevalidators(type: EventType) {
+ removeEventHandler(type);
+
+ const flagIndex = type === 'focus' ? 5 : 6;
+
+ // Clear all revalidators with this flag
+ revalidators.forEach((entry, key) => {
+ if (entry[flagIndex]) {
+ removeRevalidator(key);
+ }
+ });
+}
+
+/**
+ * Registers a generic revalidation event handler for the specified event type.
+ * Ensures the handler is only added once and only in browser environments.
+ *
+ * @param event - The type of event to listen for (e.g., 'focus', 'visibilitychange').
+ */
+function addEventHandler(event: EventType) {
+ if (!isBrowser() || eventHandlers.has(event)) {
+ return;
+ }
+
+ const handler = revalidateAll.bind(null, event, true);
+
+ eventHandlers.set(event, handler);
+ window.addEventListener(event, handler);
+}
+
+/**
+ * Removes the generic event handler for the specified event type from the window object.
+ *
+ * @param event - The type of event whose handler should be removed.
+ */
+function removeEventHandler(event: EventType) {
+ if (!isBrowser()) {
+ return;
+ }
+
+ const handler = eventHandlers.get(event);
+
+ if (handler) {
+ window.removeEventListener(event, handler);
+
+ eventHandlers.delete(event);
+ }
+}
+
+/**
+ * Registers a revalidation functions for a specific cache key.
+ *
+ * @param {string} key Cache key to utilize
+ * @param {RevalidatorFn} revalidatorFn Main revalidation function (marks in-flight requests)
+ * @param {number} [ttl] Time to live in milliseconds (default: 3 minutes)
+ * @param {number} [staleTime] Time (in seconds) after which the cache entry is considered stale
+ * @param {RevalidatorFn} [bgRevalidatorFn] For stale revalidation (does not mark in-flight requests)
+ * @param {boolean} [refetchOnFocus] Whether to revalidate on window focus
+ * @param {boolean} [refetchOnReconnect] Whether to revalidate on network reconnect
+ */
+export function addRevalidator(
+ key: string,
+ revalidatorFn: RevalidatorFn, // Main revalidation function (marks in-flight requests)
+ ttl?: number,
+ staleTime?: number,
+ bgRevalidatorFn?: RevalidatorFn, // For stale revalidation (does not mark in-flight requests)
+ refetchOnFocus?: boolean,
+ refetchOnReconnect?: boolean,
+) {
+ revalidators.set(key, [
+ revalidatorFn,
+ timeNow(),
+ ttl ?? DEFAULT_TTL,
+ staleTime,
+ bgRevalidatorFn,
+ refetchOnFocus,
+ refetchOnReconnect,
+ ]);
+
+ if (refetchOnFocus) {
+ addEventHandler('focus');
+ }
+
+ if (refetchOnReconnect) {
+ addEventHandler('online');
+ }
+
+ if (staleTime) {
+ addTimeout('s:' + key, revalidate.bind(null, key, true), staleTime * 1000);
+ }
+}
+
+export function removeRevalidator(key: string) {
+ revalidators.delete(key);
+
+ // Clean up stale timer
+ removeTimeout('s:' + key);
+}
+
+/**
+ * Periodically cleans up expired revalidators from the registry.
+ * Removes any revalidator whose TTL has expired.
+ *
+ * @param {number} intervalMs How often to run cleanup (default: 3 minutes)
+ * @returns {() => void} A function to stop the periodic cleanup
+ */
+export function startRevalidatorCleanup(
+ intervalMs: number = DEFAULT_TTL,
+): () => void {
+ const intervalId = setInterval(() => {
+ const now = timeNow();
+
+ revalidators.forEach(
+ ([, lastUsed, ttl, , , refetchOnFocus, refetchOnReconnect], key) => {
+ // Skip focus-only or reconnect-only revalidators to keep them alive
+ if (refetchOnFocus || refetchOnReconnect) {
+ return;
+ }
+
+ if (ttl > 0 && now - lastUsed > ttl) {
+ removeRevalidator(key);
+ }
+ },
+ );
+ }, intervalMs);
+
+ return () => clearInterval(intervalId);
+}
diff --git a/src/timeout-wheel.ts b/src/timeout-wheel.ts
new file mode 100644
index 00000000..e78cea54
--- /dev/null
+++ b/src/timeout-wheel.ts
@@ -0,0 +1,127 @@
+/**
+ * @module timeout-wheel
+ * @description
+ * Ultra-minimal timing wheel implementation optimized for max performance & many requests.
+ * For most of the cases it's 4-100x faster than setTimeout and setInterval alone.
+ * Provides efficient scheduling and cancellation of timeouts using a circular array.
+ *
+ * Position 0 â 1 â 2 â ... â 599 â 0 â 1 â 2 ...
+ * Time: 0s 1s 2s 599s 600s 601s 602s
+ *
+ * The timing wheel consists of 600 slots (one per second for 10 min).
+ * Each slot contains a list of timeout items, each associated with a unique key and callback.
+ * Timeouts are scheduled by placing them in the appropriate slot based on the delay in seconds.
+ * The wheel advances every second, executing and removing callbacks as their timeouts expire.
+ * Defaults to setTimeout if the delay exceeds 10 minutes or is not divisible by 1000.
+ *
+ * @remarks
+ * - Designed for minimal footprint and simplicity.
+ * - Only supports second-level granularity (minimum timeout: 1 second).
+ * - Automatically stops the internal timer when no timeouts remain.
+ */
+
+import { noop } from './utils';
+
+type TimeoutCallback = () => unknown | Promise;
+type TimeoutItem = [string, TimeoutCallback]; // [key, callback]
+
+const WHEEL_SIZE = 600; // 600 slots for 10 min (1 slot per second)
+const SECOND = 1000; // 1 second in milliseconds
+const MAX_WHEEL_MS = WHEEL_SIZE * SECOND;
+const wheel: TimeoutItem[][] = Array(WHEEL_SIZE)
+ .fill(0)
+ .map(() => []);
+
+const keyMap = new Map();
+let position = 0;
+let timer: NodeJS.Timeout | null = null;
+
+const handleCallback = ([key, callback]: TimeoutItem): void => {
+ keyMap.delete(key);
+
+ try {
+ const result = callback();
+ if (result && result instanceof Promise) {
+ // Silently ignore async errors to prevent wheel from stopping
+ result.catch(noop);
+ }
+ } catch {
+ // Ignore callback errors to prevent wheel from stopping
+ }
+};
+
+export const addTimeout = (
+ key: string,
+ cb: TimeoutCallback,
+ ms: number,
+): void => {
+ removeTimeout(key);
+
+ // Fallback to setTimeout if wheel size is exceeded or ms is not divisible by SECOND
+ if (ms > MAX_WHEEL_MS || ms % SECOND !== 0) {
+ keyMap.set(key, [setTimeout(handleCallback.bind(null, [key, cb]), ms)]); // Store timeout ID instead of slot
+
+ return;
+ }
+
+ // No need for Math.ceil here since ms is guaranteed by modulo above
+ const seconds = ms / SECOND;
+ const slot = (position + seconds) % WHEEL_SIZE;
+
+ wheel[slot].push([key, cb]);
+ keyMap.set(key, slot);
+
+ if (!timer) {
+ timer = setInterval(() => {
+ position = (position + 1) % WHEEL_SIZE;
+ wheel[position].forEach(handleCallback);
+ wheel[position] = [];
+
+ if (!keyMap.size && timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+ }, SECOND);
+ }
+};
+
+export const removeTimeout = (key: string): void => {
+ const slotOrTimeout = keyMap.get(key);
+
+ if (slotOrTimeout !== undefined) {
+ // It's a Timeout object from setTimeout
+ if (Array.isArray(slotOrTimeout)) {
+ clearTimeout(slotOrTimeout[0]);
+ } else {
+ wheel[slotOrTimeout].splice(
+ wheel[slotOrTimeout].findIndex(([k]) => k === key),
+ 1,
+ );
+ }
+
+ keyMap.delete(key);
+
+ if (!keyMap.size && timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+ }
+};
+
+export const clearAllTimeouts = () => {
+ // Clear native setTimeout timeouts first!
+ keyMap.forEach((value) => {
+ if (Array.isArray(value)) {
+ clearTimeout(value[0]);
+ }
+ });
+
+ if (timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+
+ keyMap.clear();
+ wheel.forEach((slot) => (slot.length = 0));
+ position = 0;
+};
diff --git a/src/tsconfig.json b/src/tsconfig.json
index 145f6396..3b9438d8 100644
--- a/src/tsconfig.json
+++ b/src/tsconfig.json
@@ -21,7 +21,14 @@
"noImplicitReturns": true,
"noImplicitUseStrict": false,
"noFallthroughCasesInSwitch": true,
- "lib": ["esnext", "ES2018", "dom"]
+ "lib": ["esnext", "ES2018", "dom"],
+ "baseUrl": ".",
+ "paths": {
+ "react": ["../node_modules/react"],
+ "react-dom": ["../node_modules/react-dom"],
+ "fetchff/*": ["../src/*"],
+ "fetchff": ["../src/index.ts"]
+ }
},
"exclude": ["demo", "scripts", "node_modules"]
}
diff --git a/src/types/api-handler.ts b/src/types/api-handler.ts
index eeec9c5a..f31ff0f8 100644
--- a/src/types/api-handler.ts
+++ b/src/types/api-handler.ts
@@ -1,12 +1,10 @@
+import type { DefaultRequestType } from './request-handler';
+import type { Req } from './request-handler';
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
RequestConfig,
- RequestHandlerConfig,
FetchResponse,
- RequestHandlerReturnType,
- CreatedCustomFetcherInstance,
DefaultResponse,
- ExtendedRequestConfig,
} from './request-handler';
// Common type definitions
@@ -19,14 +17,21 @@ declare const emptyObjectSymbol: unique symbol;
export type EmptyObject = { [emptyObjectSymbol]?: never };
-export type DefaultParams = Record;
-export type DefaultUrlParams = Record;
+export type DefaultParams =
+ | Record
+ | URLSearchParams
+ | NameValuePair[]
+ | EmptyObject
+ | null;
+
+export type DefaultUrlParams = Record;
export type DefaultPayload = Record;
export declare type QueryParams =
| (ParamsType & EmptyObject)
| URLSearchParams
| NameValuePair[]
+ | EmptyObject
| null;
export declare type UrlPathParams =
@@ -42,100 +47,141 @@ export declare type BodyPayload =
| PayloadType[]
| null;
-// Helper types declared outside the interface
-export type FallbackValue = [T] extends [never] ? U : D;
+type EndpointDefaults = Endpoint;
-export type FinalResponse = FallbackValue<
- Response,
- ResponseData
->;
+/**
+ * Represents an API endpoint definition with optional type parameters for various request and response components.
+ *
+ * @template T - An object that can specify the following optional properties:
+ * @property response - The expected response type returned by the endpoint (default: `DefaultResponse`).
+ * @property body - The type of the request body accepted by the endpoint (default: `BodyPayload`).
+ * @property params - The type of the query parameters accepted by the endpoint (default: `QueryParams`).
+ * @property urlPathParams - The type of the path parameters accepted by the endpoint (default: `UrlPathParams`).
+ *
+ * @example
+ * interface EndpointTypes {
+ * getUser: Endpoint<{ response: UserResponse }>;
+ * getPosts: Endpoint<{
+ * response: PostsResponse;
+ * params: PostsQueryParams;
+ * urlPathParams: PostsUrlPathParams;
+ * body: PostsRequestBody;
+ * }>;
+ * }
+ */
+export type Endpoint =
+ EndpointFunction;
-export type FinalParams = [
- ParamsType,
-] extends [never]
- ? DefaultParams
- : [Response] extends [never]
- ? DefaultParams
- : ParamsType | EmptyObject;
+// Helper to support 4 generics
+export type EndpointReq<
+ ResponseData extends DefaultResponse | undefined = DefaultResponse,
+ RequestBody extends DefaultPayload | undefined = DefaultPayload,
+ QueryParams extends DefaultParams | undefined = DefaultParams,
+ UrlPathParams extends DefaultUrlParams | undefined = DefaultUrlParams,
+> = Endpoint>;
+
+type MergeEndpointShape<
+ O extends Partial,
+ T extends DefaultRequestType,
+> = {
+ response: O extends { response: infer R }
+ ? R
+ : T extends { response: infer R }
+ ? R
+ : DefaultResponse;
+ body: O extends { body: infer B }
+ ? B
+ : T extends { body: infer B }
+ ? B
+ : BodyPayload;
+ params: O extends { params: infer P }
+ ? P
+ : T extends { params: infer P }
+ ? P
+ : QueryParams;
+ urlPathParams: O extends { urlPathParams: infer U }
+ ? U
+ : T extends { urlPathParams: infer U }
+ ? U
+ : UrlPathParams;
+};
interface EndpointFunction<
- ResponseData,
- QueryParams_,
- PathParams,
- RequestBody_,
+ T extends Partial = DefaultRequestType,
> {
- (
- requestConfig?: ExtendedRequestConfig<
- FallbackValue,
- FinalParams,
- FinalParams,
- FallbackValue
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ = {}>(
+ requestConfig?: RequestConfig<
+ MergeEndpointShape['response'],
+ MergeEndpointShape['params'],
+ MergeEndpointShape['urlPathParams'],
+ MergeEndpointShape['body']
>,
- ): Promise>>;
+ ): Promise<
+ FetchResponse<
+ MergeEndpointShape['response'],
+ MergeEndpointShape['body'],
+ MergeEndpointShape['params'],
+ MergeEndpointShape['urlPathParams']
+ >
+ >;
}
-export interface RequestEndpointFunction {
- <
- ResponseData = never,
- QueryParams_ = never,
- UrlParams = never,
- RequestBody = never,
- >(
- endpointName: keyof EndpointsMethods | string,
+export interface RequestEndpointFunction {
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ = {}>(
+ endpointNameOrUrl: keyof EndpointTypes | string,
requestConfig?: RequestConfig<
- FinalResponse,
- FinalParams,
- FinalParams,
- FallbackValue
+ MergeEndpointShape['response'],
+ MergeEndpointShape['params'],
+ MergeEndpointShape['urlPathParams'],
+ MergeEndpointShape['body']
>,
- ): Promise>>;
+ ): Promise<
+ FetchResponse<
+ MergeEndpointShape['response'],
+ MergeEndpointShape['body'],
+ MergeEndpointShape['params'],
+ MergeEndpointShape['urlPathParams']
+ >
+ >;
}
-/**
- * Represents an API endpoint handler with support for customizable query parameters, URL path parameters,
- * and request configuration.
- *
- * The overloads allow customization of the returned data type (`ReturnedData`), query parameters (`T`),
- * and URL path parameters (`T2`).
- *
- * @template ResponseData - The type of the response data (default: `DefaultResponse`).
- * @template QueryParams - The type of the query parameters (default: `QueryParams`).
- * @template PathParams - The type of the URL path parameters (default: `UrlPathParams`).
- * @template RequestBody - The type of the Requesty Body (default: `BodyPayload`).
- *
- * @example
- * interface EndpointsMethods {
- * getUser: Endpoint;
- * getPosts: Endpoint;
- * }
- */
-export declare type Endpoint<
- ResponseData = DefaultResponse,
- QueryParams_ = QueryParams,
- PathParams = UrlPathParams,
- RequestBody = BodyPayload,
-> = EndpointFunction;
-
-// Setting 'unknown here lets us infer typings for non-predefined endpoints with dynamically set generic response data
-type EndpointDefaults = Endpoint;
+type MergeWithEndpointDef<
+ EndpointTypes,
+ K extends keyof EndpointTypes,
+ O extends Partial,
+> = MergeEndpointShape<
+ O,
+ EndpointTypes[K] extends Endpoint ? S : DefaultRequestType
+>;
-type AFunction = (...args: any[]) => any;
+type EndpointMethod = <
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ O extends Partial = {},
+>(
+ requestConfig?: RequestConfig<
+ MergeWithEndpointDef['response'],
+ MergeWithEndpointDef['params'],
+ MergeWithEndpointDef['urlPathParams'],
+ MergeWithEndpointDef['body']
+ >,
+) => Promise<
+ FetchResponse<
+ MergeWithEndpointDef['response'],
+ MergeWithEndpointDef