diff --git a/.fern/metadata.json b/.fern/metadata.json
deleted file mode 100644
index c557186..0000000
--- a/.fern/metadata.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "cliVersion": "0.113.1",
- "generatorName": "fernapi/fern-typescript-sdk",
- "generatorVersion": "3.28.6"
-}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5834b38..a27fb10 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,40 +3,40 @@ name: ci
on: [push]
jobs:
- compile:
- runs-on: ubuntu-latest
+ compile:
+ runs-on: ubuntu-latest
- steps:
- - name: Checkout repo
- uses: actions/checkout@v4
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
- - name: Set up node
- uses: actions/setup-node@v4
+ - name: Set up node
+ uses: actions/setup-node@v3
- - name: Install pnpm
- uses: pnpm/action-setup@v4
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
- - name: Install dependencies
- run: pnpm install
+ - name: Install dependencies
+ run: pnpm install
- - name: Compile
- run: pnpm build
+ - name: Compile
+ run: pnpm build
- test:
- runs-on: ubuntu-latest
+ test:
+ runs-on: ubuntu-latest
- steps:
- - name: Checkout repo
- uses: actions/checkout@v4
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
- - name: Set up node
- uses: actions/setup-node@v4
-
- - name: Install pnpm
- uses: pnpm/action-setup@v4
+ - name: Set up node
+ uses: actions/setup-node@v3
- - name: Install dependencies
- run: pnpm install
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
- - name: Test
- run: pnpm test
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Test
+ run: pnpm test
diff --git a/.npmignore b/.npmignore
index c0c40ac..383dd36 100644
--- a/.npmignore
+++ b/.npmignore
@@ -5,7 +5,6 @@ tests
.github
.fernignore
.prettierrc.yml
-biome.json
tsconfig.json
yarn.lock
pnpm-lock.yaml
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..e4691be
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,9 @@
+dist
+*.tsbuildinfo
+_tmp_*
+*.tmp
+.tmp/
+*.log
+.DS_Store
+Thumbs.db
+
\ No newline at end of file
diff --git a/.prettierrc.yml b/.prettierrc.yml
new file mode 100644
index 0000000..0c06786
--- /dev/null
+++ b/.prettierrc.yml
@@ -0,0 +1,2 @@
+tabWidth: 4
+printWidth: 120
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index fe5bc2f..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,133 +0,0 @@
-# Contributing
-
-Thanks for your interest in contributing to this SDK! This document provides guidelines for contributing to the project.
-
-## Getting Started
-
-### Prerequisites
-
-- Node.js 20 or higher
-- pnpm package manager
-
-### Installation
-
-Install the project dependencies:
-
-```bash
-pnpm install
-```
-
-### Building
-
-Build the project:
-
-```bash
-pnpm build
-```
-
-### Testing
-
-Run the test suite:
-
-```bash
-pnpm test
-```
-
-Run specific test types:
-- `pnpm test:unit` - Run unit tests
-- `pnpm test:wire` - Run wire/integration tests
-
-### Linting and Formatting
-
-Check code style:
-
-```bash
-pnpm run lint
-pnpm run format:check
-```
-
-Fix code style issues:
-
-```bash
-pnpm run lint:fix
-pnpm run format:fix
-```
-
-Or use the combined check command:
-
-```bash
-pnpm run check:fix
-```
-
-## About Generated Code
-
-**Important**: Most files in this SDK are automatically generated by [Fern](https://buildwithfern.com) from the API definition. Direct modifications to generated files will be overwritten the next time the SDK is generated.
-
-### Generated Files
-
-The following directories contain generated code:
-- `src/api/` - API client classes and types
-- `src/serialization/` - Serialization/deserialization logic
-- Most TypeScript files in `src/`
-
-### How to Customize
-
-If you need to customize the SDK, you have two options:
-
-#### Option 1: Use `.fernignore`
-
-For custom code that should persist across SDK regenerations:
-
-1. Create a `.fernignore` file in the project root
-2. Add file patterns for files you want to preserve (similar to `.gitignore` syntax)
-3. Add your custom code to those files
-
-Files listed in `.fernignore` will not be overwritten when the SDK is regenerated.
-
-For more information, see the [Fern documentation on custom code](https://buildwithfern.com/learn/sdks/overview/custom-code).
-
-#### Option 2: Contribute to the Generator
-
-If you want to change how code is generated for all users of this SDK:
-
-1. The TypeScript SDK generator lives in the [Fern repository](https://github.com/fern-api/fern)
-2. Generator code is located at `generators/typescript/sdk/`
-3. Follow the [Fern contributing guidelines](https://github.com/fern-api/fern/blob/main/CONTRIBUTING.md)
-4. Submit a pull request with your changes to the generator
-
-This approach is best for:
-- Bug fixes in generated code
-- New features that would benefit all users
-- Improvements to code generation patterns
-
-## Making Changes
-
-### Workflow
-
-1. Create a new branch for your changes
-2. Make your modifications
-3. Run tests to ensure nothing breaks: `pnpm test`
-4. Run linting and formatting: `pnpm run check:fix`
-5. Build the project: `pnpm build`
-6. Commit your changes with a clear commit message
-7. Push your branch and create a pull request
-
-### Commit Messages
-
-Write clear, descriptive commit messages that explain what changed and why.
-
-### Code Style
-
-This project uses automated code formatting and linting. Run `pnpm run check:fix` before committing to ensure your code meets the project's style guidelines.
-
-## Questions or Issues?
-
-If you have questions or run into issues:
-
-1. Check the [Fern documentation](https://buildwithfern.com)
-2. Search existing [GitHub issues](https://github.com/fern-api/fern/issues)
-3. Open a new issue if your question hasn't been addressed
-
-## License
-
-By contributing to this project, you agree that your contributions will be licensed under the same license as the project.
diff --git a/README.md b/README.md
index 35b0a51..b1870be 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,26 @@
The FernAutopilotTest TypeScript library provides convenient access to the FernAutopilotTest APIs from TypeScript.
+## Table of Contents
+
+- [Installation](#installation)
+- [Reference](#reference)
+- [Usage](#usage)
+- [Exception Handling](#exception-handling)
+- [Advanced](#advanced)
+ - [Additional Headers](#additional-headers)
+ - [Additional Query String Parameters](#additional-query-string-parameters)
+ - [Retries](#retries)
+ - [Timeouts](#timeouts)
+ - [Aborting Requests](#aborting-requests)
+ - [Access Raw Response Data](#access-raw-response-data)
+ - [Runtime Compatibility](#runtime-compatibility)
+- [Contributing](#contributing)
+
## Installation
```sh
-npm i -s
+npm i -s
```
## Reference
@@ -25,7 +41,9 @@ import { FernAutopilotTestApiClient } from "";
const client = new FernAutopilotTestApiClient({ environment: "YOUR_BASE_URL" });
await client.imdb.createMovie({
title: "title",
- rating: 1.1
+ rating: 1.1,
+ metadata: "metadata",
+ more_metadata: "more_metadata",
});
```
@@ -129,76 +147,10 @@ console.log(data);
console.log(rawResponse.headers['X-My-Header']);
```
-### Logging
-
-The SDK supports logging. You can configure the logger by passing in a `logging` object to the client options.
-
-```typescript
-import { FernAutopilotTestApiClient, logging } from "FernAutopilotTestApi";
-
-const client = new FernAutopilotTestApiClient({
- ...
- logging: {
- level: logging.LogLevel.Debug, // defaults to logging.LogLevel.Info
- logger: new logging.ConsoleLogger(), // defaults to ConsoleLogger
- silent: false, // defaults to true, set to false to enable logging
- }
-});
-```
-The `logging` object can have the following properties:
-- `level`: The log level to use. Defaults to `logging.LogLevel.Info`.
-- `logger`: The logger to use. Defaults to a `logging.ConsoleLogger`.
-- `silent`: Whether to silence the logger. Defaults to `true`.
-
-The `level` property can be one of the following values:
-- `logging.LogLevel.Debug`
-- `logging.LogLevel.Info`
-- `logging.LogLevel.Warn`
-- `logging.LogLevel.Error`
-
-To provide a custom logger, you can pass in an object that implements the `logging.ILogger` interface.
-
-
-Custom logger examples
-
-Here's an example using the popular `winston` logging library.
-```ts
-import winston from 'winston';
-
-const winstonLogger = winston.createLogger({...});
-
-const logger: logging.ILogger = {
- debug: (msg, ...args) => winstonLogger.debug(msg, ...args),
- info: (msg, ...args) => winstonLogger.info(msg, ...args),
- warn: (msg, ...args) => winstonLogger.warn(msg, ...args),
- error: (msg, ...args) => winstonLogger.error(msg, ...args),
-};
-```
-
-Here's an example using the popular `pino` logging library.
-
-```ts
-import pino from 'pino';
-
-const pinoLogger = pino({...});
-
-const logger: logging.ILogger = {
- debug: (msg, ...args) => pinoLogger.debug(args, msg),
- info: (msg, ...args) => pinoLogger.info(args, msg),
- warn: (msg, ...args) => pinoLogger.warn(args, msg),
- error: (msg, ...args) => pinoLogger.error(args, msg),
-};
-```
-
-
-
### Runtime Compatibility
-
The SDK works in the following runtimes:
-
-
- Node.js 18+
- Vercel
- Cloudflare Workers
@@ -228,4 +180,4 @@ otherwise they would be overwritten upon the next generated release. Feel free t
a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
an issue first to discuss with us!
-On the other hand, contributions to the README are always very welcome!
\ No newline at end of file
+On the other hand, contributions to the README are always very welcome!
diff --git a/biome.json b/biome.json
deleted file mode 100644
index a777468..0000000
--- a/biome.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
- "$schema": "https://biomejs.dev/schemas/2.3.1/schema.json",
- "root": true,
- "vcs": {
- "enabled": false
- },
- "files": {
- "ignoreUnknown": true,
- "includes": [
- "**",
- "!!dist",
- "!!**/dist",
- "!!lib",
- "!!**/lib",
- "!!_tmp_*",
- "!!**/_tmp_*",
- "!!*.tmp",
- "!!**/*.tmp",
- "!!.tmp/",
- "!!**/.tmp/",
- "!!*.log",
- "!!**/*.log",
- "!!**/.DS_Store",
- "!!**/Thumbs.db"
- ]
- },
- "formatter": {
- "enabled": true,
- "indentStyle": "space",
- "indentWidth": 4,
- "lineWidth": 120
- },
- "javascript": {
- "formatter": {
- "quoteStyle": "double"
- }
- },
- "assist": {
- "enabled": true,
- "actions": {
- "source": {
- "organizeImports": "on"
- }
- }
- },
- "linter": {
- "rules": {
- "style": {
- "useNodejsImportProtocol": "off"
- },
- "suspicious": {
- "noAssignInExpressions": "warn",
- "noUselessEscapeInString": {
- "level": "warn",
- "fix": "none",
- "options": {}
- },
- "noThenProperty": "warn",
- "useIterableCallbackReturn": "warn",
- "noShadowRestrictedNames": "warn",
- "noTsIgnore": {
- "level": "warn",
- "fix": "none",
- "options": {}
- },
- "noConfusingVoidType": {
- "level": "warn",
- "fix": "none",
- "options": {}
- }
- }
- }
- }
-}
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 0000000..3d497b3
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,4 @@
+## 2.0.1 - 2025-11-17
+* SDK regeneration
+* Unable to analyze changes with AI, incrementing PATCH version.
+
diff --git a/package.json b/package.json
index 5426420..d959e2b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "",
- "version": "2.0.0",
+ "version": "2.0.1",
"private": false,
"repository": "github:fern-demo/autopilot-typescript-sdk",
"type": "commonjs",
@@ -29,12 +29,7 @@
"LICENSE"
],
"scripts": {
- "format": "biome format --write --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
- "format:check": "biome format --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
- "lint": "biome lint --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
- "lint:fix": "biome lint --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
- "check": "biome check --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
- "check:fix": "biome check --fix --unsafe --skip-parse-errors --no-errors-on-unmatched --max-diagnostics=none",
+ "format": "prettier . --write --ignore-unknown",
"build": "pnpm build:cjs && pnpm build:esm",
"build:cjs": "tsc --project ./tsconfig.cjs.json",
"build:esm": "tsc --project ./tsconfig.esm.json && node scripts/rename-to-esm-files.js dist/esm",
@@ -42,15 +37,14 @@
"test:unit": "vitest --project unit",
"test:wire": "vitest --project wire"
},
- "dependencies": {},
"devDependencies": {
"webpack": "^5.97.1",
"ts-loader": "^9.5.1",
"vitest": "^3.2.4",
"msw": "2.11.2",
"@types/node": "^18.19.70",
- "typescript": "~5.7.2",
- "@biomejs/biome": "2.3.1"
+ "prettier": "^3.4.2",
+ "typescript": "~5.7.2"
},
"browser": {
"fs": false,
@@ -58,7 +52,7 @@
"path": false,
"stream": false
},
- "packageManager": "pnpm@10.20.0",
+ "packageManager": "pnpm@10.14.0",
"engines": {
"node": ">=18.0.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fdbfec3..3ce8d6c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,15 +8,15 @@ importers:
.:
devDependencies:
- '@biomejs/biome':
- specifier: 2.3.1
- version: 2.3.1
'@types/node':
specifier: ^18.19.70
- version: 18.19.130
+ version: 18.19.129
msw:
specifier: 2.11.2
- version: 2.11.2(@types/node@18.19.130)(typescript@5.7.3)
+ version: 2.11.2(@types/node@18.19.129)(typescript@5.7.3)
+ prettier:
+ specifier: ^3.4.2
+ version: 3.6.2
ts-loader:
specifier: ^9.5.1
version: 9.5.4(typescript@5.7.3)(webpack@5.102.1)
@@ -25,234 +25,181 @@ importers:
version: 5.7.3
vitest:
specifier: ^3.2.4
- version: 3.2.4(@types/node@18.19.130)(msw@2.11.2(@types/node@18.19.130)(typescript@5.7.3))(terser@5.44.1)
+ version: 3.2.4(@types/node@18.19.129)(msw@2.11.2(@types/node@18.19.129)(typescript@5.7.3))(terser@5.44.0)
webpack:
specifier: ^5.97.1
version: 5.102.1
packages:
- '@biomejs/biome@2.3.1':
- resolution: {integrity: sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==}
- engines: {node: '>=14.21.3'}
- hasBin: true
-
- '@biomejs/cli-darwin-arm64@2.3.1':
- resolution: {integrity: sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [darwin]
-
- '@biomejs/cli-darwin-x64@2.3.1':
- resolution: {integrity: sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [darwin]
-
- '@biomejs/cli-linux-arm64-musl@2.3.1':
- resolution: {integrity: sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [linux]
-
- '@biomejs/cli-linux-arm64@2.3.1':
- resolution: {integrity: sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [linux]
-
- '@biomejs/cli-linux-x64-musl@2.3.1':
- resolution: {integrity: sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [linux]
-
- '@biomejs/cli-linux-x64@2.3.1':
- resolution: {integrity: sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [linux]
-
- '@biomejs/cli-win32-arm64@2.3.1':
- resolution: {integrity: sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==}
- engines: {node: '>=14.21.3'}
- cpu: [arm64]
- os: [win32]
-
- '@biomejs/cli-win32-x64@2.3.1':
- resolution: {integrity: sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==}
- engines: {node: '>=14.21.3'}
- cpu: [x64]
- os: [win32]
-
'@bundled-es-modules/cookie@2.0.1':
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
'@bundled-es-modules/statuses@1.0.1':
resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
- '@esbuild/aix-ppc64@0.25.12':
- resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ '@esbuild/aix-ppc64@0.25.10':
+ resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.25.12':
- resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ '@esbuild/android-arm64@0.25.10':
+ resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.25.12':
- resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ '@esbuild/android-arm@0.25.10':
+ resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.25.12':
- resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ '@esbuild/android-x64@0.25.10':
+ resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.25.12':
- resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ '@esbuild/darwin-arm64@0.25.10':
+ resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.25.12':
- resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ '@esbuild/darwin-x64@0.25.10':
+ resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.25.12':
- resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ '@esbuild/freebsd-arm64@0.25.10':
+ resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.25.12':
- resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ '@esbuild/freebsd-x64@0.25.10':
+ resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.25.12':
- resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ '@esbuild/linux-arm64@0.25.10':
+ resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.25.12':
- resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ '@esbuild/linux-arm@0.25.10':
+ resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.25.12':
- resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ '@esbuild/linux-ia32@0.25.10':
+ resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.25.12':
- resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ '@esbuild/linux-loong64@0.25.10':
+ resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.25.12':
- resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ '@esbuild/linux-mips64el@0.25.10':
+ resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.25.12':
- resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ '@esbuild/linux-ppc64@0.25.10':
+ resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.25.12':
- resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ '@esbuild/linux-riscv64@0.25.10':
+ resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.25.12':
- resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ '@esbuild/linux-s390x@0.25.10':
+ resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.25.12':
- resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ '@esbuild/linux-x64@0.25.10':
+ resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/netbsd-arm64@0.25.12':
- resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ '@esbuild/netbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.25.12':
- resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ '@esbuild/netbsd-x64@0.25.10':
+ resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.25.12':
- resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ '@esbuild/openbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.25.12':
- resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ '@esbuild/openbsd-x64@0.25.10':
+ resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/openharmony-arm64@0.25.12':
- resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ '@esbuild/openharmony-arm64@0.25.10':
+ resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
- '@esbuild/sunos-x64@0.25.12':
- resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ '@esbuild/sunos-x64@0.25.10':
+ resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.25.12':
- resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ '@esbuild/win32-arm64@0.25.10':
+ resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.25.12':
- resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ '@esbuild/win32-ia32@0.25.10':
+ resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.25.12':
- resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ '@esbuild/win32-x64@0.25.10':
+ resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
- '@inquirer/ansi@1.0.1':
- resolution: {integrity: sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==}
+ '@inquirer/ansi@1.0.0':
+ resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==}
engines: {node: '>=18'}
- '@inquirer/confirm@5.1.19':
- resolution: {integrity: sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==}
+ '@inquirer/confirm@5.1.18':
+ resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
@@ -260,8 +207,8 @@ packages:
'@types/node':
optional: true
- '@inquirer/core@10.3.0':
- resolution: {integrity: sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==}
+ '@inquirer/core@10.2.2':
+ resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
@@ -269,12 +216,12 @@ packages:
'@types/node':
optional: true
- '@inquirer/figures@1.0.14':
- resolution: {integrity: sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==}
+ '@inquirer/figures@1.0.13':
+ resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==}
engines: {node: '>=18'}
- '@inquirer/type@3.0.9':
- resolution: {integrity: sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==}
+ '@inquirer/type@3.0.8':
+ resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
@@ -298,8 +245,8 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
- '@mswjs/interceptors@0.39.8':
- resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==}
+ '@mswjs/interceptors@0.39.7':
+ resolution: {integrity: sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==}
engines: {node: '>=18'}
'@open-draft/deferred-promise@2.2.0':
@@ -311,118 +258,118 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
- '@rollup/rollup-android-arm-eabi@4.53.1':
- resolution: {integrity: sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==}
+ '@rollup/rollup-android-arm-eabi@4.52.4':
+ resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.53.1':
- resolution: {integrity: sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==}
+ '@rollup/rollup-android-arm64@4.52.4':
+ resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.53.1':
- resolution: {integrity: sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==}
+ '@rollup/rollup-darwin-arm64@4.52.4':
+ resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.53.1':
- resolution: {integrity: sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==}
+ '@rollup/rollup-darwin-x64@4.52.4':
+ resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.53.1':
- resolution: {integrity: sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==}
+ '@rollup/rollup-freebsd-arm64@4.52.4':
+ resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.53.1':
- resolution: {integrity: sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==}
+ '@rollup/rollup-freebsd-x64@4.52.4':
+ resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.53.1':
- resolution: {integrity: sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.4':
+ resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.53.1':
- resolution: {integrity: sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==}
+ '@rollup/rollup-linux-arm-musleabihf@4.52.4':
+ resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.53.1':
- resolution: {integrity: sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==}
+ '@rollup/rollup-linux-arm64-gnu@4.52.4':
+ resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.53.1':
- resolution: {integrity: sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==}
+ '@rollup/rollup-linux-arm64-musl@4.52.4':
+ resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loong64-gnu@4.53.1':
- resolution: {integrity: sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==}
+ '@rollup/rollup-linux-loong64-gnu@4.52.4':
+ resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-ppc64-gnu@4.53.1':
- resolution: {integrity: sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==}
+ '@rollup/rollup-linux-ppc64-gnu@4.52.4':
+ resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.53.1':
- resolution: {integrity: sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==}
+ '@rollup/rollup-linux-riscv64-gnu@4.52.4':
+ resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-musl@4.53.1':
- resolution: {integrity: sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==}
+ '@rollup/rollup-linux-riscv64-musl@4.52.4':
+ resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.53.1':
- resolution: {integrity: sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==}
+ '@rollup/rollup-linux-s390x-gnu@4.52.4':
+ resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.53.1':
- resolution: {integrity: sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==}
+ '@rollup/rollup-linux-x64-gnu@4.52.4':
+ resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.53.1':
- resolution: {integrity: sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==}
+ '@rollup/rollup-linux-x64-musl@4.52.4':
+ resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-openharmony-arm64@4.53.1':
- resolution: {integrity: sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==}
+ '@rollup/rollup-openharmony-arm64@4.52.4':
+ resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==}
cpu: [arm64]
os: [openharmony]
- '@rollup/rollup-win32-arm64-msvc@4.53.1':
- resolution: {integrity: sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==}
+ '@rollup/rollup-win32-arm64-msvc@4.52.4':
+ resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.53.1':
- resolution: {integrity: sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==}
+ '@rollup/rollup-win32-ia32-msvc@4.52.4':
+ resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-gnu@4.53.1':
- resolution: {integrity: sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==}
+ '@rollup/rollup-win32-x64-gnu@4.52.4':
+ resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==}
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.53.1':
- resolution: {integrity: sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==}
+ '@rollup/rollup-win32-x64-msvc@4.52.4':
+ resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==}
cpu: [x64]
os: [win32]
- '@types/chai@5.2.3':
- resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+ '@types/chai@5.2.2':
+ resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@@ -442,8 +389,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/node@18.19.130':
- resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
+ '@types/node@18.19.129':
+ resolution: {integrity: sha512-hrmi5jWt2w60ayox3iIXwpMEnfUvOLJCRtrOPbHtH15nTjvO7uhnelvrdAs0dO0/zl5DZ3ZbahiaXEVb54ca/A==}
'@types/statuses@2.0.6':
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
@@ -567,16 +514,16 @@ packages:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
- baseline-browser-mapping@2.8.25:
- resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==}
+ baseline-browser-mapping@2.8.13:
+ resolution: {integrity: sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==}
hasBin: true
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- browserslist@4.27.0:
- resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
+ browserslist@4.26.3:
+ resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -587,8 +534,8 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
- caniuse-lite@1.0.30001754:
- resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==}
+ caniuse-lite@1.0.30001748:
+ resolution: {integrity: sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==}
chai@5.3.3:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
@@ -641,8 +588,8 @@ packages:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
- electron-to-chromium@1.5.248:
- resolution: {integrity: sha512-zsur2yunphlyAO4gIubdJEXCK6KOVvtpiuDfCIqbM9FjcnMYiyn0ICa3hWfPr0nc41zcLWobgy1iL7VvoOyA2Q==}
+ electron-to-chromium@1.5.232:
+ resolution: {integrity: sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -654,8 +601,8 @@ packages:
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
- esbuild@0.25.12:
- resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ esbuild@0.25.10:
+ resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
engines: {node: '>=18'}
hasBin: true
@@ -724,8 +671,8 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- graphql@16.12.0:
- resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==}
+ graphql@16.11.0:
+ resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
has-flag@4.0.0:
@@ -759,15 +706,15 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
- loader-runner@4.3.1:
- resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
+ loader-runner@4.3.0:
+ resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
engines: {node: '>=6.11.5'}
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
- magic-string@0.30.21:
- resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ magic-string@0.30.19:
+ resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -809,8 +756,8 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
- node-releases@2.0.27:
- resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+ node-releases@2.0.23:
+ resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==}
outvariant@1.4.3:
resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
@@ -840,6 +787,11 @@ packages:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
+ prettier@3.6.2:
+ resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@@ -854,8 +806,8 @@ packages:
rettime@0.7.0:
resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==}
- rollup@4.53.1:
- resolution: {integrity: sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==}
+ rollup@4.52.4:
+ resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -903,8 +855,8 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
- std-env@3.10.0:
- resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+ std-env@3.9.0:
+ resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
strict-event-emitter@0.5.1:
resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
@@ -948,8 +900,8 @@ packages:
uglify-js:
optional: true
- terser@5.44.1:
- resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
+ terser@5.44.0:
+ resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}
engines: {node: '>=10'}
hasBin: true
@@ -975,11 +927,11 @@ packages:
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
engines: {node: '>=14.0.0'}
- tldts-core@7.0.17:
- resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==}
+ tldts-core@7.0.16:
+ resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==}
- tldts@7.0.17:
- resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==}
+ tldts@7.0.16:
+ resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==}
hasBin: true
to-regex-range@5.0.1:
@@ -1009,8 +961,8 @@ packages:
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
- update-browserslist-db@1.1.4:
- resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
@@ -1020,8 +972,8 @@ packages:
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
- vite@7.2.2:
- resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==}
+ vite@7.1.9:
+ resolution: {integrity: sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -1137,41 +1089,6 @@ packages:
snapshots:
- '@biomejs/biome@2.3.1':
- optionalDependencies:
- '@biomejs/cli-darwin-arm64': 2.3.1
- '@biomejs/cli-darwin-x64': 2.3.1
- '@biomejs/cli-linux-arm64': 2.3.1
- '@biomejs/cli-linux-arm64-musl': 2.3.1
- '@biomejs/cli-linux-x64': 2.3.1
- '@biomejs/cli-linux-x64-musl': 2.3.1
- '@biomejs/cli-win32-arm64': 2.3.1
- '@biomejs/cli-win32-x64': 2.3.1
-
- '@biomejs/cli-darwin-arm64@2.3.1':
- optional: true
-
- '@biomejs/cli-darwin-x64@2.3.1':
- optional: true
-
- '@biomejs/cli-linux-arm64-musl@2.3.1':
- optional: true
-
- '@biomejs/cli-linux-arm64@2.3.1':
- optional: true
-
- '@biomejs/cli-linux-x64-musl@2.3.1':
- optional: true
-
- '@biomejs/cli-linux-x64@2.3.1':
- optional: true
-
- '@biomejs/cli-win32-arm64@2.3.1':
- optional: true
-
- '@biomejs/cli-win32-x64@2.3.1':
- optional: true
-
'@bundled-es-modules/cookie@2.0.1':
dependencies:
cookie: 0.7.2
@@ -1180,111 +1097,111 @@ snapshots:
dependencies:
statuses: 2.0.2
- '@esbuild/aix-ppc64@0.25.12':
+ '@esbuild/aix-ppc64@0.25.10':
optional: true
- '@esbuild/android-arm64@0.25.12':
+ '@esbuild/android-arm64@0.25.10':
optional: true
- '@esbuild/android-arm@0.25.12':
+ '@esbuild/android-arm@0.25.10':
optional: true
- '@esbuild/android-x64@0.25.12':
+ '@esbuild/android-x64@0.25.10':
optional: true
- '@esbuild/darwin-arm64@0.25.12':
+ '@esbuild/darwin-arm64@0.25.10':
optional: true
- '@esbuild/darwin-x64@0.25.12':
+ '@esbuild/darwin-x64@0.25.10':
optional: true
- '@esbuild/freebsd-arm64@0.25.12':
+ '@esbuild/freebsd-arm64@0.25.10':
optional: true
- '@esbuild/freebsd-x64@0.25.12':
+ '@esbuild/freebsd-x64@0.25.10':
optional: true
- '@esbuild/linux-arm64@0.25.12':
+ '@esbuild/linux-arm64@0.25.10':
optional: true
- '@esbuild/linux-arm@0.25.12':
+ '@esbuild/linux-arm@0.25.10':
optional: true
- '@esbuild/linux-ia32@0.25.12':
+ '@esbuild/linux-ia32@0.25.10':
optional: true
- '@esbuild/linux-loong64@0.25.12':
+ '@esbuild/linux-loong64@0.25.10':
optional: true
- '@esbuild/linux-mips64el@0.25.12':
+ '@esbuild/linux-mips64el@0.25.10':
optional: true
- '@esbuild/linux-ppc64@0.25.12':
+ '@esbuild/linux-ppc64@0.25.10':
optional: true
- '@esbuild/linux-riscv64@0.25.12':
+ '@esbuild/linux-riscv64@0.25.10':
optional: true
- '@esbuild/linux-s390x@0.25.12':
+ '@esbuild/linux-s390x@0.25.10':
optional: true
- '@esbuild/linux-x64@0.25.12':
+ '@esbuild/linux-x64@0.25.10':
optional: true
- '@esbuild/netbsd-arm64@0.25.12':
+ '@esbuild/netbsd-arm64@0.25.10':
optional: true
- '@esbuild/netbsd-x64@0.25.12':
+ '@esbuild/netbsd-x64@0.25.10':
optional: true
- '@esbuild/openbsd-arm64@0.25.12':
+ '@esbuild/openbsd-arm64@0.25.10':
optional: true
- '@esbuild/openbsd-x64@0.25.12':
+ '@esbuild/openbsd-x64@0.25.10':
optional: true
- '@esbuild/openharmony-arm64@0.25.12':
+ '@esbuild/openharmony-arm64@0.25.10':
optional: true
- '@esbuild/sunos-x64@0.25.12':
+ '@esbuild/sunos-x64@0.25.10':
optional: true
- '@esbuild/win32-arm64@0.25.12':
+ '@esbuild/win32-arm64@0.25.10':
optional: true
- '@esbuild/win32-ia32@0.25.12':
+ '@esbuild/win32-ia32@0.25.10':
optional: true
- '@esbuild/win32-x64@0.25.12':
+ '@esbuild/win32-x64@0.25.10':
optional: true
- '@inquirer/ansi@1.0.1': {}
+ '@inquirer/ansi@1.0.0': {}
- '@inquirer/confirm@5.1.19(@types/node@18.19.130)':
+ '@inquirer/confirm@5.1.18(@types/node@18.19.129)':
dependencies:
- '@inquirer/core': 10.3.0(@types/node@18.19.130)
- '@inquirer/type': 3.0.9(@types/node@18.19.130)
+ '@inquirer/core': 10.2.2(@types/node@18.19.129)
+ '@inquirer/type': 3.0.8(@types/node@18.19.129)
optionalDependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
- '@inquirer/core@10.3.0(@types/node@18.19.130)':
+ '@inquirer/core@10.2.2(@types/node@18.19.129)':
dependencies:
- '@inquirer/ansi': 1.0.1
- '@inquirer/figures': 1.0.14
- '@inquirer/type': 3.0.9(@types/node@18.19.130)
+ '@inquirer/ansi': 1.0.0
+ '@inquirer/figures': 1.0.13
+ '@inquirer/type': 3.0.8(@types/node@18.19.129)
cli-width: 4.1.0
mute-stream: 2.0.0
signal-exit: 4.1.0
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
- '@inquirer/figures@1.0.14': {}
+ '@inquirer/figures@1.0.13': {}
- '@inquirer/type@3.0.9(@types/node@18.19.130)':
+ '@inquirer/type@3.0.8(@types/node@18.19.129)':
optionalDependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
'@jridgewell/gen-mapping@0.3.13':
dependencies:
@@ -1305,7 +1222,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@mswjs/interceptors@0.39.8':
+ '@mswjs/interceptors@0.39.7':
dependencies:
'@open-draft/deferred-promise': 2.2.0
'@open-draft/logger': 0.3.0
@@ -1323,76 +1240,75 @@ snapshots:
'@open-draft/until@2.1.0': {}
- '@rollup/rollup-android-arm-eabi@4.53.1':
+ '@rollup/rollup-android-arm-eabi@4.52.4':
optional: true
- '@rollup/rollup-android-arm64@4.53.1':
+ '@rollup/rollup-android-arm64@4.52.4':
optional: true
- '@rollup/rollup-darwin-arm64@4.53.1':
+ '@rollup/rollup-darwin-arm64@4.52.4':
optional: true
- '@rollup/rollup-darwin-x64@4.53.1':
+ '@rollup/rollup-darwin-x64@4.52.4':
optional: true
- '@rollup/rollup-freebsd-arm64@4.53.1':
+ '@rollup/rollup-freebsd-arm64@4.52.4':
optional: true
- '@rollup/rollup-freebsd-x64@4.53.1':
+ '@rollup/rollup-freebsd-x64@4.52.4':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.53.1':
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.4':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.53.1':
+ '@rollup/rollup-linux-arm-musleabihf@4.52.4':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.53.1':
+ '@rollup/rollup-linux-arm64-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.53.1':
+ '@rollup/rollup-linux-arm64-musl@4.52.4':
optional: true
- '@rollup/rollup-linux-loong64-gnu@4.53.1':
+ '@rollup/rollup-linux-loong64-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-ppc64-gnu@4.53.1':
+ '@rollup/rollup-linux-ppc64-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.53.1':
+ '@rollup/rollup-linux-riscv64-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.53.1':
+ '@rollup/rollup-linux-riscv64-musl@4.52.4':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.53.1':
+ '@rollup/rollup-linux-s390x-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.53.1':
+ '@rollup/rollup-linux-x64-gnu@4.52.4':
optional: true
- '@rollup/rollup-linux-x64-musl@4.53.1':
+ '@rollup/rollup-linux-x64-musl@4.52.4':
optional: true
- '@rollup/rollup-openharmony-arm64@4.53.1':
+ '@rollup/rollup-openharmony-arm64@4.52.4':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.53.1':
+ '@rollup/rollup-win32-arm64-msvc@4.52.4':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.53.1':
+ '@rollup/rollup-win32-ia32-msvc@4.52.4':
optional: true
- '@rollup/rollup-win32-x64-gnu@4.53.1':
+ '@rollup/rollup-win32-x64-gnu@4.52.4':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.53.1':
+ '@rollup/rollup-win32-x64-msvc@4.52.4':
optional: true
- '@types/chai@5.2.3':
+ '@types/chai@5.2.2':
dependencies:
'@types/deep-eql': 4.0.2
- assertion-error: 2.0.1
'@types/cookie@0.6.0': {}
@@ -1412,7 +1328,7 @@ snapshots:
'@types/json-schema@7.0.15': {}
- '@types/node@18.19.130':
+ '@types/node@18.19.129':
dependencies:
undici-types: 5.26.5
@@ -1420,20 +1336,20 @@ snapshots:
'@vitest/expect@3.2.4':
dependencies:
- '@types/chai': 5.2.3
+ '@types/chai': 5.2.2
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
tinyrainbow: 2.0.0
- '@vitest/mocker@3.2.4(msw@2.11.2(@types/node@18.19.130)(typescript@5.7.3))(vite@7.2.2(@types/node@18.19.130)(terser@5.44.1))':
+ '@vitest/mocker@3.2.4(msw@2.11.2(@types/node@18.19.129)(typescript@5.7.3))(vite@7.1.9(@types/node@18.19.129)(terser@5.44.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
- magic-string: 0.30.21
+ magic-string: 0.30.19
optionalDependencies:
- msw: 2.11.2(@types/node@18.19.130)(typescript@5.7.3)
- vite: 7.2.2(@types/node@18.19.130)(terser@5.44.1)
+ msw: 2.11.2(@types/node@18.19.129)(typescript@5.7.3)
+ vite: 7.1.9(@types/node@18.19.129)(terser@5.44.0)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -1448,7 +1364,7 @@ snapshots:
'@vitest/snapshot@3.2.4':
dependencies:
'@vitest/pretty-format': 3.2.4
- magic-string: 0.30.21
+ magic-string: 0.30.19
pathe: 2.0.3
'@vitest/spy@3.2.4':
@@ -1571,25 +1487,25 @@ snapshots:
assertion-error@2.0.1: {}
- baseline-browser-mapping@2.8.25: {}
+ baseline-browser-mapping@2.8.13: {}
braces@3.0.3:
dependencies:
fill-range: 7.1.1
- browserslist@4.27.0:
+ browserslist@4.26.3:
dependencies:
- baseline-browser-mapping: 2.8.25
- caniuse-lite: 1.0.30001754
- electron-to-chromium: 1.5.248
- node-releases: 2.0.27
- update-browserslist-db: 1.1.4(browserslist@4.27.0)
+ baseline-browser-mapping: 2.8.13
+ caniuse-lite: 1.0.30001748
+ electron-to-chromium: 1.5.232
+ node-releases: 2.0.23
+ update-browserslist-db: 1.1.3(browserslist@4.26.3)
buffer-from@1.1.2: {}
cac@6.7.14: {}
- caniuse-lite@1.0.30001754: {}
+ caniuse-lite@1.0.30001748: {}
chai@5.3.3:
dependencies:
@@ -1632,7 +1548,7 @@ snapshots:
deep-eql@5.0.2: {}
- electron-to-chromium@1.5.248: {}
+ electron-to-chromium@1.5.232: {}
emoji-regex@8.0.0: {}
@@ -1643,34 +1559,34 @@ snapshots:
es-module-lexer@1.7.0: {}
- esbuild@0.25.12:
+ esbuild@0.25.10:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.12
- '@esbuild/android-arm': 0.25.12
- '@esbuild/android-arm64': 0.25.12
- '@esbuild/android-x64': 0.25.12
- '@esbuild/darwin-arm64': 0.25.12
- '@esbuild/darwin-x64': 0.25.12
- '@esbuild/freebsd-arm64': 0.25.12
- '@esbuild/freebsd-x64': 0.25.12
- '@esbuild/linux-arm': 0.25.12
- '@esbuild/linux-arm64': 0.25.12
- '@esbuild/linux-ia32': 0.25.12
- '@esbuild/linux-loong64': 0.25.12
- '@esbuild/linux-mips64el': 0.25.12
- '@esbuild/linux-ppc64': 0.25.12
- '@esbuild/linux-riscv64': 0.25.12
- '@esbuild/linux-s390x': 0.25.12
- '@esbuild/linux-x64': 0.25.12
- '@esbuild/netbsd-arm64': 0.25.12
- '@esbuild/netbsd-x64': 0.25.12
- '@esbuild/openbsd-arm64': 0.25.12
- '@esbuild/openbsd-x64': 0.25.12
- '@esbuild/openharmony-arm64': 0.25.12
- '@esbuild/sunos-x64': 0.25.12
- '@esbuild/win32-arm64': 0.25.12
- '@esbuild/win32-ia32': 0.25.12
- '@esbuild/win32-x64': 0.25.12
+ '@esbuild/aix-ppc64': 0.25.10
+ '@esbuild/android-arm': 0.25.10
+ '@esbuild/android-arm64': 0.25.10
+ '@esbuild/android-x64': 0.25.10
+ '@esbuild/darwin-arm64': 0.25.10
+ '@esbuild/darwin-x64': 0.25.10
+ '@esbuild/freebsd-arm64': 0.25.10
+ '@esbuild/freebsd-x64': 0.25.10
+ '@esbuild/linux-arm': 0.25.10
+ '@esbuild/linux-arm64': 0.25.10
+ '@esbuild/linux-ia32': 0.25.10
+ '@esbuild/linux-loong64': 0.25.10
+ '@esbuild/linux-mips64el': 0.25.10
+ '@esbuild/linux-ppc64': 0.25.10
+ '@esbuild/linux-riscv64': 0.25.10
+ '@esbuild/linux-s390x': 0.25.10
+ '@esbuild/linux-x64': 0.25.10
+ '@esbuild/netbsd-arm64': 0.25.10
+ '@esbuild/netbsd-x64': 0.25.10
+ '@esbuild/openbsd-arm64': 0.25.10
+ '@esbuild/openbsd-x64': 0.25.10
+ '@esbuild/openharmony-arm64': 0.25.10
+ '@esbuild/sunos-x64': 0.25.10
+ '@esbuild/win32-arm64': 0.25.10
+ '@esbuild/win32-ia32': 0.25.10
+ '@esbuild/win32-x64': 0.25.10
escalade@3.2.0: {}
@@ -1716,7 +1632,7 @@ snapshots:
graceful-fs@4.2.11: {}
- graphql@16.12.0: {}
+ graphql@16.11.0: {}
has-flag@4.0.0: {}
@@ -1730,7 +1646,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -1740,11 +1656,11 @@ snapshots:
json-schema-traverse@1.0.0: {}
- loader-runner@4.3.1: {}
+ loader-runner@4.3.0: {}
loupe@3.2.1: {}
- magic-string@0.30.21:
+ magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -1763,17 +1679,17 @@ snapshots:
ms@2.1.3: {}
- msw@2.11.2(@types/node@18.19.130)(typescript@5.7.3):
+ msw@2.11.2(@types/node@18.19.129)(typescript@5.7.3):
dependencies:
'@bundled-es-modules/cookie': 2.0.1
'@bundled-es-modules/statuses': 1.0.1
- '@inquirer/confirm': 5.1.19(@types/node@18.19.130)
- '@mswjs/interceptors': 0.39.8
+ '@inquirer/confirm': 5.1.18(@types/node@18.19.129)
+ '@mswjs/interceptors': 0.39.7
'@open-draft/deferred-promise': 2.2.0
'@open-draft/until': 2.1.0
'@types/cookie': 0.6.0
'@types/statuses': 2.0.6
- graphql: 16.12.0
+ graphql: 16.11.0
headers-polyfill: 4.0.3
is-node-process: 1.2.0
outvariant: 1.4.3
@@ -1795,7 +1711,7 @@ snapshots:
neo-async@2.6.2: {}
- node-releases@2.0.27: {}
+ node-releases@2.0.23: {}
outvariant@1.4.3: {}
@@ -1817,6 +1733,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ prettier@3.6.2: {}
+
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@@ -1827,32 +1745,32 @@ snapshots:
rettime@0.7.0: {}
- rollup@4.53.1:
+ rollup@4.52.4:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.53.1
- '@rollup/rollup-android-arm64': 4.53.1
- '@rollup/rollup-darwin-arm64': 4.53.1
- '@rollup/rollup-darwin-x64': 4.53.1
- '@rollup/rollup-freebsd-arm64': 4.53.1
- '@rollup/rollup-freebsd-x64': 4.53.1
- '@rollup/rollup-linux-arm-gnueabihf': 4.53.1
- '@rollup/rollup-linux-arm-musleabihf': 4.53.1
- '@rollup/rollup-linux-arm64-gnu': 4.53.1
- '@rollup/rollup-linux-arm64-musl': 4.53.1
- '@rollup/rollup-linux-loong64-gnu': 4.53.1
- '@rollup/rollup-linux-ppc64-gnu': 4.53.1
- '@rollup/rollup-linux-riscv64-gnu': 4.53.1
- '@rollup/rollup-linux-riscv64-musl': 4.53.1
- '@rollup/rollup-linux-s390x-gnu': 4.53.1
- '@rollup/rollup-linux-x64-gnu': 4.53.1
- '@rollup/rollup-linux-x64-musl': 4.53.1
- '@rollup/rollup-openharmony-arm64': 4.53.1
- '@rollup/rollup-win32-arm64-msvc': 4.53.1
- '@rollup/rollup-win32-ia32-msvc': 4.53.1
- '@rollup/rollup-win32-x64-gnu': 4.53.1
- '@rollup/rollup-win32-x64-msvc': 4.53.1
+ '@rollup/rollup-android-arm-eabi': 4.52.4
+ '@rollup/rollup-android-arm64': 4.52.4
+ '@rollup/rollup-darwin-arm64': 4.52.4
+ '@rollup/rollup-darwin-x64': 4.52.4
+ '@rollup/rollup-freebsd-arm64': 4.52.4
+ '@rollup/rollup-freebsd-x64': 4.52.4
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.4
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.4
+ '@rollup/rollup-linux-arm64-gnu': 4.52.4
+ '@rollup/rollup-linux-arm64-musl': 4.52.4
+ '@rollup/rollup-linux-loong64-gnu': 4.52.4
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.4
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.4
+ '@rollup/rollup-linux-riscv64-musl': 4.52.4
+ '@rollup/rollup-linux-s390x-gnu': 4.52.4
+ '@rollup/rollup-linux-x64-gnu': 4.52.4
+ '@rollup/rollup-linux-x64-musl': 4.52.4
+ '@rollup/rollup-openharmony-arm64': 4.52.4
+ '@rollup/rollup-win32-arm64-msvc': 4.52.4
+ '@rollup/rollup-win32-ia32-msvc': 4.52.4
+ '@rollup/rollup-win32-x64-gnu': 4.52.4
+ '@rollup/rollup-win32-x64-msvc': 4.52.4
fsevents: 2.3.3
safe-buffer@5.2.1: {}
@@ -1889,7 +1807,7 @@ snapshots:
statuses@2.0.2: {}
- std-env@3.10.0: {}
+ std-env@3.9.0: {}
strict-event-emitter@0.5.1: {}
@@ -1923,10 +1841,10 @@ snapshots:
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
- terser: 5.44.1
+ terser: 5.44.0
webpack: 5.102.1
- terser@5.44.1:
+ terser@5.44.0:
dependencies:
'@jridgewell/source-map': 0.3.11
acorn: 8.15.0
@@ -1948,11 +1866,11 @@ snapshots:
tinyspy@4.0.4: {}
- tldts-core@7.0.17: {}
+ tldts-core@7.0.16: {}
- tldts@7.0.17:
+ tldts@7.0.16:
dependencies:
- tldts-core: 7.0.17
+ tldts-core: 7.0.16
to-regex-range@5.0.1:
dependencies:
@@ -1960,7 +1878,7 @@ snapshots:
tough-cookie@6.0.0:
dependencies:
- tldts: 7.0.17
+ tldts: 7.0.16
ts-loader@9.5.4(typescript@5.7.3)(webpack@5.102.1):
dependencies:
@@ -1978,19 +1896,19 @@ snapshots:
undici-types@5.26.5: {}
- update-browserslist-db@1.1.4(browserslist@4.27.0):
+ update-browserslist-db@1.1.3(browserslist@4.26.3):
dependencies:
- browserslist: 4.27.0
+ browserslist: 4.26.3
escalade: 3.2.0
picocolors: 1.1.1
- vite-node@3.2.4(@types/node@18.19.130)(terser@5.44.1):
+ vite-node@3.2.4(@types/node@18.19.129)(terser@5.44.0):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.2.2(@types/node@18.19.130)(terser@5.44.1)
+ vite: 7.1.9(@types/node@18.19.129)(terser@5.44.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -2005,24 +1923,24 @@ snapshots:
- tsx
- yaml
- vite@7.2.2(@types/node@18.19.130)(terser@5.44.1):
+ vite@7.1.9(@types/node@18.19.129)(terser@5.44.0):
dependencies:
- esbuild: 0.25.12
+ esbuild: 0.25.10
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.53.1
+ rollup: 4.52.4
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
fsevents: 2.3.3
- terser: 5.44.1
+ terser: 5.44.0
- vitest@3.2.4(@types/node@18.19.130)(msw@2.11.2(@types/node@18.19.130)(typescript@5.7.3))(terser@5.44.1):
+ vitest@3.2.4(@types/node@18.19.129)(msw@2.11.2(@types/node@18.19.129)(typescript@5.7.3))(terser@5.44.0):
dependencies:
- '@types/chai': 5.2.3
+ '@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.11.2(@types/node@18.19.130)(typescript@5.7.3))(vite@7.2.2(@types/node@18.19.130)(terser@5.44.1))
+ '@vitest/mocker': 3.2.4(msw@2.11.2(@types/node@18.19.129)(typescript@5.7.3))(vite@7.1.9(@types/node@18.19.129)(terser@5.44.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -2031,20 +1949,20 @@ snapshots:
chai: 5.3.3
debug: 4.4.3
expect-type: 1.2.2
- magic-string: 0.30.21
+ magic-string: 0.30.19
pathe: 2.0.3
picomatch: 4.0.3
- std-env: 3.10.0
+ std-env: 3.9.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 7.2.2(@types/node@18.19.130)(terser@5.44.1)
- vite-node: 3.2.4(@types/node@18.19.130)(terser@5.44.1)
+ vite: 7.1.9(@types/node@18.19.129)(terser@5.44.0)
+ vite-node: 3.2.4(@types/node@18.19.129)(terser@5.44.0)
why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 18.19.130
+ '@types/node': 18.19.129
transitivePeerDependencies:
- jiti
- less
@@ -2076,7 +1994,7 @@ snapshots:
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
acorn-import-phases: 1.0.4(acorn@8.15.0)
- browserslist: 4.27.0
+ browserslist: 4.26.3
chrome-trace-event: 1.0.4
enhanced-resolve: 5.18.3
es-module-lexer: 1.7.0
@@ -2085,7 +2003,7 @@ snapshots:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
json-parse-even-better-errors: 2.3.1
- loader-runner: 4.3.1
+ loader-runner: 4.3.0
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 4.3.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6e4c395..339da38 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1 +1 @@
-packages: ['.']
\ No newline at end of file
+packages: ["."]
diff --git a/reference.md b/reference.md
index 1e28385..81851b3 100644
--- a/reference.md
+++ b/reference.md
@@ -1,5 +1,7 @@
# Reference
+
## Imdb
+
client.imdb.createMovie({ ...params }) -> FernAutopilotTestApi.MovieId
-
@@ -13,6 +15,7 @@
-
Add a movie to the database
+
@@ -29,10 +32,12 @@ Add a movie to the database
```typescript
await client.imdb.createMovie({
title: "title",
- rating: 1.1
+ rating: 1.1,
+ metadata: "metadata",
+ more_metadata: "more_metadata",
});
-
```
+
@@ -46,22 +51,21 @@ await client.imdb.createMovie({
-
-**request:** `FernAutopilotTestApi.CreateMovieRequest`
-
+**request:** `FernAutopilotTestApi.CreateMovieRequest`
+
-
-**requestOptions:** `Imdb.RequestOptions`
-
+**requestOptions:** `Imdb.RequestOptions`
+
-
@@ -79,6 +83,7 @@ await client.imdb.createMovie({
Retrieve a movie from the database based on the ID
+
@@ -94,8 +99,8 @@ Retrieve a movie from the database based on the ID
```typescript
await client.imdb.getMovie("tt0111161");
-
```
+
@@ -109,22 +114,21 @@ await client.imdb.getMovie("tt0111161");
-
-**id:** `FernAutopilotTestApi.MovieId`
-
+**id:** `FernAutopilotTestApi.MovieId`
+
-
-**requestOptions:** `Imdb.RequestOptions`
-
+**requestOptions:** `Imdb.RequestOptions`
+
-
diff --git a/src/BaseClient.ts b/src/BaseClient.ts
deleted file mode 100644
index 678bdf0..0000000
--- a/src/BaseClient.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// This file was auto-generated by Fern from our API Definition.
-
-import type * as core from "./core/index.js";
-
-export interface BaseClientOptions {
- environment: core.Supplier;
- /** Specify a custom URL to connect the client to. */
- baseUrl?: core.Supplier;
- /** Additional headers to include in requests. */
- headers?: Record | null | undefined>;
- /** The default maximum time to wait for a response in seconds. */
- timeoutInSeconds?: number;
- /** The default number of times to retry the request. Defaults to 2. */
- maxRetries?: number;
- /** Provide a custom fetch implementation. Useful for platforms that don't have a built-in fetch or need a custom implementation. */
- fetch?: typeof fetch;
- /** Configure logging for the client. */
- logging?: core.logging.LogConfig | core.logging.Logger;
-}
-
-export interface BaseRequestOptions {
- /** The maximum time to wait for a response in seconds. */
- timeoutInSeconds?: number;
- /** The number of times to retry the request. Defaults to 2. */
- maxRetries?: number;
- /** A hook to abort the request. */
- abortSignal?: AbortSignal;
- /** Additional query string parameters to include in the request. */
- queryParams?: Record;
- /** Additional headers to include in the request. */
- headers?: Record | null | undefined>;
-}
diff --git a/src/Client.ts b/src/Client.ts
index 44bfcd0..ff674ac 100644
--- a/src/Client.ts
+++ b/src/Client.ts
@@ -1,14 +1,30 @@
// This file was auto-generated by Fern from our API Definition.
-import { Imdb } from "./api/resources/imdb/client/Client.js";
-import type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js";
-import { mergeHeaders } from "./core/headers.js";
import * as core from "./core/index.js";
+import { mergeHeaders } from "./core/headers.js";
+import { Imdb } from "./api/resources/imdb/client/Client.js";
export declare namespace FernAutopilotTestApiClient {
- export interface Options extends BaseClientOptions {}
+ export interface Options {
+ environment: core.Supplier;
+ /** Specify a custom URL to connect the client to. */
+ baseUrl?: core.Supplier;
+ /** Additional headers to include in requests. */
+ headers?: Record | null | undefined>;
+ }
- export interface RequestOptions extends BaseRequestOptions {}
+ export interface RequestOptions {
+ /** The maximum time to wait for a response in seconds. */
+ timeoutInSeconds?: number;
+ /** The number of times to retry the request. Defaults to 2. */
+ maxRetries?: number;
+ /** A hook to abort the request. */
+ abortSignal?: AbortSignal;
+ /** Additional query string parameters to include in the request. */
+ queryParams?: Record;
+ /** Additional headers to include in the request. */
+ headers?: Record | null | undefined>;
+ }
}
export class FernAutopilotTestApiClient {
@@ -18,13 +34,11 @@ export class FernAutopilotTestApiClient {
constructor(_options: FernAutopilotTestApiClient.Options) {
this._options = {
..._options,
- logging: core.logging.createLogger(_options?.logging),
headers: mergeHeaders(
{
"X-Fern-Language": "JavaScript",
"X-Fern-SDK-Name": "",
- "X-Fern-SDK-Version": "2.0.0",
- "User-Agent": "/2.0.0",
+ "X-Fern-SDK-Version": "2.0.1",
"X-Fern-Runtime": core.RUNTIME.type,
"X-Fern-Runtime-Version": core.RUNTIME.version,
},
diff --git a/src/api/resources/imdb/client/Client.ts b/src/api/resources/imdb/client/Client.ts
index 89960a8..fe7535e 100644
--- a/src/api/resources/imdb/client/Client.ts
+++ b/src/api/resources/imdb/client/Client.ts
@@ -1,15 +1,31 @@
// This file was auto-generated by Fern from our API Definition.
-import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClient.js";
-import { mergeHeaders } from "../../../../core/headers.js";
import * as core from "../../../../core/index.js";
-import * as errors from "../../../../errors/index.js";
import * as FernAutopilotTestApi from "../../../index.js";
+import { mergeHeaders } from "../../../../core/headers.js";
+import * as errors from "../../../../errors/index.js";
export declare namespace Imdb {
- export interface Options extends BaseClientOptions {}
+ export interface Options {
+ environment: core.Supplier;
+ /** Specify a custom URL to connect the client to. */
+ baseUrl?: core.Supplier;
+ /** Additional headers to include in requests. */
+ headers?: Record | null | undefined>;
+ }
- export interface RequestOptions extends BaseRequestOptions {}
+ export interface RequestOptions {
+ /** The maximum time to wait for a response in seconds. */
+ timeoutInSeconds?: number;
+ /** The number of times to retry the request. Defaults to 2. */
+ maxRetries?: number;
+ /** A hook to abort the request. */
+ abortSignal?: AbortSignal;
+ /** Additional query string parameters to include in the request. */
+ queryParams?: Record;
+ /** Additional headers to include in the request. */
+ headers?: Record | null | undefined>;
+ }
}
export class Imdb {
@@ -28,7 +44,9 @@ export class Imdb {
* @example
* await client.imdb.createMovie({
* title: "title",
- * rating: 1.1
+ * rating: 1.1,
+ * metadata: "metadata",
+ * more_metadata: "more_metadata"
* })
*/
public createMovie(
@@ -42,7 +60,7 @@ export class Imdb {
request: FernAutopilotTestApi.CreateMovieRequest,
requestOptions?: Imdb.RequestOptions,
): Promise> {
- const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers);
+ let _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join(
(await core.Supplier.get(this._options.baseUrl)) ??
@@ -55,11 +73,9 @@ export class Imdb {
queryParameters: requestOptions?.queryParams,
requestType: "json",
body: request,
- timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
- maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
+ maxRetries: requestOptions?.maxRetries,
abortSignal: requestOptions?.abortSignal,
- fetchFn: this._options?.fetch,
- logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body as FernAutopilotTestApi.MovieId, rawResponse: _response.rawResponse };
@@ -117,21 +133,19 @@ export class Imdb {
id: FernAutopilotTestApi.MovieId,
requestOptions?: Imdb.RequestOptions,
): Promise> {
- const _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers);
+ let _headers: core.Fetcher.Args["headers"] = mergeHeaders(this._options?.headers, requestOptions?.headers);
const _response = await core.fetcher({
url: core.url.join(
(await core.Supplier.get(this._options.baseUrl)) ??
(await core.Supplier.get(this._options.environment)),
- `/movies/${core.url.encodePathParam(id)}`,
+ `/movies/${encodeURIComponent(id)}`,
),
method: "GET",
headers: _headers,
queryParameters: requestOptions?.queryParams,
- timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
- maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
+ maxRetries: requestOptions?.maxRetries,
abortSignal: requestOptions?.abortSignal,
- fetchFn: this._options?.fetch,
- logging: this._options.logging,
});
if (_response.ok) {
return { data: _response.body as FernAutopilotTestApi.Movie, rawResponse: _response.rawResponse };
diff --git a/src/api/resources/imdb/errors/MovieDoesNotExistError.ts b/src/api/resources/imdb/errors/MovieDoesNotExistError.ts
index e1bd808..e53e1dd 100644
--- a/src/api/resources/imdb/errors/MovieDoesNotExistError.ts
+++ b/src/api/resources/imdb/errors/MovieDoesNotExistError.ts
@@ -1,8 +1,8 @@
// This file was auto-generated by Fern from our API Definition.
-import type * as core from "../../../../core/index.js";
import * as errors from "../../../../errors/index.js";
-import type * as FernAutopilotTestApi from "../../../index.js";
+import * as FernAutopilotTestApi from "../../../index.js";
+import * as core from "../../../../core/index.js";
export class MovieDoesNotExistError extends errors.FernAutopilotTestApiError {
constructor(body: FernAutopilotTestApi.MovieId, rawResponse?: core.RawResponse) {
diff --git a/src/api/resources/imdb/index.ts b/src/api/resources/imdb/index.ts
index b90a45b..f63e7d1 100644
--- a/src/api/resources/imdb/index.ts
+++ b/src/api/resources/imdb/index.ts
@@ -1,3 +1,3 @@
-export * from "./client/index.js";
-export * from "./errors/index.js";
export * from "./types/index.js";
+export * from "./errors/index.js";
+export * from "./client/index.js";
diff --git a/src/api/resources/imdb/types/CreateMovieRequest.ts b/src/api/resources/imdb/types/CreateMovieRequest.ts
index 86bfe80..05b6ccd 100644
--- a/src/api/resources/imdb/types/CreateMovieRequest.ts
+++ b/src/api/resources/imdb/types/CreateMovieRequest.ts
@@ -3,4 +3,6 @@
export interface CreateMovieRequest {
title: string;
rating: number;
+ metadata: string;
+ more_metadata: string;
}
diff --git a/src/api/resources/imdb/types/Movie.ts b/src/api/resources/imdb/types/Movie.ts
index 8b52190..ae90804 100644
--- a/src/api/resources/imdb/types/Movie.ts
+++ b/src/api/resources/imdb/types/Movie.ts
@@ -1,12 +1,11 @@
// This file was auto-generated by Fern from our API Definition.
-import type * as FernAutopilotTestApi from "../../../index.js";
+import * as FernAutopilotTestApi from "../../../index.js";
export interface Movie {
id: FernAutopilotTestApi.MovieId;
title: string;
/** The rating scale out of ten stars */
rating: number;
- description: string;
metadata: string;
}
diff --git a/src/api/resources/imdb/types/index.ts b/src/api/resources/imdb/types/index.ts
index e349b81..b3ed5ed 100644
--- a/src/api/resources/imdb/types/index.ts
+++ b/src/api/resources/imdb/types/index.ts
@@ -1,3 +1,3 @@
-export * from "./CreateMovieRequest.js";
-export * from "./Movie.js";
export * from "./MovieId.js";
+export * from "./Movie.js";
+export * from "./CreateMovieRequest.js";
diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts
index bee73c4..a93afdd 100644
--- a/src/api/resources/index.ts
+++ b/src/api/resources/index.ts
@@ -1,3 +1,3 @@
-export * from "./imdb/errors/index.js";
export * as imdb from "./imdb/index.js";
export * from "./imdb/types/index.js";
+export * from "./imdb/errors/index.js";
diff --git a/src/core/exports.ts b/src/core/exports.ts
deleted file mode 100644
index 69296d7..0000000
--- a/src/core/exports.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./logging/exports.js";
diff --git a/src/core/fetcher/APIResponse.ts b/src/core/fetcher/APIResponse.ts
index 97ab83c..dd4b946 100644
--- a/src/core/fetcher/APIResponse.ts
+++ b/src/core/fetcher/APIResponse.ts
@@ -1,4 +1,4 @@
-import type { RawResponse } from "./RawResponse.js";
+import { RawResponse } from "./RawResponse.js";
/**
* The response of an API call.
diff --git a/src/core/fetcher/BinaryResponse.ts b/src/core/fetcher/BinaryResponse.ts
index 4b4d0e8..614cb59 100644
--- a/src/core/fetcher/BinaryResponse.ts
+++ b/src/core/fetcher/BinaryResponse.ts
@@ -1,4 +1,4 @@
-import type { ResponseWithBody } from "./ResponseWithBody.js";
+import { ResponseWithBody } from "./ResponseWithBody.js";
export type BinaryResponse = {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */
diff --git a/src/core/fetcher/EndpointSupplier.ts b/src/core/fetcher/EndpointSupplier.ts
index 8079841..5f1ddde 100644
--- a/src/core/fetcher/EndpointSupplier.ts
+++ b/src/core/fetcher/EndpointSupplier.ts
@@ -1,5 +1,5 @@
-import type { EndpointMetadata } from "./EndpointMetadata.js";
-import type { Supplier } from "./Supplier.js";
+import { EndpointMetadata } from "./EndpointMetadata.js";
+import { Supplier } from "./Supplier.js";
type EndpointSupplierFn = (arg: { endpointMetadata: EndpointMetadata }) => T | Promise;
export type EndpointSupplier = Supplier | EndpointSupplierFn;
diff --git a/src/core/fetcher/Fetcher.ts b/src/core/fetcher/Fetcher.ts
index e68597d..09a1da1 100644
--- a/src/core/fetcher/Fetcher.ts
+++ b/src/core/fetcher/Fetcher.ts
@@ -1,8 +1,7 @@
import { toJson } from "../json.js";
-import { createLogger, type LogConfig, type Logger } from "../logging/logger.js";
-import type { APIResponse } from "./APIResponse.js";
+import { APIResponse } from "./APIResponse.js";
import { createRequestUrl } from "./createRequestUrl.js";
-import type { EndpointMetadata } from "./EndpointMetadata.js";
+import { EndpointMetadata } from "./EndpointMetadata.js";
import { EndpointSupplier } from "./EndpointSupplier.js";
import { getErrorResponseBody } from "./getErrorResponseBody.js";
import { getFetchFn } from "./getFetchFn.js";
@@ -26,12 +25,10 @@ export declare namespace Fetcher {
maxRetries?: number;
withCredentials?: boolean;
abortSignal?: AbortSignal;
- requestType?: "json" | "file" | "bytes" | "form" | "other";
+ requestType?: "json" | "file" | "bytes";
responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response";
duplex?: "half";
endpointMetadata?: EndpointMetadata;
- fetchFn?: typeof fetch;
- logging?: LogConfig | Logger;
}
export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError;
@@ -58,155 +55,6 @@ export declare namespace Fetcher {
}
}
-const SENSITIVE_HEADERS = new Set([
- "authorization",
- "www-authenticate",
- "x-api-key",
- "api-key",
- "apikey",
- "x-api-token",
- "x-auth-token",
- "auth-token",
- "cookie",
- "set-cookie",
- "proxy-authorization",
- "proxy-authenticate",
- "x-csrf-token",
- "x-xsrf-token",
- "x-session-token",
- "x-access-token",
-]);
-
-function redactHeaders(headers: Record): Record {
- const filtered: Record = {};
- for (const [key, value] of Object.entries(headers)) {
- if (SENSITIVE_HEADERS.has(key.toLowerCase())) {
- filtered[key] = "[REDACTED]";
- } else {
- filtered[key] = value;
- }
- }
- return filtered;
-}
-
-const SENSITIVE_QUERY_PARAMS = new Set([
- "api_key",
- "api-key",
- "apikey",
- "token",
- "access_token",
- "access-token",
- "auth_token",
- "auth-token",
- "password",
- "passwd",
- "secret",
- "api_secret",
- "api-secret",
- "apisecret",
- "key",
- "session",
- "session_id",
- "session-id",
-]);
-
-function redactQueryParameters(queryParameters?: Record): Record | undefined {
- if (queryParameters == null) {
- return queryParameters;
- }
- const redacted: Record = {};
- for (const [key, value] of Object.entries(queryParameters)) {
- if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) {
- redacted[key] = "[REDACTED]";
- } else {
- redacted[key] = value;
- }
- }
- return redacted;
-}
-
-function redactUrl(url: string): string {
- const protocolIndex = url.indexOf("://");
- if (protocolIndex === -1) return url;
-
- const afterProtocol = protocolIndex + 3;
-
- // Find the first delimiter that marks the end of the authority section
- const pathStart = url.indexOf("/", afterProtocol);
- let queryStart = url.indexOf("?", afterProtocol);
- let fragmentStart = url.indexOf("#", afterProtocol);
-
- const firstDelimiter = Math.min(
- pathStart === -1 ? url.length : pathStart,
- queryStart === -1 ? url.length : queryStart,
- fragmentStart === -1 ? url.length : fragmentStart,
- );
-
- // Find the LAST @ before the delimiter (handles multiple @ in credentials)
- let atIndex = -1;
- for (let i = afterProtocol; i < firstDelimiter; i++) {
- if (url[i] === "@") {
- atIndex = i;
- }
- }
-
- if (atIndex !== -1) {
- url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`;
- }
-
- // Recalculate queryStart since url might have changed
- queryStart = url.indexOf("?");
- if (queryStart === -1) return url;
-
- fragmentStart = url.indexOf("#", queryStart);
- const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length;
- const queryString = url.slice(queryStart + 1, queryEnd);
-
- if (queryString.length === 0) return url;
-
- // FAST PATH: Quick check if any sensitive keywords present
- // Using indexOf is faster than regex for simple substring matching
- const lower = queryString.toLowerCase();
- const hasSensitive =
- lower.includes("token") ||
- lower.includes("key") ||
- lower.includes("password") ||
- lower.includes("passwd") ||
- lower.includes("secret") ||
- lower.includes("session") ||
- lower.includes("auth");
-
- if (!hasSensitive) {
- return url;
- }
-
- // SLOW PATH: Parse and redact
- const redactedParams: string[] = [];
- const params = queryString.split("&");
-
- for (const param of params) {
- const equalIndex = param.indexOf("=");
- if (equalIndex === -1) {
- redactedParams.push(param);
- continue;
- }
-
- const key = param.slice(0, equalIndex);
- let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase());
-
- if (!shouldRedact && key.includes("%")) {
- try {
- const decodedKey = decodeURIComponent(key);
- shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase());
- } catch {}
- }
-
- redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param);
- }
-
- return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd);
-}
-
async function getHeaders(args: Fetcher.Args): Promise> {
const newHeaders: Record = {};
if (args.body !== undefined && args.contentType != null) {
@@ -235,22 +83,9 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise= 200 && response.status < 400) {
- if (logger.isDebug()) {
- const metadata = {
- method: args.method,
- url: redactUrl(url),
- statusCode: response.status,
- responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())),
- };
- logger.debug("HTTP request succeeded", metadata);
- }
return {
ok: true,
body: (await getResponseBody(response, args.responseType)) as R,
@@ -286,15 +112,6 @@ export async function fetcherImpl(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise(args: Fetcher.Args): Promise
case "application/ld+json":
case "application/problem+json":
case "application/vnd.api+json":
- case "text/json": {
+ case "text/json":
const text = await response.text();
return text.length > 0 ? fromJson(text) : undefined;
- }
default:
if (contentType.startsWith("application/vnd.") && contentType.endsWith("+json")) {
const text = await response.text();
diff --git a/src/core/fetcher/getRequestBody.ts b/src/core/fetcher/getRequestBody.ts
index 91d9d81..e38457c 100644
--- a/src/core/fetcher/getRequestBody.ts
+++ b/src/core/fetcher/getRequestBody.ts
@@ -1,17 +1,13 @@
import { toJson } from "../json.js";
-import { toQueryString } from "../url/qs.js";
export declare namespace GetRequestBody {
interface Args {
body: unknown;
- type: "json" | "file" | "bytes" | "form" | "other";
+ type: "json" | "file" | "bytes" | "other";
}
}
export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise {
- if (type === "form") {
- return toQueryString(body, { arrayFormat: "repeat", encode: true });
- }
if (type.includes("json")) {
return toJson(body);
} else {
diff --git a/src/core/fetcher/getResponseBody.ts b/src/core/fetcher/getResponseBody.ts
index 0f24de1..7ca8b3d 100644
--- a/src/core/fetcher/getResponseBody.ts
+++ b/src/core/fetcher/getResponseBody.ts
@@ -1,6 +1,6 @@
-import { fromJson } from "../json.js";
import { getBinaryResponse } from "./BinaryResponse.js";
import { isResponseWithBody } from "./ResponseWithBody.js";
+import { fromJson } from "../json.js";
export async function getResponseBody(response: Response, responseType?: string): Promise {
if (!isResponseWithBody(response)) {
@@ -26,9 +26,9 @@ export async function getResponseBody(response: Response, responseType?: string)
const text = await response.text();
if (text.length > 0) {
try {
- const responseBody = fromJson(text);
+ let responseBody = fromJson(text);
return responseBody;
- } catch (_err) {
+ } catch (err) {
return {
ok: false,
error: {
diff --git a/src/core/fetcher/makeRequest.ts b/src/core/fetcher/makeRequest.ts
index c78e700..1a5ffd3 100644
--- a/src/core/fetcher/makeRequest.ts
+++ b/src/core/fetcher/makeRequest.ts
@@ -13,17 +13,19 @@ export const makeRequest = async (
): Promise => {
const signals: AbortSignal[] = [];
- let timeoutAbortId: NodeJS.Timeout | undefined;
+ // Add timeout signal
+ let timeoutAbortId: NodeJS.Timeout | undefined = undefined;
if (timeoutMs != null) {
const { signal, abortId } = getTimeoutSignal(timeoutMs);
timeoutAbortId = abortId;
signals.push(signal);
}
+ // Add arbitrary signal
if (abortSignal != null) {
signals.push(abortSignal);
}
- const newSignals = anySignal(signals);
+ let newSignals = anySignal(signals);
const response = await fetchFn(url, {
method: method,
headers,
diff --git a/src/core/fetcher/requestWithRetries.ts b/src/core/fetcher/requestWithRetries.ts
index 1f68968..560432e 100644
--- a/src/core/fetcher/requestWithRetries.ts
+++ b/src/core/fetcher/requestWithRetries.ts
@@ -4,25 +4,30 @@ const DEFAULT_MAX_RETRIES = 2;
const JITTER_FACTOR = 0.2; // 20% random jitter
function addPositiveJitter(delay: number): number {
+ // Generate a random value between 0 and +JITTER_FACTOR
const jitterMultiplier = 1 + Math.random() * JITTER_FACTOR;
return delay * jitterMultiplier;
}
function addSymmetricJitter(delay: number): number {
+ // Generate a random value in a JITTER_FACTOR-sized percentage range around delay
const jitterMultiplier = 1 + (Math.random() - 0.5) * JITTER_FACTOR;
return delay * jitterMultiplier;
}
function getRetryDelayFromHeaders(response: Response, retryAttempt: number): number {
+ // Check for Retry-After header first (RFC 7231), with no jitter
const retryAfter = response.headers.get("Retry-After");
if (retryAfter) {
+ // Parse as number of seconds...
const retryAfterSeconds = parseInt(retryAfter, 10);
- if (!Number.isNaN(retryAfterSeconds) && retryAfterSeconds > 0) {
+ if (!isNaN(retryAfterSeconds) && retryAfterSeconds > 0) {
return Math.min(retryAfterSeconds * 1000, MAX_RETRY_DELAY);
}
+ // ...or as an HTTP date; both are valid
const retryAfterDate = new Date(retryAfter);
- if (!Number.isNaN(retryAfterDate.getTime())) {
+ if (!isNaN(retryAfterDate.getTime())) {
const delay = retryAfterDate.getTime() - Date.now();
if (delay > 0) {
return Math.min(Math.max(delay, 0), MAX_RETRY_DELAY);
@@ -30,10 +35,12 @@ function getRetryDelayFromHeaders(response: Response, retryAttempt: number): num
}
}
+ // Then check for industry-standard X-RateLimit-Reset header, with positive jitter
const rateLimitReset = response.headers.get("X-RateLimit-Reset");
if (rateLimitReset) {
const resetTime = parseInt(rateLimitReset, 10);
- if (!Number.isNaN(resetTime)) {
+ if (!isNaN(resetTime)) {
+ // Assume Unix timestamp in epoch seconds
const delay = resetTime * 1000 - Date.now();
if (delay > 0) {
return addPositiveJitter(Math.min(delay, MAX_RETRY_DELAY));
@@ -41,7 +48,8 @@ function getRetryDelayFromHeaders(response: Response, retryAttempt: number): num
}
}
- return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * 2 ** retryAttempt, MAX_RETRY_DELAY));
+ // Fall back to exponential backoff, with symmetric jitter
+ return addSymmetricJitter(Math.min(INITIAL_RETRY_DELAY * Math.pow(2, retryAttempt), MAX_RETRY_DELAY));
}
export async function requestWithRetries(
@@ -52,6 +60,7 @@ export async function requestWithRetries(
for (let i = 0; i < maxRetries; ++i) {
if ([408, 429].includes(response.status) || response.status >= 500) {
+ // Get delay with appropriate jitter applied
const delay = getRetryDelayFromHeaders(response, i);
await new Promise((resolve) => setTimeout(resolve, delay));
diff --git a/src/core/fetcher/signals.ts b/src/core/fetcher/signals.ts
index c9fcaef..a8d32a2 100644
--- a/src/core/fetcher/signals.ts
+++ b/src/core/fetcher/signals.ts
@@ -6,17 +6,29 @@ export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abor
return { signal: controller.signal, abortId };
}
+/**
+ * Returns an abort signal that is getting aborted when
+ * at least one of the specified abort signals is aborted.
+ *
+ * Requires at least node.js 18.
+ */
export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal {
+ // Allowing signals to be passed either as array
+ // of signals or as multiple arguments.
const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args) as AbortSignal[];
const controller = new AbortController();
for (const signal of signals) {
if (signal.aborted) {
+ // Exiting early if one of the signals
+ // is already aborted.
controller.abort((signal as any)?.reason);
break;
}
+ // Listening for signals and removing the listeners
+ // when at least one symbol is aborted.
signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), {
signal: controller.signal,
});
diff --git a/src/core/headers.ts b/src/core/headers.ts
index 78ed8b5..a723d22 100644
--- a/src/core/headers.ts
+++ b/src/core/headers.ts
@@ -6,11 +6,10 @@ export function mergeHeaders(
for (const [key, value] of headersArray
.filter((headers) => headers != null)
.flatMap((headers) => Object.entries(headers))) {
- const insensitiveKey = key.toLowerCase();
if (value != null) {
- result[insensitiveKey] = value;
- } else if (insensitiveKey in result) {
- delete result[insensitiveKey];
+ result[key] = value;
+ } else if (key in result) {
+ delete result[key];
}
}
@@ -25,9 +24,8 @@ export function mergeOnlyDefinedHeaders(
for (const [key, value] of headersArray
.filter((headers) => headers != null)
.flatMap((headers) => Object.entries(headers))) {
- const insensitiveKey = key.toLowerCase();
if (value != null) {
- result[insensitiveKey] = value;
+ result[key] = value;
}
}
diff --git a/src/core/index.ts b/src/core/index.ts
index afa8351..bbb640d 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -1,4 +1,3 @@
export * from "./fetcher/index.js";
-export * as logging from "./logging/index.js";
export * from "./runtime/index.js";
export * as url from "./url/index.js";
diff --git a/src/core/logging/exports.ts b/src/core/logging/exports.ts
deleted file mode 100644
index 88f6c00..0000000
--- a/src/core/logging/exports.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as logger from "./logger.js";
-
-export namespace logging {
- /**
- * Configuration for logger instances.
- */
- export type LogConfig = logger.LogConfig;
- export type LogLevel = logger.LogLevel;
- export const LogLevel: typeof logger.LogLevel = logger.LogLevel;
- export type ILogger = logger.ILogger;
- /**
- * Console logger implementation that outputs to the console.
- */
- export type ConsoleLogger = logger.ConsoleLogger;
- /**
- * Console logger implementation that outputs to the console.
- */
- export const ConsoleLogger: typeof logger.ConsoleLogger = logger.ConsoleLogger;
-}
diff --git a/src/core/logging/index.ts b/src/core/logging/index.ts
deleted file mode 100644
index d81cc32..0000000
--- a/src/core/logging/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./logger.js";
diff --git a/src/core/logging/logger.ts b/src/core/logging/logger.ts
deleted file mode 100644
index a3f3673..0000000
--- a/src/core/logging/logger.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-export const LogLevel = {
- Debug: "debug",
- Info: "info",
- Warn: "warn",
- Error: "error",
-} as const;
-export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
-const logLevelMap: Record = {
- [LogLevel.Debug]: 1,
- [LogLevel.Info]: 2,
- [LogLevel.Warn]: 3,
- [LogLevel.Error]: 4,
-};
-
-export interface ILogger {
- /**
- * Logs a debug message.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- debug(message: string, ...args: unknown[]): void;
- /**
- * Logs an info message.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- info(message: string, ...args: unknown[]): void;
- /**
- * Logs a warning message.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- warn(message: string, ...args: unknown[]): void;
- /**
- * Logs an error message.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- error(message: string, ...args: unknown[]): void;
-}
-
-/**
- * Configuration for logger initialization.
- */
-export interface LogConfig {
- /**
- * Minimum log level to output.
- * @default LogLevel.Info
- */
- level?: LogLevel;
- /**
- * Logger implementation to use.
- * @default new ConsoleLogger()
- */
- logger?: ILogger;
- /**
- * Whether logging should be silenced.
- * @default true
- */
- silent?: boolean;
-}
-
-/**
- * Default console-based logger implementation.
- */
-export class ConsoleLogger implements ILogger {
- debug(message: string, ...args: unknown[]): void {
- console.debug(message, ...args);
- }
- info(message: string, ...args: unknown[]): void {
- console.info(message, ...args);
- }
- warn(message: string, ...args: unknown[]): void {
- console.warn(message, ...args);
- }
- error(message: string, ...args: unknown[]): void {
- console.error(message, ...args);
- }
-}
-
-/**
- * Logger class that provides level-based logging functionality.
- */
-export class Logger {
- private readonly level: number;
- private readonly logger: ILogger;
- private readonly silent: boolean;
-
- /**
- * Creates a new logger instance.
- * @param config - Logger configuration
- */
- constructor(config: Required) {
- this.level = logLevelMap[config.level];
- this.logger = config.logger;
- this.silent = config.silent;
- }
-
- /**
- * Checks if a log level should be output based on configuration.
- * @param level - The log level to check
- * @returns True if the level should be logged
- */
- public shouldLog(level: LogLevel): boolean {
- return !this.silent && this.level <= logLevelMap[level];
- }
-
- /**
- * Checks if debug logging is enabled.
- * @returns True if debug logs should be output
- */
- public isDebug(): boolean {
- return this.shouldLog(LogLevel.Debug);
- }
-
- /**
- * Logs a debug message if debug logging is enabled.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- public debug(message: string, ...args: unknown[]): void {
- if (this.isDebug()) {
- this.logger.debug(message, ...args);
- }
- }
-
- /**
- * Checks if info logging is enabled.
- * @returns True if info logs should be output
- */
- public isInfo(): boolean {
- return this.shouldLog(LogLevel.Info);
- }
-
- /**
- * Logs an info message if info logging is enabled.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- public info(message: string, ...args: unknown[]): void {
- if (this.isInfo()) {
- this.logger.info(message, ...args);
- }
- }
-
- /**
- * Checks if warning logging is enabled.
- * @returns True if warning logs should be output
- */
- public isWarn(): boolean {
- return this.shouldLog(LogLevel.Warn);
- }
-
- /**
- * Logs a warning message if warning logging is enabled.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- public warn(message: string, ...args: unknown[]): void {
- if (this.isWarn()) {
- this.logger.warn(message, ...args);
- }
- }
-
- /**
- * Checks if error logging is enabled.
- * @returns True if error logs should be output
- */
- public isError(): boolean {
- return this.shouldLog(LogLevel.Error);
- }
-
- /**
- * Logs an error message if error logging is enabled.
- * @param message - The message to log
- * @param args - Additional arguments to log
- */
- public error(message: string, ...args: unknown[]): void {
- if (this.isError()) {
- this.logger.error(message, ...args);
- }
- }
-}
-
-export function createLogger(config?: LogConfig | Logger): Logger {
- if (config == null) {
- return defaultLogger;
- }
- if (config instanceof Logger) {
- return config;
- }
- config = config ?? {};
- config.level ??= LogLevel.Info;
- config.logger ??= new ConsoleLogger();
- config.silent ??= true;
- return new Logger(config as Required);
-}
-
-const defaultLogger: Logger = new Logger({
- level: LogLevel.Info,
- logger: new ConsoleLogger(),
- silent: true,
-});
diff --git a/src/core/url/encodePathParam.ts b/src/core/url/encodePathParam.ts
deleted file mode 100644
index 19b9012..0000000
--- a/src/core/url/encodePathParam.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export function encodePathParam(param: unknown): string {
- if (param === null) {
- return "null";
- }
- const typeofParam = typeof param;
- switch (typeofParam) {
- case "undefined":
- return "undefined";
- case "string":
- case "number":
- case "boolean":
- break;
- default:
- param = String(param);
- break;
- }
- return encodeURIComponent(param as string | number | boolean);
-}
diff --git a/src/core/url/index.ts b/src/core/url/index.ts
index f2e0fa2..ed5aa0f 100644
--- a/src/core/url/index.ts
+++ b/src/core/url/index.ts
@@ -1,3 +1,2 @@
-export { encodePathParam } from "./encodePathParam.js";
export { join } from "./join.js";
export { toQueryString } from "./qs.js";
diff --git a/src/core/url/join.ts b/src/core/url/join.ts
index 7ca7dae..200426b 100644
--- a/src/core/url/join.ts
+++ b/src/core/url/join.ts
@@ -12,11 +12,12 @@ export function join(base: string, ...segments: string[]): string {
try {
url = new URL(base);
} catch {
+ // Fallback to path joining if URL is malformed
return joinPath(base, ...segments);
}
const lastSegment = segments[segments.length - 1];
- const shouldPreserveTrailingSlash = lastSegment?.endsWith("/");
+ const shouldPreserveTrailingSlash = lastSegment && lastSegment.endsWith("/");
for (const segment of segments) {
const cleanSegment = trimSlashes(segment);
@@ -43,7 +44,7 @@ function joinPath(base: string, ...segments: string[]): string {
let result = base;
const lastSegment = segments[segments.length - 1];
- const shouldPreserveTrailingSlash = lastSegment?.endsWith("/");
+ const shouldPreserveTrailingSlash = lastSegment && lastSegment.endsWith("/");
for (const segment of segments) {
const cleanSegment = trimSlashes(segment);
@@ -63,7 +64,7 @@ function joinPathSegments(left: string, right: string): string {
if (left.endsWith("/")) {
return left + right;
}
- return `${left}/${right}`;
+ return left + "/" + right;
}
function trimSlashes(str: string): string {
diff --git a/src/errors/FernAutopilotTestApiError.ts b/src/errors/FernAutopilotTestApiError.ts
index e16087b..665e8fe 100644
--- a/src/errors/FernAutopilotTestApiError.ts
+++ b/src/errors/FernAutopilotTestApiError.ts
@@ -1,6 +1,6 @@
// This file was auto-generated by Fern from our API Definition.
-import type * as core from "../core/index.js";
+import * as core from "../core/index.js";
import { toJson } from "../core/json.js";
export class FernAutopilotTestApiError extends Error {
@@ -36,7 +36,7 @@ function buildMessage({
statusCode: number | undefined;
body: unknown | undefined;
}): string {
- const lines: string[] = [];
+ let lines: string[] = [];
if (message != null) {
lines.push(message);
}
diff --git a/src/exports.ts b/src/exports.ts
deleted file mode 100644
index 7b70ee1..0000000
--- a/src/exports.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./core/exports.js";
diff --git a/src/index.ts b/src/index.ts
index 830815b..d2212bd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,3 @@
export * as FernAutopilotTestApi from "./api/index.js";
-export type { BaseClientOptions, BaseRequestOptions } from "./BaseClient.js";
-export { FernAutopilotTestApiClient } from "./Client.js";
export { FernAutopilotTestApiError, FernAutopilotTestApiTimeoutError } from "./errors/index.js";
-export * from "./exports.js";
+export { FernAutopilotTestApiClient } from "./Client.js";
diff --git a/src/version.ts b/src/version.ts
index 478f50d..85cbd4f 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const SDK_VERSION = "2.0.0";
+export const SDK_VERSION = "2.0.1";
diff --git a/tests/mock-server/MockServer.ts b/tests/mock-server/MockServer.ts
index 5b30fe7..6e258f1 100644
--- a/tests/mock-server/MockServer.ts
+++ b/tests/mock-server/MockServer.ts
@@ -1,4 +1,4 @@
-import type { RequestHandlerOptions } from "msw";
+import { RequestHandlerOptions } from "msw";
import type { SetupServer } from "msw/node";
import { mockEndpointBuilder } from "./mockEndpointBuilder";
diff --git a/tests/mock-server/MockServerPool.ts b/tests/mock-server/MockServerPool.ts
index e1a90f7..8160806 100644
--- a/tests/mock-server/MockServerPool.ts
+++ b/tests/mock-server/MockServerPool.ts
@@ -22,7 +22,7 @@ async function formatHttpRequest(request: Request, id?: string): Promise
} else if (clone.body) {
body = await clone.text();
}
- } catch (_e) {
+ } catch (e) {
body = "(unable to parse body)";
}
@@ -48,7 +48,7 @@ async function formatHttpResponse(response: Response, id?: string): Promise {
const formattedRequest = await formatHttpRequest(request, requestId);
- console.debug(`request:start\n${formattedRequest}`);
+ console.debug("request:start\n" + formattedRequest);
});
mswServer.events.on("request:unhandled", async ({ request, requestId }) => {
const formattedRequest = await formatHttpRequest(request, requestId);
- console.debug(`request:unhandled\n${formattedRequest}`);
+ console.debug("request:unhandled\n" + formattedRequest);
});
mswServer.events.on("response:mocked", async ({ request, response, requestId }) => {
const formattedResponse = await formatHttpResponse(response, requestId);
- console.debug(`response:mocked\n${formattedResponse}`);
+ console.debug("response:mocked\n" + formattedResponse);
});
}
}
diff --git a/tests/mock-server/mockEndpointBuilder.ts b/tests/mock-server/mockEndpointBuilder.ts
index 1b0e510..88368d4 100644
--- a/tests/mock-server/mockEndpointBuilder.ts
+++ b/tests/mock-server/mockEndpointBuilder.ts
@@ -1,8 +1,7 @@
-import { type DefaultBodyType, type HttpHandler, HttpResponse, type HttpResponseResolver, http } from "msw";
+import { DefaultBodyType, HttpHandler, HttpResponse, HttpResponseResolver, http } from "msw";
import { url } from "../../src/core";
import { toJson } from "../../src/core/json";
-import { withFormUrlEncoded } from "./withFormUrlEncoded";
import { withHeaders } from "./withHeaders";
import { withJson } from "./withJson";
@@ -27,7 +26,6 @@ interface RequestHeadersStage extends RequestBodyStage, ResponseStage {
interface RequestBodyStage extends ResponseStage {
jsonBody(body: unknown): ResponseStage;
- formUrlEncodedBody(body: unknown): ResponseStage;
}
interface ResponseStage {
@@ -137,16 +135,6 @@ class RequestBuilder implements MethodStage, RequestHeadersStage, RequestBodySta
return this;
}
- formUrlEncodedBody(body: unknown): ResponseStage {
- if (body === undefined) {
- throw new Error(
- "Undefined is not valid for form-urlencoded. Do not call formUrlEncodedBody if you want an empty body.",
- );
- }
- this.predicates.push((resolver) => withFormUrlEncoded(body, resolver));
- return this;
- }
-
respondWith(): ResponseStatusStage {
return new ResponseBuilder(this.method, this.buildUrl(), this.predicates, this.handlerOptions);
}
diff --git a/tests/mock-server/withFormUrlEncoded.ts b/tests/mock-server/withFormUrlEncoded.ts
deleted file mode 100644
index e9e6ff2..0000000
--- a/tests/mock-server/withFormUrlEncoded.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { type HttpResponseResolver, passthrough } from "msw";
-
-import { toJson } from "../../src/core/json";
-
-/**
- * Creates a request matcher that validates if the request form-urlencoded body exactly matches the expected object
- * @param expectedBody - The exact body object to match against
- * @param resolver - Response resolver to execute if body matches
- */
-export function withFormUrlEncoded(expectedBody: unknown, resolver: HttpResponseResolver): HttpResponseResolver {
- return async (args) => {
- const { request } = args;
-
- let clonedRequest: Request;
- let bodyText: string | undefined;
- let actualBody: Record;
- try {
- clonedRequest = request.clone();
- bodyText = await clonedRequest.text();
- if (bodyText === "") {
- console.error("Request body is empty, expected a form-urlencoded body.");
- return passthrough();
- }
- const params = new URLSearchParams(bodyText);
- actualBody = {};
- for (const [key, value] of params.entries()) {
- actualBody[key] = value;
- }
- } catch (error) {
- console.error(`Error processing form-urlencoded request body:\n\tError: ${error}\n\tBody: ${bodyText}`);
- return passthrough();
- }
-
- const mismatches = findMismatches(actualBody, expectedBody);
- if (Object.keys(mismatches).length > 0) {
- console.error("Form-urlencoded body mismatch:", toJson(mismatches, undefined, 2));
- return passthrough();
- }
-
- return resolver(args);
- };
-}
-
-function findMismatches(actual: any, expected: any): Record {
- const mismatches: Record = {};
-
- if (typeof actual !== typeof expected) {
- return { value: { actual, expected } };
- }
-
- if (typeof actual !== "object" || actual === null || expected === null) {
- if (actual !== expected) {
- return { value: { actual, expected } };
- }
- return {};
- }
-
- const actualKeys = Object.keys(actual);
- const expectedKeys = Object.keys(expected);
-
- const allKeys = new Set([...actualKeys, ...expectedKeys]);
-
- for (const key of allKeys) {
- if (!expectedKeys.includes(key)) {
- if (actual[key] === undefined) {
- continue;
- }
- mismatches[key] = { actual: actual[key], expected: undefined };
- } else if (!actualKeys.includes(key)) {
- if (expected[key] === undefined) {
- continue;
- }
- mismatches[key] = { actual: undefined, expected: expected[key] };
- } else if (actual[key] !== expected[key]) {
- mismatches[key] = { actual: actual[key], expected: expected[key] };
- }
- }
-
- return mismatches;
-}
diff --git a/tests/mock-server/withHeaders.ts b/tests/mock-server/withHeaders.ts
index 6599d2b..e77c837 100644
--- a/tests/mock-server/withHeaders.ts
+++ b/tests/mock-server/withHeaders.ts
@@ -1,4 +1,4 @@
-import { type HttpResponseResolver, passthrough } from "msw";
+import { HttpResponseResolver, passthrough } from "msw";
/**
* Creates a request matcher that validates if request headers match specified criteria
diff --git a/tests/mock-server/withJson.ts b/tests/mock-server/withJson.ts
index b627638..03f585d 100644
--- a/tests/mock-server/withJson.ts
+++ b/tests/mock-server/withJson.ts
@@ -1,4 +1,4 @@
-import { type HttpResponseResolver, passthrough } from "msw";
+import { HttpResponseResolver, passthrough } from "msw";
import { fromJson, toJson } from "../../src/core/json";
@@ -67,7 +67,7 @@ function findMismatches(actual: any, expected: any): Record 0) {
for (const [mismatchKey, mismatchValue] of Object.entries(itemMismatches)) {
- arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : `.${mismatchKey}`}`] = mismatchValue;
+ arrayMismatches[`[${i}]${mismatchKey === "value" ? "" : "." + mismatchKey}`] = mismatchValue;
}
}
}
@@ -99,7 +99,7 @@ function findMismatches(actual: any, expected: any): Record 0) {
for (const [nestedKey, nestedValue] of Object.entries(nestedMismatches)) {
- mismatches[`${key}${nestedKey === "value" ? "" : `.${nestedKey}`}`] = nestedValue;
+ mismatches[`${key}${nestedKey === "value" ? "" : "." + nestedKey}`] = nestedValue;
}
}
} else if (actual[key] !== expected[key]) {
diff --git a/tests/unit/fetcher/Fetcher.test.ts b/tests/unit/fetcher/Fetcher.test.ts
index a8d458f..80665d7 100644
--- a/tests/unit/fetcher/Fetcher.test.ts
+++ b/tests/unit/fetcher/Fetcher.test.ts
@@ -1,8 +1,9 @@
import fs from "fs";
-import { join } from "path";
import stream from "stream";
+import { join } from "path";
+
+import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher";
import type { BinaryResponse } from "../../../src/core";
-import { type Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher";
describe("Test fetcherImpl", () => {
it("should handle successful request", async () => {
@@ -13,7 +14,6 @@ describe("Test fetcherImpl", () => {
body: { data: "test" },
contentType: "application/json",
requestType: "json",
- maxRetries: 0,
responseType: "json",
};
@@ -48,7 +48,6 @@ describe("Test fetcherImpl", () => {
headers: { "X-Test": "x-test-header" },
contentType: "application/octet-stream",
requestType: "bytes",
- maxRetries: 0,
responseType: "json",
body: fs.createReadStream(join(__dirname, "test-file.txt")),
};
@@ -82,7 +81,6 @@ describe("Test fetcherImpl", () => {
url,
method: "GET",
headers: { "X-Test": "x-test-header" },
- maxRetries: 0,
responseType: "binary-response",
};
@@ -128,7 +126,6 @@ describe("Test fetcherImpl", () => {
url,
method: "GET",
headers: { "X-Test": "x-test-header" },
- maxRetries: 0,
responseType: "binary-response",
};
@@ -174,7 +171,6 @@ describe("Test fetcherImpl", () => {
url,
method: "GET",
headers: { "X-Test": "x-test-header" },
- maxRetries: 0,
responseType: "binary-response",
};
@@ -218,7 +214,6 @@ describe("Test fetcherImpl", () => {
url,
method: "GET",
headers: { "X-Test": "x-test-header" },
- maxRetries: 0,
responseType: "binary-response",
};
diff --git a/tests/unit/fetcher/HttpResponsePromise.test.ts b/tests/unit/fetcher/HttpResponsePromise.test.ts
index 2ec008e..c48ac30 100644
--- a/tests/unit/fetcher/HttpResponsePromise.test.ts
+++ b/tests/unit/fetcher/HttpResponsePromise.test.ts
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { HttpResponsePromise } from "../../../src/core/fetcher/HttpResponsePromise";
-import type { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse";
+import { RawResponse, WithRawResponse } from "../../../src/core/fetcher/RawResponse";
describe("HttpResponsePromise", () => {
const mockRawResponse: RawResponse = {
diff --git a/tests/unit/fetcher/createRequestUrl.test.ts b/tests/unit/fetcher/createRequestUrl.test.ts
index a92f1b5..06e03b2 100644
--- a/tests/unit/fetcher/createRequestUrl.test.ts
+++ b/tests/unit/fetcher/createRequestUrl.test.ts
@@ -1,163 +1,160 @@
import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl";
describe("Test createRequestUrl", () => {
- const BASE_URL = "https://api.example.com";
-
- interface TestCase {
- description: string;
- baseUrl: string;
- queryParams?: Record;
- expected: string;
- }
-
- const testCases: TestCase[] = [
- {
- description: "should return the base URL when no query parameters are provided",
- baseUrl: BASE_URL,
- expected: BASE_URL,
- },
- {
- description: "should append simple query parameters",
- baseUrl: BASE_URL,
- queryParams: { key: "value", another: "param" },
- expected: "https://api.example.com?key=value&another=param",
- },
- {
- description: "should handle array query parameters",
- baseUrl: BASE_URL,
- queryParams: { items: ["a", "b", "c"] },
- expected: "https://api.example.com?items=a&items=b&items=c",
- },
- {
- description: "should handle object query parameters",
- baseUrl: BASE_URL,
- queryParams: { filter: { name: "John", age: 30 } },
- expected: "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30",
- },
- {
- description: "should handle mixed types of query parameters",
- baseUrl: BASE_URL,
- queryParams: {
- simple: "value",
- array: ["x", "y"],
- object: { key: "value" },
- },
- expected: "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value",
- },
- {
- description: "should handle empty query parameters object",
- baseUrl: BASE_URL,
- queryParams: {},
- expected: BASE_URL,
- },
- {
- description: "should encode special characters in query parameters",
- baseUrl: BASE_URL,
- queryParams: { special: "a&b=c d" },
- expected: "https://api.example.com?special=a%26b%3Dc%20d",
- },
- {
- description: "should handle numeric values",
- baseUrl: BASE_URL,
- queryParams: { count: 42, price: 19.99, active: 1, inactive: 0 },
- expected: "https://api.example.com?count=42&price=19.99&active=1&inactive=0",
- },
- {
- description: "should handle boolean values",
- baseUrl: BASE_URL,
- queryParams: { enabled: true, disabled: false },
- expected: "https://api.example.com?enabled=true&disabled=false",
- },
- {
- description: "should handle null and undefined values",
- baseUrl: BASE_URL,
- queryParams: {
- valid: "value",
- nullValue: null,
- undefinedValue: undefined,
- emptyString: "",
- },
- expected: "https://api.example.com?valid=value&nullValue=&emptyString=",
- },
- {
- description: "should handle deeply nested objects",
- baseUrl: BASE_URL,
- queryParams: {
- user: {
- profile: {
- name: "John",
- settings: { theme: "dark" },
- },
+ it("should return the base URL when no query parameters are provided", () => {
+ const baseUrl = "https://api.example.com";
+ expect(createRequestUrl(baseUrl)).toBe(baseUrl);
+ });
+
+ it("should append simple query parameters", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { key: "value", another: "param" };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param");
+ });
+
+ it("should handle array query parameters", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { items: ["a", "b", "c"] };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c");
+ });
+
+ it("should handle object query parameters", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { filter: { name: "John", age: 30 } };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30",
+ );
+ });
+
+ it("should handle mixed types of query parameters", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ simple: "value",
+ array: ["x", "y"],
+ object: { key: "value" },
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value",
+ );
+ });
+
+ it("should handle empty query parameters object", () => {
+ const baseUrl = "https://api.example.com";
+ expect(createRequestUrl(baseUrl, {})).toBe(baseUrl);
+ });
+
+ it("should encode special characters in query parameters", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { special: "a&b=c d" };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d");
+ });
+
+ // Additional tests for edge cases and different value types
+ it("should handle numeric values", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { count: 42, price: 19.99, active: 1, inactive: 0 };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?count=42&price=19.99&active=1&inactive=0",
+ );
+ });
+
+ it("should handle boolean values", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { enabled: true, disabled: false };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?enabled=true&disabled=false");
+ });
+
+ it("should handle null and undefined values", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ valid: "value",
+ nullValue: null,
+ undefinedValue: undefined,
+ emptyString: "",
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?valid=value&nullValue=&emptyString=",
+ );
+ });
+
+ it("should handle deeply nested objects", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ user: {
+ profile: {
+ name: "John",
+ settings: { theme: "dark" },
},
},
- expected:
- "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark",
- },
- {
- description: "should handle arrays of objects",
- baseUrl: BASE_URL,
- queryParams: {
- users: [
- { name: "John", age: 30 },
- { name: "Jane", age: 25 },
- ],
- },
- expected:
- "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25",
- },
- {
- description: "should handle mixed arrays",
- baseUrl: BASE_URL,
- queryParams: {
- mixed: ["string", 42, true, { key: "value" }],
- },
- expected: "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value",
- },
- {
- description: "should handle empty arrays",
- baseUrl: BASE_URL,
- queryParams: { emptyArray: [] },
- expected: BASE_URL,
- },
- {
- description: "should handle empty objects",
- baseUrl: BASE_URL,
- queryParams: { emptyObject: {} },
- expected: BASE_URL,
- },
- {
- description: "should handle special characters in keys",
- baseUrl: BASE_URL,
- queryParams: { "key with spaces": "value", "key[with]brackets": "value" },
- expected: "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value",
- },
- {
- description: "should handle URL with existing query parameters",
- baseUrl: "https://api.example.com?existing=param",
- queryParams: { new: "value" },
- expected: "https://api.example.com?existing=param?new=value",
- },
- {
- description: "should handle complex nested structures",
- baseUrl: BASE_URL,
- queryParams: {
- filters: {
- status: ["active", "pending"],
- category: {
- type: "electronics",
- subcategories: ["phones", "laptops"],
- },
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark",
+ );
+ });
+
+ it("should handle arrays of objects", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ users: [
+ { name: "John", age: 30 },
+ { name: "Jane", age: 25 },
+ ],
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?users%5Bname%5D=John&users%5Bage%5D=30&users%5Bname%5D=Jane&users%5Bage%5D=25",
+ );
+ });
+
+ it("should handle mixed arrays", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ mixed: ["string", 42, true, { key: "value" }],
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?mixed=string&mixed=42&mixed=true&mixed%5Bkey%5D=value",
+ );
+ });
+
+ it("should handle empty arrays", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { emptyArray: [] };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(baseUrl);
+ });
+
+ it("should handle empty objects", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { emptyObject: {} };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(baseUrl);
+ });
+
+ it("should handle special characters in keys", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = { "key with spaces": "value", "key[with]brackets": "value" };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?key%20with%20spaces=value&key%5Bwith%5Dbrackets=value",
+ );
+ });
+
+ it("should handle URL with existing query parameters", () => {
+ const baseUrl = "https://api.example.com?existing=param";
+ const queryParams = { new: "value" };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?existing=param?new=value");
+ });
+
+ it("should handle complex nested structures", () => {
+ const baseUrl = "https://api.example.com";
+ const queryParams = {
+ filters: {
+ status: ["active", "pending"],
+ category: {
+ type: "electronics",
+ subcategories: ["phones", "laptops"],
},
- sort: { field: "name", direction: "asc" },
},
- expected:
- "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
- },
- ];
-
- testCases.forEach(({ description, baseUrl, queryParams, expected }) => {
- it(description, () => {
- expect(createRequestUrl(baseUrl, queryParams)).toBe(expected);
- });
+ sort: { field: "name", direction: "asc" },
+ };
+ expect(createRequestUrl(baseUrl, queryParams)).toBe(
+ "https://api.example.com?filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
+ );
});
});
diff --git a/tests/unit/fetcher/getRequestBody.test.ts b/tests/unit/fetcher/getRequestBody.test.ts
index 8a6c3a5..e864c8b 100644
--- a/tests/unit/fetcher/getRequestBody.test.ts
+++ b/tests/unit/fetcher/getRequestBody.test.ts
@@ -2,117 +2,15 @@ import { getRequestBody } from "../../../src/core/fetcher/getRequestBody";
import { RUNTIME } from "../../../src/core/runtime";
describe("Test getRequestBody", () => {
- interface TestCase {
- description: string;
- input: any;
- type: "json" | "form" | "file" | "bytes" | "other";
- expected: any;
- skipCondition?: () => boolean;
- }
-
- const testCases: TestCase[] = [
- {
- description: "should stringify body if not FormData in Node environment",
- input: { key: "value" },
- type: "json",
- expected: '{"key":"value"}',
- skipCondition: () => RUNTIME.type !== "node",
- },
- {
- description: "should stringify body if not FormData in browser environment",
- input: { key: "value" },
- type: "json",
- expected: '{"key":"value"}',
- skipCondition: () => RUNTIME.type !== "browser",
- },
- {
- description: "should return the Uint8Array",
- input: new Uint8Array([1, 2, 3]),
- type: "bytes",
- expected: new Uint8Array([1, 2, 3]),
- },
- {
- description: "should serialize objects for form-urlencoded content type",
- input: { username: "johndoe", email: "john@example.com" },
- type: "form",
- expected: "username=johndoe&email=john%40example.com",
- },
- {
- description: "should serialize complex nested objects and arrays for form-urlencoded content type",
- input: {
- user: {
- profile: {
- name: "John Doe",
- settings: {
- theme: "dark",
- notifications: true,
- },
- },
- tags: ["admin", "user"],
- contacts: [
- { type: "email", value: "john@example.com" },
- { type: "phone", value: "+1234567890" },
- ],
- },
- filters: {
- status: ["active", "pending"],
- metadata: {
- created: "2024-01-01",
- categories: ["electronics", "books"],
- },
- },
- preferences: ["notifications", "updates"],
- },
- type: "form",
- expected:
- "user%5Bprofile%5D%5Bname%5D=John%20Doe&" +
- "user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark&" +
- "user%5Bprofile%5D%5Bsettings%5D%5Bnotifications%5D=true&" +
- "user%5Btags%5D=admin&" +
- "user%5Btags%5D=user&" +
- "user%5Bcontacts%5D%5Btype%5D=email&" +
- "user%5Bcontacts%5D%5Bvalue%5D=john%40example.com&" +
- "user%5Bcontacts%5D%5Btype%5D=phone&" +
- "user%5Bcontacts%5D%5Bvalue%5D=%2B1234567890&" +
- "filters%5Bstatus%5D=active&" +
- "filters%5Bstatus%5D=pending&" +
- "filters%5Bmetadata%5D%5Bcreated%5D=2024-01-01&" +
- "filters%5Bmetadata%5D%5Bcategories%5D=electronics&" +
- "filters%5Bmetadata%5D%5Bcategories%5D=books&" +
- "preferences=notifications&" +
- "preferences=updates",
- },
- {
- description: "should return the input for pre-serialized form-urlencoded strings",
- input: "key=value&another=param",
- type: "other",
- expected: "key=value&another=param",
- },
- {
- description: "should JSON stringify objects",
- input: { key: "value" },
- type: "json",
- expected: '{"key":"value"}',
- },
- ];
-
- testCases.forEach(({ description, input, type, expected, skipCondition }) => {
- it(description, async () => {
- if (skipCondition?.()) {
- return;
- }
-
+ it("should stringify body if not FormData in Node environment", async () => {
+ if (RUNTIME.type === "node") {
+ const body = { key: "value" };
const result = await getRequestBody({
- body: input,
- type,
+ body,
+ type: "json",
});
-
- if (input instanceof Uint8Array) {
- expect(result).toBe(input);
- } else {
- expect(result).toBe(expected);
- }
- });
+ expect(result).toBe('{"key":"value"}');
+ }
});
it("should return FormData in browser environment", async () => {
@@ -126,4 +24,42 @@ describe("Test getRequestBody", () => {
expect(result).toBe(formData);
}
});
+
+ it("should stringify body if not FormData in browser environment", async () => {
+ if (RUNTIME.type === "browser") {
+ const body = { key: "value" };
+ const result = await getRequestBody({
+ body,
+ type: "json",
+ });
+ expect(result).toBe('{"key":"value"}');
+ }
+ });
+
+ it("should return the Uint8Array", async () => {
+ const input = new Uint8Array([1, 2, 3]);
+ const result = await getRequestBody({
+ body: input,
+ type: "bytes",
+ });
+ expect(result).toBe(input);
+ });
+
+ it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => {
+ const input = "key=value&another=param";
+ const result = await getRequestBody({
+ body: input,
+ type: "other",
+ });
+ expect(result).toBe(input);
+ });
+
+ it("should JSON stringify objects", async () => {
+ const input = { key: "value" };
+ const result = await getRequestBody({
+ body: input,
+ type: "json",
+ });
+ expect(result).toBe('{"key":"value"}');
+ });
});
diff --git a/tests/unit/fetcher/getResponseBody.test.ts b/tests/unit/fetcher/getResponseBody.test.ts
index ad6be7f..400782f 100644
--- a/tests/unit/fetcher/getResponseBody.test.ts
+++ b/tests/unit/fetcher/getResponseBody.test.ts
@@ -1,61 +1,7 @@
-import { getResponseBody } from "../../../src/core/fetcher/getResponseBody";
-
import { RUNTIME } from "../../../src/core/runtime";
+import { getResponseBody } from "../../../src/core/fetcher/getResponseBody";
describe("Test getResponseBody", () => {
- interface SimpleTestCase {
- description: string;
- responseData: string | Record;
- responseType?: "blob" | "sse" | "streaming" | "text";
- expected: any;
- skipCondition?: () => boolean;
- }
-
- const simpleTestCases: SimpleTestCase[] = [
- {
- description: "should handle text response type",
- responseData: "test text",
- responseType: "text",
- expected: "test text",
- },
- {
- description: "should handle JSON response",
- responseData: { key: "value" },
- expected: { key: "value" },
- },
- {
- description: "should handle empty response",
- responseData: "",
- expected: undefined,
- },
- {
- description: "should handle non-JSON response",
- responseData: "invalid json",
- expected: {
- ok: false,
- error: {
- reason: "non-json",
- statusCode: 200,
- rawBody: "invalid json",
- },
- },
- },
- ];
-
- simpleTestCases.forEach(({ description, responseData, responseType, expected, skipCondition }) => {
- it(description, async () => {
- if (skipCondition?.()) {
- return;
- }
-
- const mockResponse = new Response(
- typeof responseData === "string" ? responseData : JSON.stringify(responseData),
- );
- const result = await getResponseBody(mockResponse, responseType);
- expect(result).toEqual(expected);
- });
- });
-
it("should handle blob response type", async () => {
const mockBlob = new Blob(["test"], { type: "text/plain" });
const mockResponse = new Response(mockBlob);
@@ -74,6 +20,7 @@ describe("Test getResponseBody", () => {
});
it("should handle streaming response type", async () => {
+ // Create a ReadableStream with some test data
const encoder = new TextEncoder();
const testData = "test stream data";
const mockStream = new ReadableStream({
@@ -88,10 +35,43 @@ describe("Test getResponseBody", () => {
expect(result).toBeInstanceOf(ReadableStream);
+ // Read and verify the stream content
const reader = result.getReader();
const decoder = new TextDecoder();
const { value } = await reader.read();
const streamContent = decoder.decode(value);
expect(streamContent).toBe(testData);
});
+
+ it("should handle text response type", async () => {
+ const mockResponse = new Response("test text");
+ const result = await getResponseBody(mockResponse, "text");
+ expect(result).toBe("test text");
+ });
+
+ it("should handle JSON response", async () => {
+ const mockJson = { key: "value" };
+ const mockResponse = new Response(JSON.stringify(mockJson));
+ const result = await getResponseBody(mockResponse);
+ expect(result).toEqual(mockJson);
+ });
+
+ it("should handle empty response", async () => {
+ const mockResponse = new Response("");
+ const result = await getResponseBody(mockResponse);
+ expect(result).toBeUndefined();
+ });
+
+ it("should handle non-JSON response", async () => {
+ const mockResponse = new Response("invalid json");
+ const result = await getResponseBody(mockResponse);
+ expect(result).toEqual({
+ ok: false,
+ error: {
+ reason: "non-json",
+ statusCode: 200,
+ rawBody: "invalid json",
+ },
+ });
+ });
});
diff --git a/tests/unit/fetcher/logging.test.ts b/tests/unit/fetcher/logging.test.ts
deleted file mode 100644
index 4fbac8e..0000000
--- a/tests/unit/fetcher/logging.test.ts
+++ /dev/null
@@ -1,517 +0,0 @@
-import { fetcherImpl } from "../../../src/core/fetcher/Fetcher";
-
-function createMockLogger() {
- return {
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- };
-}
-
-function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") {
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify(data), {
- status,
- statusText,
- }),
- );
-}
-
-function mockErrorResponse(data: unknown = { error: "Error" }, status = 404, statusText = "Not Found") {
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify(data), {
- status,
- statusText,
- }),
- );
-}
-
-describe("Fetcher Logging Integration", () => {
- describe("Request Logging", () => {
- it("should log successful request at debug level", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: { test: "data" },
- contentType: "application/json",
- requestType: "json",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- method: "POST",
- url: "https://example.com/api",
- headers: expect.objectContaining({
- "Content-Type": "application/json",
- }),
- hasBody: true,
- }),
- );
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "HTTP request succeeded",
- expect.objectContaining({
- method: "POST",
- url: "https://example.com/api",
- statusCode: 200,
- }),
- );
- });
-
- it("should not log debug messages at info level for successful requests", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "info",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).not.toHaveBeenCalled();
- });
-
- it("should log request with body flag", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "POST",
- body: { data: "test" },
- contentType: "application/json",
- requestType: "json",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- hasBody: true,
- }),
- );
- });
-
- it("should log request without body flag", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- hasBody: false,
- }),
- );
- });
-
- it("should not log when silent mode is enabled", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: true,
- },
- });
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).not.toHaveBeenCalled();
- expect(mockLogger.warn).not.toHaveBeenCalled();
- expect(mockLogger.error).not.toHaveBeenCalled();
- });
-
- it("should not log when no logging config is provided", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- });
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- });
- });
-
- describe("Error Logging", () => {
- it("should log 4xx errors at error level", async () => {
- const mockLogger = createMockLogger();
- mockErrorResponse({ error: "Not found" }, 404, "Not Found");
-
- const result = await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(result.ok).toBe(false);
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request failed with error status",
- expect.objectContaining({
- method: "GET",
- url: "https://example.com/api",
- statusCode: 404,
- }),
- );
- });
-
- it("should log 5xx errors at error level", async () => {
- const mockLogger = createMockLogger();
- mockErrorResponse({ error: "Internal error" }, 500, "Internal Server Error");
-
- const result = await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(result.ok).toBe(false);
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request failed with error status",
- expect.objectContaining({
- method: "GET",
- url: "https://example.com/api",
- statusCode: 500,
- }),
- );
- });
-
- it("should log aborted request errors", async () => {
- const mockLogger = createMockLogger();
-
- const abortController = new AbortController();
- abortController.abort();
-
- global.fetch = vi.fn().mockRejectedValue(new Error("Aborted"));
-
- const result = await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- abortSignal: abortController.signal,
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(result.ok).toBe(false);
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request was aborted",
- expect.objectContaining({
- method: "GET",
- url: "https://example.com/api",
- }),
- );
- });
-
- it("should log timeout errors", async () => {
- const mockLogger = createMockLogger();
-
- const timeoutError = new Error("Request timeout");
- timeoutError.name = "AbortError";
-
- global.fetch = vi.fn().mockRejectedValue(timeoutError);
-
- const result = await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(result.ok).toBe(false);
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request timed out",
- expect.objectContaining({
- method: "GET",
- url: "https://example.com/api",
- timeoutMs: undefined,
- }),
- );
- });
-
- it("should log unknown errors", async () => {
- const mockLogger = createMockLogger();
-
- const unknownError = new Error("Unknown error");
-
- global.fetch = vi.fn().mockRejectedValue(unknownError);
-
- const result = await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(result.ok).toBe(false);
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request failed with error",
- expect.objectContaining({
- method: "GET",
- url: "https://example.com/api",
- errorMessage: "Unknown error",
- }),
- );
- });
- });
-
- describe("Logging with Redaction", () => {
- it("should redact sensitive data in error logs", async () => {
- const mockLogger = createMockLogger();
- mockErrorResponse({ error: "Unauthorized" }, 401, "Unauthorized");
-
- await fetcherImpl({
- url: "https://example.com/api?api_key=secret",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request failed with error status",
- expect.objectContaining({
- url: "https://example.com/api?api_key=[REDACTED]",
- }),
- );
- });
- });
-
- describe("Different HTTP Methods", () => {
- it("should log GET requests", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- method: "GET",
- }),
- );
- });
-
- it("should log POST requests", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse({ data: "test" }, 201, "Created");
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "POST",
- body: { data: "test" },
- contentType: "application/json",
- requestType: "json",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- method: "POST",
- }),
- );
- });
-
- it("should log PUT requests", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "PUT",
- body: { data: "test" },
- contentType: "application/json",
- requestType: "json",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- method: "PUT",
- }),
- );
- });
-
- it("should log DELETE requests", async () => {
- const mockLogger = createMockLogger();
- global.fetch = vi.fn().mockResolvedValue(
- new Response(null, {
- status: 200,
- statusText: "OK",
- }),
- );
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "DELETE",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- method: "DELETE",
- }),
- );
- });
- });
-
- describe("Status Code Logging", () => {
- it("should log 2xx success status codes", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse({ data: "test" }, 201, "Created");
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "POST",
- body: { data: "test" },
- contentType: "application/json",
- requestType: "json",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "HTTP request succeeded",
- expect.objectContaining({
- statusCode: 201,
- }),
- );
- });
-
- it("should log 3xx redirect status codes as success", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse({ data: "test" }, 301, "Moved Permanently");
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "HTTP request succeeded",
- expect.objectContaining({
- statusCode: 301,
- }),
- );
- });
- });
-});
diff --git a/tests/unit/fetcher/redacting.test.ts b/tests/unit/fetcher/redacting.test.ts
deleted file mode 100644
index 9b9775b..0000000
--- a/tests/unit/fetcher/redacting.test.ts
+++ /dev/null
@@ -1,1115 +0,0 @@
-import { fetcherImpl } from "../../../src/core/fetcher/Fetcher";
-
-function createMockLogger() {
- return {
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- };
-}
-
-function mockSuccessResponse(data: unknown = { data: "test" }, status = 200, statusText = "OK") {
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify(data), {
- status,
- statusText,
- }),
- );
-}
-
-describe("Redacting Logic", () => {
- describe("Header Redaction", () => {
- it("should redact authorization header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { Authorization: "Bearer secret-token-12345" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- Authorization: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact api-key header (case-insensitive)", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "X-API-KEY": "secret-api-key" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "X-API-KEY": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact cookie header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { Cookie: "session=abc123; token=xyz789" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- Cookie: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact x-auth-token header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "x-auth-token": "auth-token-12345" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "x-auth-token": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact proxy-authorization header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "Proxy-Authorization": "Basic credentials" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "Proxy-Authorization": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact x-csrf-token header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "X-CSRF-Token": "csrf-token-abc" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "X-CSRF-Token": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact www-authenticate header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "WWW-Authenticate": "Bearer realm=example" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "WWW-Authenticate": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact x-session-token header", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: { "X-Session-Token": "session-token-xyz" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "X-Session-Token": "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should not redact non-sensitive headers", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- "User-Agent": "Test/1.0",
- Accept: "application/json",
- },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- "Content-Type": "application/json",
- "User-Agent": "Test/1.0",
- Accept: "application/json",
- }),
- }),
- );
- });
-
- it("should redact multiple sensitive headers at once", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- headers: {
- Authorization: "Bearer token",
- "X-API-Key": "api-key",
- Cookie: "session=123",
- "Content-Type": "application/json",
- },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- headers: expect.objectContaining({
- Authorization: "[REDACTED]",
- "X-API-Key": "[REDACTED]",
- Cookie: "[REDACTED]",
- "Content-Type": "application/json",
- }),
- }),
- );
- });
- });
-
- describe("Response Header Redaction", () => {
- it("should redact Set-Cookie in response headers", async () => {
- const mockLogger = createMockLogger();
-
- const mockHeaders = new Headers();
- mockHeaders.set("Set-Cookie", "session=abc123; HttpOnly; Secure");
- mockHeaders.set("Content-Type", "application/json");
-
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify({ data: "test" }), {
- status: 200,
- statusText: "OK",
- headers: mockHeaders,
- }),
- );
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "HTTP request succeeded",
- expect.objectContaining({
- responseHeaders: expect.objectContaining({
- "set-cookie": "[REDACTED]",
- "content-type": "application/json",
- }),
- }),
- );
- });
-
- it("should redact authorization in response headers", async () => {
- const mockLogger = createMockLogger();
-
- const mockHeaders = new Headers();
- mockHeaders.set("Authorization", "Bearer token-123");
- mockHeaders.set("Content-Type", "application/json");
-
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify({ data: "test" }), {
- status: 200,
- statusText: "OK",
- headers: mockHeaders,
- }),
- );
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "HTTP request succeeded",
- expect.objectContaining({
- responseHeaders: expect.objectContaining({
- authorization: "[REDACTED]",
- "content-type": "application/json",
- }),
- }),
- );
- });
-
- it("should redact response headers in error responses", async () => {
- const mockLogger = createMockLogger();
-
- const mockHeaders = new Headers();
- mockHeaders.set("WWW-Authenticate", "Bearer realm=example");
- mockHeaders.set("Content-Type", "application/json");
-
- global.fetch = vi.fn().mockResolvedValue(
- new Response(JSON.stringify({ error: "Unauthorized" }), {
- status: 401,
- statusText: "Unauthorized",
- headers: mockHeaders,
- }),
- );
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "error",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.error).toHaveBeenCalledWith(
- "HTTP request failed with error status",
- expect.objectContaining({
- responseHeaders: expect.objectContaining({
- "www-authenticate": "[REDACTED]",
- "content-type": "application/json",
- }),
- }),
- );
- });
- });
-
- describe("Query Parameter Redaction", () => {
- it("should redact api_key query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { api_key: "secret-key" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- api_key: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact token query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { token: "secret-token" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- token: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact access_token query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { access_token: "secret-access-token" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- access_token: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact password query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { password: "secret-password" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- password: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact secret query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { secret: "secret-value" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- secret: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should redact session_id query parameter", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { session_id: "session-123" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- session_id: "[REDACTED]",
- }),
- }),
- );
- });
-
- it("should not redact non-sensitive query parameters", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: {
- page: "1",
- limit: "10",
- sort: "name",
- },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- page: "1",
- limit: "10",
- sort: "name",
- }),
- }),
- );
- });
-
- it("should not redact parameters containing 'auth' substring like 'author'", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: {
- author: "john",
- authenticate: "false",
- authorization_level: "user",
- },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- author: "john",
- authenticate: "false",
- authorization_level: "user",
- }),
- }),
- );
- });
-
- it("should handle undefined query parameters", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: undefined,
- }),
- );
- });
-
- it("should redact case-insensitive query parameters", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- queryParameters: { API_KEY: "secret-key", Token: "secret-token" },
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- queryParameters: expect.objectContaining({
- API_KEY: "[REDACTED]",
- Token: "[REDACTED]",
- }),
- }),
- );
- });
- });
-
- describe("URL Redaction", () => {
- it("should redact credentials in URL", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://user:password@example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://[REDACTED]@example.com/api",
- }),
- );
- });
-
- it("should redact api_key in query string", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?api_key=secret-key&page=1",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?api_key=[REDACTED]&page=1",
- }),
- );
- });
-
- it("should redact token in query string", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?token=secret-token",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?token=[REDACTED]",
- }),
- );
- });
-
- it("should redact password in query string", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?username=user&password=secret",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?username=user&password=[REDACTED]",
- }),
- );
- });
-
- it("should not redact non-sensitive query strings", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?page=1&limit=10&sort=name",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?page=1&limit=10&sort=name",
- }),
- );
- });
-
- it("should not redact URL parameters containing 'auth' substring like 'author'", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?author=john&authenticate=false&page=1",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?author=john&authenticate=false&page=1",
- }),
- );
- });
-
- it("should handle URL with fragment", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?token=secret#section",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?token=[REDACTED]#section",
- }),
- );
- });
-
- it("should redact URL-encoded query parameters", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?api%5Fkey=secret",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?api%5Fkey=[REDACTED]",
- }),
- );
- });
-
- it("should handle URL without query string", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api",
- }),
- );
- });
-
- it("should handle empty query string", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?",
- }),
- );
- });
-
- it("should redact multiple sensitive parameters in URL", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?api_key=secret1&token=secret2&page=1",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?api_key=[REDACTED]&token=[REDACTED]&page=1",
- }),
- );
- });
-
- it("should redact both credentials and query parameters", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://user:pass@example.com/api?token=secret",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://[REDACTED]@example.com/api?token=[REDACTED]",
- }),
- );
- });
-
- it("should use fast path for URLs without sensitive keywords", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?page=1&limit=10&sort=name&filter=value",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?page=1&limit=10&sort=name&filter=value",
- }),
- );
- });
-
- it("should handle query parameter without value", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?flag&token=secret",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?flag&token=[REDACTED]",
- }),
- );
- });
-
- it("should handle URL with multiple @ symbols in credentials", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://user@example.com:pass@host.com/api",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://[REDACTED]@host.com/api",
- }),
- );
- });
-
- it("should handle URL with @ in query parameter but not in credentials", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://example.com/api?email=user@example.com",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://example.com/api?email=user@example.com",
- }),
- );
- });
-
- it("should handle URL with both credentials and @ in path", async () => {
- const mockLogger = createMockLogger();
- mockSuccessResponse();
-
- await fetcherImpl({
- url: "https://user:pass@example.com/users/@username",
- method: "GET",
- responseType: "json",
- maxRetries: 0,
- logging: {
- level: "debug",
- logger: mockLogger,
- silent: false,
- },
- });
-
- expect(mockLogger.debug).toHaveBeenCalledWith(
- "Making HTTP request",
- expect.objectContaining({
- url: "https://[REDACTED]@example.com/users/@username",
- }),
- );
- });
- });
-});
diff --git a/tests/unit/fetcher/requestWithRetries.test.ts b/tests/unit/fetcher/requestWithRetries.test.ts
index 978557f..7d46082 100644
--- a/tests/unit/fetcher/requestWithRetries.test.ts
+++ b/tests/unit/fetcher/requestWithRetries.test.ts
@@ -1,14 +1,15 @@
import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries";
describe("requestWithRetries", () => {
- let mockFetch: import("vi").Mock;
+ let mockFetch: import("vitest").Mock;
let originalMathRandom: typeof Math.random;
- let setTimeoutSpy: import("vi").MockInstance;
+ let setTimeoutSpy: import("vitest").MockInstance;
beforeEach(() => {
mockFetch = vi.fn();
originalMathRandom = Math.random;
+ // Mock Math.random for consistent jitter
Math.random = vi.fn(() => 0.5);
vi.useFakeTimers({
@@ -98,67 +99,6 @@ describe("requestWithRetries", () => {
}
});
- interface RetryHeaderTestCase {
- description: string;
- headerName: string;
- headerValue: string | (() => string);
- expectedDelayMin: number;
- expectedDelayMax: number;
- }
-
- const retryHeaderTests: RetryHeaderTestCase[] = [
- {
- description: "should respect retry-after header with seconds value",
- headerName: "retry-after",
- headerValue: "5",
- expectedDelayMin: 4000,
- expectedDelayMax: 6000,
- },
- {
- description: "should respect retry-after header with HTTP date value",
- headerName: "retry-after",
- headerValue: () => new Date(Date.now() + 3000).toUTCString(),
- expectedDelayMin: 2000,
- expectedDelayMax: 4000,
- },
- {
- description: "should respect x-ratelimit-reset header",
- headerName: "x-ratelimit-reset",
- headerValue: () => Math.floor((Date.now() + 4000) / 1000).toString(),
- expectedDelayMin: 3000,
- expectedDelayMax: 6000,
- },
- ];
-
- retryHeaderTests.forEach(({ description, headerName, headerValue, expectedDelayMin, expectedDelayMax }) => {
- it(description, async () => {
- setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
- process.nextTick(callback);
- return null as any;
- });
-
- const value = typeof headerValue === "function" ? headerValue() : headerValue;
- mockFetch
- .mockResolvedValueOnce(
- new Response("", {
- status: 429,
- headers: new Headers({ [headerName]: value }),
- }),
- )
- .mockResolvedValueOnce(new Response("", { status: 200 }));
-
- const responsePromise = requestWithRetries(() => mockFetch(), 1);
- await vi.runAllTimersAsync();
- const response = await responsePromise;
-
- expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number));
- const actualDelay = setTimeoutSpy.mock.calls[0][1];
- expect(actualDelay).toBeGreaterThan(expectedDelayMin);
- expect(actualDelay).toBeLessThan(expectedDelayMax);
- expect(response.status).toBe(200);
- });
- });
-
it("should apply correct exponential backoff with jitter", async () => {
setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
process.nextTick(callback);
@@ -173,6 +113,7 @@ describe("requestWithRetries", () => {
await vi.runAllTimersAsync();
await responsePromise;
+ // Verify setTimeout calls
expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length);
expectedDelays.forEach((delay, index) => {
@@ -204,6 +145,85 @@ describe("requestWithRetries", () => {
expect(response2.status).toBe(200);
});
+ it("should respect retry-after header with seconds value", async () => {
+ setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
+ process.nextTick(callback);
+ return null as any;
+ });
+
+ mockFetch
+ .mockResolvedValueOnce(
+ new Response("", {
+ status: 429,
+ headers: new Headers({ "retry-after": "5" }),
+ }),
+ )
+ .mockResolvedValueOnce(new Response("", { status: 200 }));
+
+ const responsePromise = requestWithRetries(() => mockFetch(), 1);
+ await vi.runAllTimersAsync();
+ const response = await responsePromise;
+
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000); // 5 seconds = 5000ms
+ expect(response.status).toBe(200);
+ });
+
+ it("should respect retry-after header with HTTP date value", async () => {
+ setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
+ process.nextTick(callback);
+ return null as any;
+ });
+
+ const futureDate = new Date(Date.now() + 3000); // 3 seconds from now
+ mockFetch
+ .mockResolvedValueOnce(
+ new Response("", {
+ status: 429,
+ headers: new Headers({ "retry-after": futureDate.toUTCString() }),
+ }),
+ )
+ .mockResolvedValueOnce(new Response("", { status: 200 }));
+
+ const responsePromise = requestWithRetries(() => mockFetch(), 1);
+ await vi.runAllTimersAsync();
+ const response = await responsePromise;
+
+ // Should use the date-based delay (approximately 3000ms, but with jitter)
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number));
+ const actualDelay = setTimeoutSpy.mock.calls[0][1];
+ expect(actualDelay).toBeGreaterThan(2000);
+ expect(actualDelay).toBeLessThan(4000);
+ expect(response.status).toBe(200);
+ });
+
+ it("should respect x-ratelimit-reset header", async () => {
+ setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
+ process.nextTick(callback);
+ return null as any;
+ });
+
+ const resetTime = Math.floor((Date.now() + 4000) / 1000); // 4 seconds from now in Unix timestamp
+ mockFetch
+ .mockResolvedValueOnce(
+ new Response("", {
+ status: 429,
+ headers: new Headers({ "x-ratelimit-reset": resetTime.toString() }),
+ }),
+ )
+ .mockResolvedValueOnce(new Response("", { status: 200 }));
+
+ const responsePromise = requestWithRetries(() => mockFetch(), 1);
+ await vi.runAllTimersAsync();
+ const response = await responsePromise;
+
+ // Should use the x-ratelimit-reset delay (approximately 4000ms, but with positive jitter)
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), expect.any(Number));
+ const actualDelay = setTimeoutSpy.mock.calls[0][1];
+ expect(actualDelay).toBeGreaterThan(3000);
+ expect(actualDelay).toBeLessThan(6000);
+ expect(response.status).toBe(200);
+ });
+
it("should cap delay at MAX_RETRY_DELAY for large header values", async () => {
setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => {
process.nextTick(callback);
@@ -223,7 +243,8 @@ describe("requestWithRetries", () => {
await vi.runAllTimersAsync();
const response = await responsePromise;
- expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000);
+ // Should be capped at MAX_RETRY_DELAY (60000ms) with jitter applied
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 60000); // Exactly MAX_RETRY_DELAY since jitter with 0.5 random keeps it at 60000
expect(response.status).toBe(200);
});
});
diff --git a/tests/unit/logging/logger.test.ts b/tests/unit/logging/logger.test.ts
deleted file mode 100644
index 2e0b5fe..0000000
--- a/tests/unit/logging/logger.test.ts
+++ /dev/null
@@ -1,454 +0,0 @@
-import { ConsoleLogger, createLogger, Logger, LogLevel } from "../../../src/core/logging/logger";
-
-function createMockLogger() {
- return {
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- };
-}
-
-describe("Logger", () => {
- describe("LogLevel", () => {
- it("should have correct log levels", () => {
- expect(LogLevel.Debug).toBe("debug");
- expect(LogLevel.Info).toBe("info");
- expect(LogLevel.Warn).toBe("warn");
- expect(LogLevel.Error).toBe("error");
- });
- });
-
- describe("ConsoleLogger", () => {
- let consoleLogger: ConsoleLogger;
- let consoleSpy: {
- debug: ReturnType;
- info: ReturnType;
- warn: ReturnType;
- error: ReturnType;
- };
-
- beforeEach(() => {
- consoleLogger = new ConsoleLogger();
- consoleSpy = {
- debug: vi.spyOn(console, "debug").mockImplementation(() => {}),
- info: vi.spyOn(console, "info").mockImplementation(() => {}),
- warn: vi.spyOn(console, "warn").mockImplementation(() => {}),
- error: vi.spyOn(console, "error").mockImplementation(() => {}),
- };
- });
-
- afterEach(() => {
- consoleSpy.debug.mockRestore();
- consoleSpy.info.mockRestore();
- consoleSpy.warn.mockRestore();
- consoleSpy.error.mockRestore();
- });
-
- it("should log debug messages", () => {
- consoleLogger.debug("debug message", { data: "test" });
- expect(consoleSpy.debug).toHaveBeenCalledWith("debug message", { data: "test" });
- });
-
- it("should log info messages", () => {
- consoleLogger.info("info message", { data: "test" });
- expect(consoleSpy.info).toHaveBeenCalledWith("info message", { data: "test" });
- });
-
- it("should log warn messages", () => {
- consoleLogger.warn("warn message", { data: "test" });
- expect(consoleSpy.warn).toHaveBeenCalledWith("warn message", { data: "test" });
- });
-
- it("should log error messages", () => {
- consoleLogger.error("error message", { data: "test" });
- expect(consoleSpy.error).toHaveBeenCalledWith("error message", { data: "test" });
- });
-
- it("should handle multiple arguments", () => {
- consoleLogger.debug("message", "arg1", "arg2", { key: "value" });
- expect(consoleSpy.debug).toHaveBeenCalledWith("message", "arg1", "arg2", { key: "value" });
- });
- });
-
- describe("Logger with level filtering", () => {
- let mockLogger: {
- debug: ReturnType;
- info: ReturnType;
- warn: ReturnType;
- error: ReturnType;
- };
-
- beforeEach(() => {
- mockLogger = createMockLogger();
- });
-
- describe("Debug level", () => {
- it("should log all levels when set to debug", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("debug");
- logger.info("info");
- logger.warn("warn");
- logger.error("error");
-
- expect(mockLogger.debug).toHaveBeenCalledWith("debug");
- expect(mockLogger.info).toHaveBeenCalledWith("info");
- expect(mockLogger.warn).toHaveBeenCalledWith("warn");
- expect(mockLogger.error).toHaveBeenCalledWith("error");
- });
-
- it("should report correct level checks", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger.isDebug()).toBe(true);
- expect(logger.isInfo()).toBe(true);
- expect(logger.isWarn()).toBe(true);
- expect(logger.isError()).toBe(true);
- });
- });
-
- describe("Info level", () => {
- it("should log info, warn, and error when set to info", () => {
- const logger = new Logger({
- level: LogLevel.Info,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("debug");
- logger.info("info");
- logger.warn("warn");
- logger.error("error");
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).toHaveBeenCalledWith("info");
- expect(mockLogger.warn).toHaveBeenCalledWith("warn");
- expect(mockLogger.error).toHaveBeenCalledWith("error");
- });
-
- it("should report correct level checks", () => {
- const logger = new Logger({
- level: LogLevel.Info,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger.isDebug()).toBe(false);
- expect(logger.isInfo()).toBe(true);
- expect(logger.isWarn()).toBe(true);
- expect(logger.isError()).toBe(true);
- });
- });
-
- describe("Warn level", () => {
- it("should log warn and error when set to warn", () => {
- const logger = new Logger({
- level: LogLevel.Warn,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("debug");
- logger.info("info");
- logger.warn("warn");
- logger.error("error");
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).not.toHaveBeenCalled();
- expect(mockLogger.warn).toHaveBeenCalledWith("warn");
- expect(mockLogger.error).toHaveBeenCalledWith("error");
- });
-
- it("should report correct level checks", () => {
- const logger = new Logger({
- level: LogLevel.Warn,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger.isDebug()).toBe(false);
- expect(logger.isInfo()).toBe(false);
- expect(logger.isWarn()).toBe(true);
- expect(logger.isError()).toBe(true);
- });
- });
-
- describe("Error level", () => {
- it("should only log error when set to error", () => {
- const logger = new Logger({
- level: LogLevel.Error,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("debug");
- logger.info("info");
- logger.warn("warn");
- logger.error("error");
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).not.toHaveBeenCalled();
- expect(mockLogger.warn).not.toHaveBeenCalled();
- expect(mockLogger.error).toHaveBeenCalledWith("error");
- });
-
- it("should report correct level checks", () => {
- const logger = new Logger({
- level: LogLevel.Error,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger.isDebug()).toBe(false);
- expect(logger.isInfo()).toBe(false);
- expect(logger.isWarn()).toBe(false);
- expect(logger.isError()).toBe(true);
- });
- });
-
- describe("Silent mode", () => {
- it("should not log anything when silent is true", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: true,
- });
-
- logger.debug("debug");
- logger.info("info");
- logger.warn("warn");
- logger.error("error");
-
- expect(mockLogger.debug).not.toHaveBeenCalled();
- expect(mockLogger.info).not.toHaveBeenCalled();
- expect(mockLogger.warn).not.toHaveBeenCalled();
- expect(mockLogger.error).not.toHaveBeenCalled();
- });
-
- it("should report all level checks as false when silent", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: true,
- });
-
- expect(logger.isDebug()).toBe(false);
- expect(logger.isInfo()).toBe(false);
- expect(logger.isWarn()).toBe(false);
- expect(logger.isError()).toBe(false);
- });
- });
-
- describe("shouldLog", () => {
- it("should correctly determine if level should be logged", () => {
- const logger = new Logger({
- level: LogLevel.Info,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger.shouldLog(LogLevel.Debug)).toBe(false);
- expect(logger.shouldLog(LogLevel.Info)).toBe(true);
- expect(logger.shouldLog(LogLevel.Warn)).toBe(true);
- expect(logger.shouldLog(LogLevel.Error)).toBe(true);
- });
-
- it("should return false for all levels when silent", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: true,
- });
-
- expect(logger.shouldLog(LogLevel.Debug)).toBe(false);
- expect(logger.shouldLog(LogLevel.Info)).toBe(false);
- expect(logger.shouldLog(LogLevel.Warn)).toBe(false);
- expect(logger.shouldLog(LogLevel.Error)).toBe(false);
- });
- });
-
- describe("Multiple arguments", () => {
- it("should pass multiple arguments to logger", () => {
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("message", "arg1", { key: "value" }, 123);
- expect(mockLogger.debug).toHaveBeenCalledWith("message", "arg1", { key: "value" }, 123);
- });
- });
- });
-
- describe("createLogger", () => {
- it("should return default logger when no config provided", () => {
- const logger = createLogger();
- expect(logger).toBeInstanceOf(Logger);
- });
-
- it("should return same logger instance when Logger is passed", () => {
- const customLogger = new Logger({
- level: LogLevel.Debug,
- logger: new ConsoleLogger(),
- silent: false,
- });
-
- const result = createLogger(customLogger);
- expect(result).toBe(customLogger);
- });
-
- it("should create logger with custom config", () => {
- const mockLogger = createMockLogger();
-
- const logger = createLogger({
- level: LogLevel.Warn,
- logger: mockLogger,
- silent: false,
- });
-
- expect(logger).toBeInstanceOf(Logger);
- logger.warn("test");
- expect(mockLogger.warn).toHaveBeenCalledWith("test");
- });
-
- it("should use default values for missing config", () => {
- const logger = createLogger({});
- expect(logger).toBeInstanceOf(Logger);
- });
-
- it("should override default level", () => {
- const mockLogger = createMockLogger();
-
- const logger = createLogger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("test");
- expect(mockLogger.debug).toHaveBeenCalledWith("test");
- });
-
- it("should override default silent mode", () => {
- const mockLogger = createMockLogger();
-
- const logger = createLogger({
- logger: mockLogger,
- silent: false,
- });
-
- logger.info("test");
- expect(mockLogger.info).toHaveBeenCalledWith("test");
- });
-
- it("should use provided logger implementation", () => {
- const customLogger = createMockLogger();
-
- const logger = createLogger({
- logger: customLogger,
- level: LogLevel.Debug,
- silent: false,
- });
-
- logger.debug("test");
- expect(customLogger.debug).toHaveBeenCalledWith("test");
- });
-
- it("should default to silent: true", () => {
- const mockLogger = createMockLogger();
-
- const logger = createLogger({
- logger: mockLogger,
- level: LogLevel.Debug,
- });
-
- logger.debug("test");
- expect(mockLogger.debug).not.toHaveBeenCalled();
- });
- });
-
- describe("Default logger", () => {
- it("should have silent: true by default", () => {
- const logger = createLogger();
- expect(logger.shouldLog(LogLevel.Info)).toBe(false);
- });
-
- it("should not log when using default logger", () => {
- const logger = createLogger();
-
- logger.info("test");
- expect(logger.isInfo()).toBe(false);
- });
- });
-
- describe("Edge cases", () => {
- it("should handle empty message", () => {
- const mockLogger = createMockLogger();
-
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("");
- expect(mockLogger.debug).toHaveBeenCalledWith("");
- });
-
- it("should handle no arguments", () => {
- const mockLogger = createMockLogger();
-
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- logger.debug("message");
- expect(mockLogger.debug).toHaveBeenCalledWith("message");
- });
-
- it("should handle complex objects", () => {
- const mockLogger = createMockLogger();
-
- const logger = new Logger({
- level: LogLevel.Debug,
- logger: mockLogger,
- silent: false,
- });
-
- const complexObject = {
- nested: { key: "value" },
- array: [1, 2, 3],
- fn: () => "test",
- };
-
- logger.debug("message", complexObject);
- expect(mockLogger.debug).toHaveBeenCalledWith("message", complexObject);
- });
-
- it("should handle errors as arguments", () => {
- const mockLogger = createMockLogger();
-
- const logger = new Logger({
- level: LogLevel.Error,
- logger: mockLogger,
- silent: false,
- });
-
- const error = new Error("Test error");
- logger.error("Error occurred", error);
- expect(mockLogger.error).toHaveBeenCalledWith("Error occurred", error);
- });
- });
-});
diff --git a/tests/unit/url/join.test.ts b/tests/unit/url/join.test.ts
index 123488f..984cfe6 100644
--- a/tests/unit/url/join.test.ts
+++ b/tests/unit/url/join.test.ts
@@ -1,223 +1,88 @@
import { join } from "../../../src/core/url/index";
describe("join", () => {
- interface TestCase {
- description: string;
- base: string;
- segments: string[];
- expected: string;
- }
-
describe("basic functionality", () => {
- const basicTests: TestCase[] = [
- { description: "should return empty string for empty base", base: "", segments: [], expected: "" },
- {
- description: "should return empty string for empty base with path",
- base: "",
- segments: ["path"],
- expected: "",
- },
- {
- description: "should handle single segment",
- base: "base",
- segments: ["segment"],
- expected: "base/segment",
- },
- {
- description: "should handle single segment with trailing slash on base",
- base: "base/",
- segments: ["segment"],
- expected: "base/segment",
- },
- {
- description: "should handle single segment with leading slash",
- base: "base",
- segments: ["/segment"],
- expected: "base/segment",
- },
- {
- description: "should handle single segment with both slashes",
- base: "base/",
- segments: ["/segment"],
- expected: "base/segment",
- },
- {
- description: "should handle multiple segments",
- base: "base",
- segments: ["path1", "path2", "path3"],
- expected: "base/path1/path2/path3",
- },
- {
- description: "should handle multiple segments with slashes",
- base: "base/",
- segments: ["/path1/", "/path2/", "/path3/"],
- expected: "base/path1/path2/path3/",
- },
- ];
+ it("should return empty string for empty base", () => {
+ expect(join("")).toBe("");
+ expect(join("", "path")).toBe("");
+ });
+
+ it("should handle single segment", () => {
+ expect(join("base", "segment")).toBe("base/segment");
+ expect(join("base/", "segment")).toBe("base/segment");
+ expect(join("base", "/segment")).toBe("base/segment");
+ expect(join("base/", "/segment")).toBe("base/segment");
+ });
- basicTests.forEach(({ description, base, segments, expected }) => {
- it(description, () => {
- expect(join(base, ...segments)).toBe(expected);
- });
+ it("should handle multiple segments", () => {
+ expect(join("base", "path1", "path2", "path3")).toBe("base/path1/path2/path3");
+ expect(join("base/", "/path1/", "/path2/", "/path3/")).toBe("base/path1/path2/path3/");
});
});
describe("URL handling", () => {
- const urlTests: TestCase[] = [
- {
- description: "should handle absolute URLs",
- base: "https://example.com",
- segments: ["api", "v1"],
- expected: "https://example.com/api/v1",
- },
- {
- description: "should handle absolute URLs with slashes",
- base: "https://example.com/",
- segments: ["/api/", "/v1/"],
- expected: "https://example.com/api/v1/",
- },
- {
- description: "should handle absolute URLs with base path",
- base: "https://example.com/base",
- segments: ["api", "v1"],
- expected: "https://example.com/base/api/v1",
- },
- {
- description: "should preserve URL query parameters",
- base: "https://example.com?query=1",
- segments: ["api"],
- expected: "https://example.com/api?query=1",
- },
- {
- description: "should preserve URL fragments",
- base: "https://example.com#fragment",
- segments: ["api"],
- expected: "https://example.com/api#fragment",
- },
- {
- description: "should preserve URL query and fragments",
- base: "https://example.com?query=1#fragment",
- segments: ["api"],
- expected: "https://example.com/api?query=1#fragment",
- },
- {
- description: "should handle http protocol",
- base: "http://example.com",
- segments: ["api"],
- expected: "http://example.com/api",
- },
- {
- description: "should handle ftp protocol",
- base: "ftp://example.com",
- segments: ["files"],
- expected: "ftp://example.com/files",
- },
- {
- description: "should handle ws protocol",
- base: "ws://example.com",
- segments: ["socket"],
- expected: "ws://example.com/socket",
- },
- {
- description: "should fallback to path joining for malformed URLs",
- base: "not-a-url://",
- segments: ["path"],
- expected: "not-a-url:///path",
- },
- ];
+ it("should handle absolute URLs", () => {
+ expect(join("https://example.com", "api", "v1")).toBe("https://example.com/api/v1");
+ expect(join("https://example.com/", "/api/", "/v1/")).toBe("https://example.com/api/v1/");
+ expect(join("https://example.com/base", "api", "v1")).toBe("https://example.com/base/api/v1");
+ });
- urlTests.forEach(({ description, base, segments, expected }) => {
- it(description, () => {
- expect(join(base, ...segments)).toBe(expected);
- });
+ it("should preserve URL query parameters and fragments", () => {
+ expect(join("https://example.com?query=1", "api")).toBe("https://example.com/api?query=1");
+ expect(join("https://example.com#fragment", "api")).toBe("https://example.com/api#fragment");
+ expect(join("https://example.com?query=1#fragment", "api")).toBe(
+ "https://example.com/api?query=1#fragment",
+ );
+ });
+
+ it("should handle different protocols", () => {
+ expect(join("http://example.com", "api")).toBe("http://example.com/api");
+ expect(join("ftp://example.com", "files")).toBe("ftp://example.com/files");
+ expect(join("ws://example.com", "socket")).toBe("ws://example.com/socket");
+ });
+
+ it("should fallback to path joining for malformed URLs", () => {
+ expect(join("not-a-url://", "path")).toBe("not-a-url:///path");
});
});
describe("edge cases", () => {
- const edgeCaseTests: TestCase[] = [
- {
- description: "should handle empty segments",
- base: "base",
- segments: ["", "path"],
- expected: "base/path",
- },
- {
- description: "should handle null segments",
- base: "base",
- segments: [null as any, "path"],
- expected: "base/path",
- },
- {
- description: "should handle undefined segments",
- base: "base",
- segments: [undefined as any, "path"],
- expected: "base/path",
- },
- {
- description: "should handle segments with only single slash",
- base: "base",
- segments: ["/", "path"],
- expected: "base/path",
- },
- {
- description: "should handle segments with only double slash",
- base: "base",
- segments: ["//", "path"],
- expected: "base/path",
- },
- {
- description: "should handle base paths with trailing slashes",
- base: "base/",
- segments: ["path"],
- expected: "base/path",
- },
- {
- description: "should handle complex nested paths",
- base: "api/v1/",
- segments: ["/users/", "/123/", "/profile"],
- expected: "api/v1/users/123/profile",
- },
- ];
+ it("should handle empty segments", () => {
+ expect(join("base", "", "path")).toBe("base/path");
+ expect(join("base", null as any, "path")).toBe("base/path");
+ expect(join("base", undefined as any, "path")).toBe("base/path");
+ });
+
+ it("should handle segments with only slashes", () => {
+ expect(join("base", "/", "path")).toBe("base/path");
+ expect(join("base", "//", "path")).toBe("base/path");
+ });
+
+ it("should handle base paths with trailing slashes", () => {
+ expect(join("base/", "path")).toBe("base/path");
+ });
- edgeCaseTests.forEach(({ description, base, segments, expected }) => {
- it(description, () => {
- expect(join(base, ...segments)).toBe(expected);
- });
+ it("should handle complex nested paths", () => {
+ expect(join("api/v1/", "/users/", "/123/", "/profile")).toBe("api/v1/users/123/profile");
});
});
describe("real-world scenarios", () => {
- const realWorldTests: TestCase[] = [
- {
- description: "should handle API endpoint construction",
- base: "https://api.example.com/v1",
- segments: ["users", "123", "posts"],
- expected: "https://api.example.com/v1/users/123/posts",
- },
- {
- description: "should handle file path construction",
- base: "/var/www",
- segments: ["html", "assets", "images"],
- expected: "/var/www/html/assets/images",
- },
- {
- description: "should handle relative path construction",
- base: "../parent",
- segments: ["child", "grandchild"],
- expected: "../parent/child/grandchild",
- },
- {
- description: "should handle Windows-style paths",
- base: "C:\\Users",
- segments: ["Documents", "file.txt"],
- expected: "C:\\Users/Documents/file.txt",
- },
- ];
+ it("should handle API endpoint construction", () => {
+ const baseUrl = "https://api.example.com/v1";
+ expect(join(baseUrl, "users", "123", "posts")).toBe("https://api.example.com/v1/users/123/posts");
+ });
+
+ it("should handle file path construction", () => {
+ expect(join("/var/www", "html", "assets", "images")).toBe("/var/www/html/assets/images");
+ });
- realWorldTests.forEach(({ description, base, segments, expected }) => {
- it(description, () => {
- expect(join(base, ...segments)).toBe(expected);
- });
+ it("should handle relative path construction", () => {
+ expect(join("../parent", "child", "grandchild")).toBe("../parent/child/grandchild");
+ });
+
+ it("should handle Windows-style paths", () => {
+ expect(join("C:\\Users", "Documents", "file.txt")).toBe("C:\\Users/Documents/file.txt");
});
});
@@ -225,7 +90,7 @@ describe("join", () => {
it("should handle many segments efficiently", () => {
const segments = Array(100).fill("segment");
const result = join("base", ...segments);
- expect(result).toBe(`base/${segments.join("/")}`);
+ expect(result).toBe("base/" + segments.join("/"));
});
it("should handle long URLs", () => {
@@ -235,50 +100,21 @@ describe("join", () => {
});
describe("trailing slash preservation", () => {
- const trailingSlashTests: TestCase[] = [
- {
- description:
- "should preserve trailing slash on final result when base has trailing slash and no segments",
- base: "https://api.example.com/",
- segments: [],
- expected: "https://api.example.com/",
- },
- {
- description: "should preserve trailing slash on v1 path",
- base: "https://api.example.com/v1/",
- segments: [],
- expected: "https://api.example.com/v1/",
- },
- {
- description: "should preserve trailing slash when last segment has trailing slash",
- base: "https://api.example.com",
- segments: ["users/"],
- expected: "https://api.example.com/users/",
- },
- {
- description: "should preserve trailing slash with relative path",
- base: "api/v1",
- segments: ["users/"],
- expected: "api/v1/users/",
- },
- {
- description: "should preserve trailing slash with multiple segments",
- base: "https://api.example.com",
- segments: ["v1", "collections/"],
- expected: "https://api.example.com/v1/collections/",
- },
- {
- description: "should preserve trailing slash with base path",
- base: "base",
- segments: ["path1", "path2/"],
- expected: "base/path1/path2/",
- },
- ];
+ it("should preserve trailing slash on final result when base has trailing slash and no segments", () => {
+ expect(join("https://api.example.com/")).toBe("https://api.example.com/");
+ expect(join("https://api.example.com/v1/")).toBe("https://api.example.com/v1/");
+ });
+
+ it("should preserve trailing slash when last segment has trailing slash", () => {
+ expect(join("https://api.example.com", "users/")).toBe("https://api.example.com/users/");
+ expect(join("api/v1", "users/")).toBe("api/v1/users/");
+ });
- trailingSlashTests.forEach(({ description, base, segments, expected }) => {
- it(description, () => {
- expect(join(base, ...segments)).toBe(expected);
- });
+ it("should preserve trailing slash with multiple segments where last has trailing slash", () => {
+ expect(join("https://api.example.com", "v1", "collections/")).toBe(
+ "https://api.example.com/v1/collections/",
+ );
+ expect(join("base", "path1", "path2/")).toBe("base/path1/path2/");
});
});
});
diff --git a/tests/unit/url/qs.test.ts b/tests/unit/url/qs.test.ts
index 42cdffb..80e7e04 100644
--- a/tests/unit/url/qs.test.ts
+++ b/tests/unit/url/qs.test.ts
@@ -1,278 +1,187 @@
import { toQueryString } from "../../../src/core/url/index";
describe("Test qs toQueryString", () => {
- interface BasicTestCase {
- description: string;
- input: any;
- expected: string;
- }
-
describe("Basic functionality", () => {
- const basicTests: BasicTestCase[] = [
- { description: "should return empty string for null", input: null, expected: "" },
- { description: "should return empty string for undefined", input: undefined, expected: "" },
- { description: "should return empty string for string primitive", input: "hello", expected: "" },
- { description: "should return empty string for number primitive", input: 42, expected: "" },
- { description: "should return empty string for true boolean", input: true, expected: "" },
- { description: "should return empty string for false boolean", input: false, expected: "" },
- { description: "should handle empty objects", input: {}, expected: "" },
- {
- description: "should handle simple key-value pairs",
- input: { name: "John", age: 30 },
- expected: "name=John&age=30",
- },
- ];
+ it("should return empty string for null/undefined", () => {
+ expect(toQueryString(null)).toBe("");
+ expect(toQueryString(undefined)).toBe("");
+ });
- basicTests.forEach(({ description, input, expected }) => {
- it(description, () => {
- expect(toQueryString(input)).toBe(expected);
- });
+ it("should return empty string for primitive values", () => {
+ expect(toQueryString("hello")).toBe("");
+ expect(toQueryString(42)).toBe("");
+ expect(toQueryString(true)).toBe("");
+ expect(toQueryString(false)).toBe("");
+ });
+
+ it("should handle empty objects", () => {
+ expect(toQueryString({})).toBe("");
+ });
+
+ it("should handle simple key-value pairs", () => {
+ const obj = { name: "John", age: 30 };
+ expect(toQueryString(obj)).toBe("name=John&age=30");
});
});
describe("Array handling", () => {
- interface ArrayTestCase {
- description: string;
- input: any;
- options?: { arrayFormat?: "repeat" | "indices" };
- expected: string;
- }
+ it("should handle arrays with indices format (default)", () => {
+ const obj = { items: ["a", "b", "c"] };
+ expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c");
+ });
+
+ it("should handle arrays with repeat format", () => {
+ const obj = { items: ["a", "b", "c"] };
+ expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe("items=a&items=b&items=c");
+ });
- const arrayTests: ArrayTestCase[] = [
- {
- description: "should handle arrays with indices format (default)",
- input: { items: ["a", "b", "c"] },
- expected: "items%5B0%5D=a&items%5B1%5D=b&items%5B2%5D=c",
- },
- {
- description: "should handle arrays with repeat format",
- input: { items: ["a", "b", "c"] },
- options: { arrayFormat: "repeat" },
- expected: "items=a&items=b&items=c",
- },
- {
- description: "should handle empty arrays",
- input: { items: [] },
- expected: "",
- },
- {
- description: "should handle arrays with mixed types",
- input: { mixed: ["string", 42, true, false] },
- expected: "mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false",
- },
- {
- description: "should handle arrays with objects",
- input: { users: [{ name: "John" }, { name: "Jane" }] },
- expected: "users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane",
- },
- {
- description: "should handle arrays with objects in repeat format",
- input: { users: [{ name: "John" }, { name: "Jane" }] },
- options: { arrayFormat: "repeat" },
- expected: "users%5Bname%5D=John&users%5Bname%5D=Jane",
- },
- ];
+ it("should handle empty arrays", () => {
+ const obj = { items: [] };
+ expect(toQueryString(obj)).toBe("");
+ });
+
+ it("should handle arrays with mixed types", () => {
+ const obj = { mixed: ["string", 42, true, false] };
+ expect(toQueryString(obj)).toBe("mixed%5B0%5D=string&mixed%5B1%5D=42&mixed%5B2%5D=true&mixed%5B3%5D=false");
+ });
+
+ it("should handle arrays with objects", () => {
+ const obj = { users: [{ name: "John" }, { name: "Jane" }] };
+ expect(toQueryString(obj)).toBe("users%5B0%5D%5Bname%5D=John&users%5B1%5D%5Bname%5D=Jane");
+ });
- arrayTests.forEach(({ description, input, options, expected }) => {
- it(description, () => {
- expect(toQueryString(input, options)).toBe(expected);
- });
+ it("should handle arrays with objects in repeat format", () => {
+ const obj = { users: [{ name: "John" }, { name: "Jane" }] };
+ expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe("users%5Bname%5D=John&users%5Bname%5D=Jane");
});
});
describe("Nested objects", () => {
- const nestedTests: BasicTestCase[] = [
- {
- description: "should handle nested objects",
- input: { user: { name: "John", age: 30 } },
- expected: "user%5Bname%5D=John&user%5Bage%5D=30",
- },
- {
- description: "should handle deeply nested objects",
- input: { user: { profile: { name: "John", settings: { theme: "dark" } } } },
- expected: "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark",
- },
- {
- description: "should handle empty nested objects",
- input: { user: {} },
- expected: "",
- },
- ];
+ it("should handle nested objects", () => {
+ const obj = { user: { name: "John", age: 30 } };
+ expect(toQueryString(obj)).toBe("user%5Bname%5D=John&user%5Bage%5D=30");
+ });
+
+ it("should handle deeply nested objects", () => {
+ const obj = { user: { profile: { name: "John", settings: { theme: "dark" } } } };
+ expect(toQueryString(obj)).toBe(
+ "user%5Bprofile%5D%5Bname%5D=John&user%5Bprofile%5D%5Bsettings%5D%5Btheme%5D=dark",
+ );
+ });
- nestedTests.forEach(({ description, input, expected }) => {
- it(description, () => {
- expect(toQueryString(input)).toBe(expected);
- });
+ it("should handle empty nested objects", () => {
+ const obj = { user: {} };
+ expect(toQueryString(obj)).toBe("");
});
});
describe("Encoding", () => {
- interface EncodingTestCase {
- description: string;
- input: any;
- options?: { encode?: boolean };
- expected: string;
- }
+ it("should encode by default", () => {
+ const obj = { name: "John Doe", email: "john@example.com" };
+ expect(toQueryString(obj)).toBe("name=John%20Doe&email=john%40example.com");
+ });
- const encodingTests: EncodingTestCase[] = [
- {
- description: "should encode by default",
- input: { name: "John Doe", email: "john@example.com" },
- expected: "name=John%20Doe&email=john%40example.com",
- },
- {
- description: "should not encode when encode is false",
- input: { name: "John Doe", email: "john@example.com" },
- options: { encode: false },
- expected: "name=John Doe&email=john@example.com",
- },
- {
- description: "should encode special characters in keys",
- input: { "user name": "John", "email[primary]": "john@example.com" },
- expected: "user%20name=John&email%5Bprimary%5D=john%40example.com",
- },
- {
- description: "should not encode special characters in keys when encode is false",
- input: { "user name": "John", "email[primary]": "john@example.com" },
- options: { encode: false },
- expected: "user name=John&email[primary]=john@example.com",
- },
- ];
+ it("should not encode when encode is false", () => {
+ const obj = { name: "John Doe", email: "john@example.com" };
+ expect(toQueryString(obj, { encode: false })).toBe("name=John Doe&email=john@example.com");
+ });
+
+ it("should encode special characters in keys", () => {
+ const obj = { "user name": "John", "email[primary]": "john@example.com" };
+ expect(toQueryString(obj)).toBe("user%20name=John&email%5Bprimary%5D=john%40example.com");
+ });
- encodingTests.forEach(({ description, input, options, expected }) => {
- it(description, () => {
- expect(toQueryString(input, options)).toBe(expected);
- });
+ it("should not encode special characters in keys when encode is false", () => {
+ const obj = { "user name": "John", "email[primary]": "john@example.com" };
+ expect(toQueryString(obj, { encode: false })).toBe("user name=John&email[primary]=john@example.com");
});
});
describe("Mixed scenarios", () => {
- interface MixedTestCase {
- description: string;
- input: any;
- options?: { arrayFormat?: "repeat" | "indices" };
- expected: string;
- }
-
- const mixedTests: MixedTestCase[] = [
- {
- description: "should handle complex nested structures",
- input: {
- filters: {
- status: ["active", "pending"],
- category: {
- type: "electronics",
- subcategories: ["phones", "laptops"],
- },
+ it("should handle complex nested structures", () => {
+ const obj = {
+ filters: {
+ status: ["active", "pending"],
+ category: {
+ type: "electronics",
+ subcategories: ["phones", "laptops"],
},
- sort: { field: "name", direction: "asc" },
},
- expected:
- "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
- },
- {
- description: "should handle complex nested structures with repeat format",
- input: {
- filters: {
- status: ["active", "pending"],
- category: {
- type: "electronics",
- subcategories: ["phones", "laptops"],
- },
+ sort: { field: "name", direction: "asc" },
+ };
+ expect(toQueryString(obj)).toBe(
+ "filters%5Bstatus%5D%5B0%5D=active&filters%5Bstatus%5D%5B1%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D%5B0%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D%5B1%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
+ );
+ });
+
+ it("should handle complex nested structures with repeat format", () => {
+ const obj = {
+ filters: {
+ status: ["active", "pending"],
+ category: {
+ type: "electronics",
+ subcategories: ["phones", "laptops"],
},
- sort: { field: "name", direction: "asc" },
},
- options: { arrayFormat: "repeat" },
- expected:
- "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
- },
- {
- description: "should handle arrays with null/undefined values",
- input: { items: ["a", null, "c", undefined, "e"] },
- expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e",
- },
- {
- description: "should handle objects with null/undefined values",
- input: { name: "John", age: null, email: undefined, active: true },
- expected: "name=John&age=&active=true",
- },
- ];
+ sort: { field: "name", direction: "asc" },
+ };
+ expect(toQueryString(obj, { arrayFormat: "repeat" })).toBe(
+ "filters%5Bstatus%5D=active&filters%5Bstatus%5D=pending&filters%5Bcategory%5D%5Btype%5D=electronics&filters%5Bcategory%5D%5Bsubcategories%5D=phones&filters%5Bcategory%5D%5Bsubcategories%5D=laptops&sort%5Bfield%5D=name&sort%5Bdirection%5D=asc",
+ );
+ });
+
+ it("should handle arrays with null/undefined values", () => {
+ const obj = { items: ["a", null, "c", undefined, "e"] };
+ expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c&items%5B4%5D=e");
+ });
- mixedTests.forEach(({ description, input, options, expected }) => {
- it(description, () => {
- expect(toQueryString(input, options)).toBe(expected);
- });
+ it("should handle objects with null/undefined values", () => {
+ const obj = { name: "John", age: null, email: undefined, active: true };
+ expect(toQueryString(obj)).toBe("name=John&age=&active=true");
});
});
describe("Edge cases", () => {
- const edgeCaseTests: BasicTestCase[] = [
- {
- description: "should handle numeric keys",
- input: { "0": "zero", "1": "one" },
- expected: "0=zero&1=one",
- },
- {
- description: "should handle boolean values in objects",
- input: { enabled: true, disabled: false },
- expected: "enabled=true&disabled=false",
- },
- {
- description: "should handle empty strings",
- input: { name: "", description: "test" },
- expected: "name=&description=test",
- },
- {
- description: "should handle zero values",
- input: { count: 0, price: 0.0 },
- expected: "count=0&price=0",
- },
- {
- description: "should handle arrays with empty strings",
- input: { items: ["a", "", "c"] },
- expected: "items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c",
- },
- ];
+ it("should handle numeric keys", () => {
+ const obj = { "0": "zero", "1": "one" };
+ expect(toQueryString(obj)).toBe("0=zero&1=one");
+ });
+
+ it("should handle boolean values in objects", () => {
+ const obj = { enabled: true, disabled: false };
+ expect(toQueryString(obj)).toBe("enabled=true&disabled=false");
+ });
+
+ it("should handle empty strings", () => {
+ const obj = { name: "", description: "test" };
+ expect(toQueryString(obj)).toBe("name=&description=test");
+ });
- edgeCaseTests.forEach(({ description, input, expected }) => {
- it(description, () => {
- expect(toQueryString(input)).toBe(expected);
- });
+ it("should handle zero values", () => {
+ const obj = { count: 0, price: 0.0 };
+ expect(toQueryString(obj)).toBe("count=0&price=0");
+ });
+
+ it("should handle arrays with empty strings", () => {
+ const obj = { items: ["a", "", "c"] };
+ expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=&items%5B2%5D=c");
});
});
describe("Options combinations", () => {
- interface OptionsTestCase {
- description: string;
- input: any;
- options?: { arrayFormat?: "repeat" | "indices"; encode?: boolean };
- expected: string;
- }
+ it("should respect both arrayFormat and encode options", () => {
+ const obj = { items: ["a & b", "c & d"] };
+ expect(toQueryString(obj, { arrayFormat: "repeat", encode: false })).toBe("items=a & b&items=c & d");
+ });
- const optionsTests: OptionsTestCase[] = [
- {
- description: "should respect both arrayFormat and encode options",
- input: { items: ["a & b", "c & d"] },
- options: { arrayFormat: "repeat", encode: false },
- expected: "items=a & b&items=c & d",
- },
- {
- description: "should use default options when none provided",
- input: { items: ["a", "b"] },
- expected: "items%5B0%5D=a&items%5B1%5D=b",
- },
- {
- description: "should merge provided options with defaults",
- input: { items: ["a", "b"], name: "John Doe" },
- options: { encode: false },
- expected: "items[0]=a&items[1]=b&name=John Doe",
- },
- ];
+ it("should use default options when none provided", () => {
+ const obj = { items: ["a", "b"] };
+ expect(toQueryString(obj)).toBe("items%5B0%5D=a&items%5B1%5D=b");
+ });
- optionsTests.forEach(({ description, input, options, expected }) => {
- it(description, () => {
- expect(toQueryString(input, options)).toBe(expected);
- });
+ it("should merge provided options with defaults", () => {
+ const obj = { items: ["a", "b"], name: "John Doe" };
+ expect(toQueryString(obj, { encode: false })).toBe("items[0]=a&items[1]=b&name=John Doe");
});
});
});
diff --git a/tests/wire/imdb.test.ts b/tests/wire/imdb.test.ts
index bf56e56..dbb9d53 100644
--- a/tests/wire/imdb.test.ts
+++ b/tests/wire/imdb.test.ts
@@ -1,14 +1,14 @@
// This file was auto-generated by Fern from our API Definition.
-import * as FernAutopilotTestApi from "../../src/api/index";
-import { FernAutopilotTestApiClient } from "../../src/Client";
import { mockServerPool } from "../mock-server/MockServerPool";
+import { FernAutopilotTestApiClient } from "../../src/Client";
+import * as FernAutopilotTestApi from "../../src/api/index";
describe("Imdb", () => {
test("createMovie", async () => {
const server = mockServerPool.createServer();
const client = new FernAutopilotTestApiClient({ environment: server.baseUrl });
- const rawRequestBody = { title: "title", rating: 1.1 };
+ const rawRequestBody = { title: "title", rating: 1.1, metadata: "metadata", more_metadata: "more_metadata" };
const rawResponseBody = "string";
server
.mockEndpoint()
@@ -22,6 +22,8 @@ describe("Imdb", () => {
const response = await client.imdb.createMovie({
title: "title",
rating: 1.1,
+ metadata: "metadata",
+ more_metadata: "more_metadata",
});
expect(response).toEqual("string");
});
@@ -30,13 +32,7 @@ describe("Imdb", () => {
const server = mockServerPool.createServer();
const client = new FernAutopilotTestApiClient({ environment: server.baseUrl });
- const rawResponseBody = {
- id: "tt0111161",
- title: "The Shawshank Redemption",
- rating: 9.3,
- description: "A story of hope and redemption.",
- metadata: "hey",
- };
+ const rawResponseBody = { id: "tt0111161", title: "The Shawshank Redemption", rating: 9.3, metadata: "hey" };
server.mockEndpoint().get("/movies/tt0111161").respondWith().statusCode(200).jsonBody(rawResponseBody).build();
const response = await client.imdb.getMovie("tt0111161");
@@ -44,7 +40,6 @@ describe("Imdb", () => {
id: "tt0111161",
title: "The Shawshank Redemption",
rating: 9.3,
- description: "A story of hope and redemption.",
metadata: "hey",
});
});
diff --git a/tsconfig.esm.json b/tsconfig.esm.json
index 6ce9097..95a5eb7 100644
--- a/tsconfig.esm.json
+++ b/tsconfig.esm.json
@@ -2,8 +2,7 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "esnext",
- "outDir": "dist/esm",
- "verbatimModuleSyntax": true
+ "outDir": "dist/esm"
},
"include": ["src"],
"exclude": []
diff --git a/vitest.config.mts b/vitest.config.ts
similarity index 100%
rename from vitest.config.mts
rename to vitest.config.ts