diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..a329b727c6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,49 @@ +/.github/ @brendt @innocenzi @aidan-casey +/.github/workflows/coding-conventions.yml @aidan-casey +/.github/workflows/create-gh-release.yml @innocenzi +/.github/workflows/deploy-docs.yml @innocenzi +/.github/workflows/integration-tests.yml @aidan-casey +/.github/workflows/integration-tests-windows.yml @aidan-casey @brendt +/.github/workflows/isolated-tests.yml @aidan-casey +/.github/workflows/lint-pr-title.yml @innocenzi +/.github/workflows/publish-javascript-packages.yml @innocenzi +/.github/workflows/subsplit-packages.yml @aidan-casey +/.github/workflows/trigger-tempest-app-qa.yml @brendt +/.github/workflows/trigger-tempest-clean-qa.yml @brendt +/.github/workflows/validate-packages.yml @aidan-casey +/src/ @brendt +/docs/ @brendt @innocenzi +/packages/auth/ @innocenzi +/packages/cache/ @innocenzi +/packages/clock/ @aidan-casey +/packages/command-bus/ @brendt @aidan-casey +/packages/console/ @brendt +/packages/container/ @brendt +/packages/core/ @brendt +/packages/cryptography/ @innocenzi +/packages/database/ @brendt @innocenzi +/packages/datetime/ @innocenzi +/packages/debug/ @innocenzi +/packages/discovery/ @brendt @aidan-casey +/packages/event-bus/ @brendt @aidan-casey +/packages/generation/ @brendt +/packages/http/ @brendt @aidan-casey +/packages/http-client/ @aidan-casey +/packages/icon/ @innocenzi +/packages/kv-store/ @innocenzi +/packages/log/ @brendt +/packages/mail/ @innocenzi +/packages/mapper/ @brendt +/packages/process/ @innocenzi +/packages/reflection/ @brendt @aidan-casey +/packages/router/ @brendt @aidan-casey +/packages/router/src/Exceptions/local @innocenzi +/packages/storage/ @innocenzi +/packages/support/ @innocenzi @brendt +/packages/upgrade/ @brendt +/packages/validation/ @brendt @innocenzi +/packages/view/ @brendt +/packages/vite/ @innocenzi +/packages/vite-plugin-tempest/ @innocenzi +*.ts @innocenzi +*.vue @innocenzi diff --git a/.github/workflows/coding-conventions.yml b/.github/workflows/coding-conventions.yml index 0192386fc7..7f66d9f3fd 100644 --- a/.github/workflows/coding-conventions.yml +++ b/.github/workflows/coding-conventions.yml @@ -4,7 +4,6 @@ on: pull_request: workflow_dispatch: -# CSFixer and Rector are temporarily disabled until they have proper PHP 8.4 support jobs: check-style: name: Run style check @@ -15,12 +14,12 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 coverage: none - name: Install dependencies run: | - composer update --prefer-dist --no-interaction + composer update --prefer-dist --no-interaction --ignore-platform-reqs composer mago:install-binary - name: Run Mago @@ -46,7 +45,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 coverage: none - name: Install composer dependencies @@ -65,7 +64,7 @@ jobs: # - name: Setup PHP # uses: shivammathur/setup-php@v2 # with: -# php-version: 8.4 +# php-version: 8.5 # coverage: none # # - name: Install composer dependencies diff --git a/.github/workflows/integration-tests-windows.yml b/.github/workflows/integration-tests-windows.yml index aea44c2f11..c2875b06d5 100644 --- a/.github/workflows/integration-tests-windows.yml +++ b/.github/workflows/integration-tests-windows.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: php: - - 8.4 + - 8.5 os: - windows-latest env: @@ -55,7 +55,7 @@ jobs: os: - windows-latest php: - - 8.4 + - 8.5 database: - sqlite - mysql @@ -84,7 +84,7 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --ignore-platform-reqs - name: "Setup Redis" if: ${{ matrix.os != 'windows-latest' }} diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 48f2862f01..d9e3e67ff6 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -20,7 +20,7 @@ jobs: fail-fast: false matrix: php: - - 8.4 + - 8.5 os: - ubuntu-latest # - windows-latest @@ -59,7 +59,7 @@ jobs: - ubuntu-latest # - windows-latest php: - - 8.4 + - 8.5 database: - sqlite - mysql @@ -90,7 +90,7 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install dependencies - run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --ignore-platform-reqs - name: "Setup Redis" if: ${{ matrix.os != 'windows-latest' }} diff --git a/.github/workflows/isolated-tests.yml b/.github/workflows/isolated-tests.yml index e296c065dc..fe1da2c33d 100644 --- a/.github/workflows/isolated-tests.yml +++ b/.github/workflows/isolated-tests.yml @@ -17,7 +17,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 coverage: none - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: - ubuntu-latest package: ${{ fromJson(needs.get_packages.outputs.matrix) }} php: - - 8.4 + - 8.5 stability: - prefer-stable - prefer-lowest @@ -66,7 +66,7 @@ jobs: uses: supercharge/redis-github-action@1.7.0 - name: Install PHPUnit - run: composer global require phpunit/phpunit:12.4.0 + run: composer global require phpunit/phpunit:12.5.8 - name: Setup problem matchers run: | @@ -75,9 +75,9 @@ jobs: - name: Install dependencies run: | - ./bin/build-changed-packages + ./bin/build-packages cd "packages/${{ matrix.package.basename }}" - composer update --${{ matrix.stability }} --prefer-dist --no-interaction + composer update --${{ matrix.stability }} --prefer-dist --no-interaction --ignore-platform-reqs - name: Execute tests run: phpunit -c "packages/${{ matrix.package.basename }}/phpunit.xml" diff --git a/.github/workflows/subsplit-packages.yml b/.github/workflows/subsplit-packages.yml index b534d3a304..5233d1d579 100644 --- a/.github/workflows/subsplit-packages.yml +++ b/.github/workflows/subsplit-packages.yml @@ -2,13 +2,13 @@ name: "Sub-split packages" on: push: - branches: [main] + branches: + - "[0-9]+.x" + - "v[0-9]+.[0-9]+-alpha.[0-9]+" + - "v[0-9]+.[0-9]+-beta.[0-9]+" tags: ["v*"] workflow_dispatch: -env: - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - jobs: get_packages: name: Get packages @@ -17,17 +17,18 @@ jobs: - name: Set Up PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 coverage: none - uses: actions/checkout@v4 - name: Get packages id: get_json - run: echo "::set-output name=json::$(bin/get-packages)" + run: echo "json=$(bin/get-packages)" >> $GITHUB_OUTPUT - name: Output packages run: echo "${{ steps.get_json.outputs.json }}" + outputs: matrix: ${{ steps.get_json.outputs.json }} @@ -41,34 +42,11 @@ jobs: package: ${{ fromJson(needs.get_packages.outputs.matrix) }} steps: - uses: actions/checkout@v4 - # no tag - - if: "!startsWith(github.ref, 'refs/tags/')" - uses: "symplify/monorepo-split-github-action@v2.3.0" - with: - # ↓ split "packages/console" directory - package_directory: "${{ matrix.package.directory }}" - - # ↓ into https://github.com/tempestphp/tempest-console repository - repository_organization: "${{ matrix.package.organization }}" - repository_name: "${{ matrix.package.repository }}" - - # ↓ the user signed under the split commit - user_name: "aidan-casey" - user_email: "aidan@caseyhouse.net" - - # with tag - - if: "startsWith(github.ref, 'refs/tags/')" - uses: "symplify/monorepo-split-github-action@v2.3.0" - with: - tag: ${GITHUB_REF#refs/tags/} - - # ↓ split "packages/console" directory - package_directory: "${{ matrix.package.directory }}" - # ↓ into https://github.com/tempestphp/tempest-console repository - repository_organization: "${{ matrix.package.organization }}" - repository_name: "${{ matrix.package.repository }}" + - name: Set up git-filter-repo + run: pip install git-filter-repo - # ↓ the user signed under the split commit - user_name: "aidan-casey" - user_email: "aidan@caseyhouse.net" + - name: Split Package + env: + GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: ./bin/split "${{ matrix.package.name }}" diff --git a/.github/workflows/validate-packages.yml b/.github/workflows/validate-packages.yml index 7a62fd23cc..dc7643f31b 100644 --- a/.github/workflows/validate-packages.yml +++ b/.github/workflows/validate-packages.yml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 extensions: dom, curl, libxml, mbstring, pcntl, fileinfo, intl coverage: none diff --git a/bin/build-changed-packages b/bin/build-changed-packages deleted file mode 100755 index 1f017bea1a..0000000000 --- a/bin/build-changed-packages +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env php - [], -]; - -foreach ($tempestPackages as $package) { - // Find out if there are changes in this package. - $diff = exec(sprintf('git diff --name-only HEAD^ -- %s', $package['directory'])); - - $composerPath = sprintf('%s/composer.json', $package['directory']); - $composerFile = json_decode(file_get_contents($composerPath), true); - - // If there are changes, bundle the package and - // add it to our root packages.json file. - if (empty($diff) === false) { - // Bundle the current package as a tar file. - passthru(sprintf("cd %s && tar -cf package.tar --exclude='package.tar' *", $package['directory'])); - - // TODO: Update the package version. - $composerFile['version'] = 'dev-main'; - $composerFile['dist']['type'] = 'tar'; - $composerFile['dist']['url'] = 'file://'. $package['directory'] . '/package.tar'; - - // Add the package details to the root "packages.json." - $composerPackages['packages'][$composerFile['name']][$composerFile['version']] = $composerFile; - } - - // Load the packages from the root "packages.json" file we will write in a second. - $composerFile['repositories'] = [ - [ - 'type' => 'composer', - 'url' => realpath(__DIR__ . '/../'), - ] - ]; - - file_put_contents($composerPath, json_encode($composerFile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); -} - -file_put_contents(__DIR__ . '/../packages.json', json_encode($composerPackages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - -var_dump(file_get_contents(__DIR__ . '/../packages.json')); \ No newline at end of file diff --git a/bin/build-packages b/bin/build-packages new file mode 100755 index 0000000000..3c68c4bb3c --- /dev/null +++ b/bin/build-packages @@ -0,0 +1,62 @@ +#!/usr/bin/env php + $version) { + if (str_starts_with($dep, 'tempest/') && str_starts_with($version, 'dev-')) { + $devVersion = $version; + break 2; + } + } +} + +$composerPackages = [ + 'packages' => [], +]; + +foreach ($tempestPackages as $package) { + $composerPath = sprintf('%s/composer.json', $package['directory']); + $composerFile = json_decode(file_get_contents($composerPath), true); + + // Bundle ALL packages as tar files to ensure consistent versions. + // This ensures all inter-package dependencies (e.g., dev-3.x) can be + // resolved from the local repository. + passthru(sprintf("cd %s && tar -cf package.tar --exclude='package.tar' *", $package['directory'])); + + $composerFile['version'] = $devVersion; + $composerFile['dist']['type'] = 'tar'; + $composerFile['dist']['url'] = 'file://'. $package['directory'] . '/package.tar'; + + // Add the package details to the root "packages.json." + $composerPackages['packages'][$composerFile['name']][$composerFile['version']] = $composerFile; + + // Load the packages from the root "packages.json" file we will write in a second. + $composerFile['repositories'] = [ + [ + 'type' => 'composer', + 'url' => realpath(__DIR__ . '/../'), + ] + ]; + + file_put_contents($composerPath, json_encode($composerFile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); +} + +file_put_contents(__DIR__ . '/../packages.json', json_encode($composerPackages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + +var_dump(file_get_contents(__DIR__ . '/../packages.json')); diff --git a/bin/release b/bin/release index 293575f1d5..0039c622e6 100755 --- a/bin/release +++ b/bin/release @@ -18,11 +18,10 @@ use Composer\Semver\VersionParser; use Tempest\Console\Console; use Tempest\Console\ConsoleApplication; use Tempest\Console\Exceptions\InterruptException; -use Tempest\Http\Response; use Tempest\HttpClient\HttpClient; use Tempest\Support\Json; +use Tempest\Container; -use function Tempest\get; use function Tempest\Support\arr; use function Tempest\Support\str; @@ -87,9 +86,11 @@ function bumpPhpPackages(string $version, bool $isMajor): void */ function cleanUpAfterRelease(): void { - // We want to still be able to `require tempest/package:dev-main`, so we need - // to update back all composer files to use `dev-main` instead of a fixed version. - executeCommands('./vendor/bin/monorepo-builder bump-interdependency dev-main'); + $devVersion = sprintf("%s-dev", getCurrentBranch()); + + // We want to still be able to `require tempest/package:X.x-dev`, so we need + // to update back all composer files to use the dev version instead of a fixed version. + executeCommands("./vendor/bin/monorepo-builder bump-interdependency {$devVersion}"); // Finds all `composer.json` files in `packages/`, and revert the `tempest/highlight` dependency to the saved version setPhpDependencyVersion( @@ -145,7 +146,7 @@ function updateChangelog(string $version): void /** * Ensure the release script can run. */ -function performPreReleaseChecks(string $remote, string $branch): void +function performPreReleaseChecks(string $remote): void { if (empty(shell_exec('which bun'))) { throw new Exception('This script requires `bun` to be installed.'); @@ -155,6 +156,12 @@ function performPreReleaseChecks(string $remote, string $branch): void throw new Exception('Repository must be in a clean state to release.'); } + $branch = getCurrentBranch(); + + if (! preg_match('/^\d+\.x$/', $branch)) { + throw new Exception("You must be on a version branch to release. Current branch is {$branch}."); + } + if (! str_starts_with(shell_exec('git rev-parse --abbrev-ref --symbolic-full-name @{u}'), "{$remote}/{$branch}")) { throw new Exception("You must be on the {$remote}/{$branch} branch to release."); } @@ -178,7 +185,7 @@ function updateBranchProtection(bool $enabled): void $token = Tempest\env('RELEASE_GITHUB_TOKEN'); $uri = "https://api.github.com/repos/tempestphp/tempest-framework/rulesets/{$ruleset}"; - $httpClient = Tempest\get(HttpClient::class); + $httpClient = Container\get(HttpClient::class); $response = $httpClient->put( uri: $uri, headers: ['Authorization' => "Bearer {$token}"], @@ -196,7 +203,7 @@ function triggerSubsplit(): void $token = Tempest\env('RELEASE_GITHUB_TOKEN'); $uri = 'https://api.github.com/repos/tempestphp/tempest-framework/actions/workflows/subsplit-packages.yml/dispatches'; - $httpClient = Tempest\get(HttpClient::class); + $httpClient = Container\get(HttpClient::class); $response = $httpClient->post( uri: $uri, @@ -205,7 +212,7 @@ function triggerSubsplit(): void 'Accept' => 'application/vnd.github+json', 'X-GitHub-Api-Version' => '2022-11-28', ], - body: Json\encode(['ref' => 'main']), + body: Json\encode(['ref' => getCurrentBranch()]), ); if (! $response->status->isSuccessful()) { @@ -222,6 +229,14 @@ function getCurrentVersion(): string return exec('git describe --tags --abbrev=0'); } +/** + * Gets the current git branch name. + */ +function getCurrentBranch(): string +{ + return trim(shell_exec('git rev-parse --abbrev-ref HEAD') ?? ''); +} + /** * Suggests a semver-valid version. */ @@ -344,9 +359,9 @@ function ensureTagDoesNotExist(string $version): void try { ConsoleApplication::boot(); - performPreReleaseChecks('origin', 'main'); + performPreReleaseChecks('origin'); - $console = get(Console::class); + $console = Container\get(Console::class); $console->writeln(); $console->info(sprintf('Current version is %s.', $current = getCurrentVersion())); diff --git a/bin/split b/bin/split new file mode 100755 index 0000000000..2d2cc48e25 --- /dev/null +++ b/bin/split @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +PACKAGE_NAME=$1 +SUBSPLIT_DIR="${PACKAGE_NAME}_subsplit" +ORG_NAME="tempestphp" +REPOSITORY_NAME="tempest-${PACKAGE_NAME}" + +# Check if the split repository exists, if not create it +if ! gh repo view "${ORG_NAME}/${REPOSITORY_NAME}" &>/dev/null; then + gh repo create "${ORG_NAME}/${REPOSITORY_NAME}" \ + --public \ + --description "[READ ONLY] Sub split of the Tempest ${PACKAGE_NAME} component." \ + --disable-issues \ + --disable-wiki +fi + +# Clone our Tempest repository and jump into it. +git clone https://github.com/tempestphp/tempest-framework "$SUBSPLIT_DIR" +cd "$SUBSPLIT_DIR" + +# Get the current default branch of the Tempest repo. +DEFAULT_BRANCH=$(git branch --show-current) + +# Filter the history down to only the one package. +git filter-repo --subdirectory-filter "packages/${PACKAGE_NAME}" + +# Setup our remote repository we are splitting to. +git remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${ORG_NAME}/${REPOSITORY_NAME}.git" +git branch -m "$DEFAULT_BRANCH" + +# Push all branches + history to the repo. +git push --force --all origin + +# Push all releases / tags to the repo. +git push --force --tags origin + +# Cleanup +cd ../ +rm -rf "${SUBSPLIT_DIR}" diff --git a/composer.json b/composer.json index 411d6500d0..6d2fa71066 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "monolog/monolog": "^3.7.0", "nette/php-generator": "^4.1.6", "nikic/php-parser": "^5.3", - "php": "^8.4", + "php": "^8.5", "psr-discovery/http-client-implementations": "^1.4", "psr-discovery/http-factory-implementations": "^1.2", "psr/cache": "^3.0", @@ -33,7 +33,7 @@ "psr/http-factory": "^1.0", "psr/http-message": "^1.0|^2.0", "psr/log": "^3.0.0", - "rector/rector": "^2.2.5", + "rector/rector": "^2.3.2", "symfony/cache": "^7.3", "symfony/mailer": "^7.2.6", "symfony/process": "^7.3", @@ -51,7 +51,6 @@ "azure-oss/storage-blob-flysystem": "^1.2", "brianium/paratest": "^7.14", "carthage-software/mago": "1.0.0-beta.28", - "depotwarehouse/oauth2-twitch": "^1.3", "guzzlehttp/psr7": "^2.6.1", "league/flysystem-aws-s3-v3": "^3.25.1", "league/flysystem-ftp": "^3.25.1", @@ -75,7 +74,7 @@ "phpat/phpat": "^0.11.0", "phpbench/phpbench": "84.x-dev", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^12.2.3", + "phpunit/phpunit": "^12.5.8", "predis/predis": "^3.0.0", "riskio/oauth2-auth0": "^2.4", "smolblog/oauth2-twitter": "^1.0", @@ -88,6 +87,7 @@ "tempest/blade": "dev-main", "thenetworg/oauth2-azure": "^2.2", "twig/twig": "^3.16", + "vertisan/oauth2-twitch-helix": "^2.0", "wohali/oauth2-discord-new": "^1.2" }, "replace": { @@ -212,6 +212,7 @@ "Tempest\\Cryptography\\Tests\\": "packages/cryptography/tests", "Tempest\\Database\\Tests\\": "packages/database/tests", "Tempest\\DateTime\\Tests\\": "packages/datetime/tests", + "Tempest\\Debug\\Tests\\": "packages/debug/tests", "Tempest\\EventBus\\Tests\\": "packages/event-bus/tests", "Tempest\\Generation\\Tests\\": "packages/generation/tests", "Tempest\\HttpClient\\Tests\\": "packages/http-client/tests", @@ -249,11 +250,14 @@ "lint:fix": "vendor/bin/mago lint --fix --format-after-fix", "style": "composer fmt && composer lint:fix", "test": "composer phpunit", + "test:stop": "composer phpunit -- --stop-on-error --stop-on-failure", "lint": "vendor/bin/mago lint --potentially-unsafe --minimum-fail-level=note", "phpstan": "vendor/bin/phpstan analyse src tests --memory-limit=1G", "rector": "vendor/bin/rector process --no-ansi", "merge": "php -d\"error_reporting = E_ALL & ~E_DEPRECATED\" vendor/bin/monorepo-builder merge", "intl:plural": "./packages/intl/bin/plural-rules.php", + "exceptions:dev": "cd ./packages/router/src/Exceptions/local && bun i && bun run dev", + "exceptions:build": "cd ./packages/router/src/Exceptions/local && bun i && bun run build", "release": [ "composer qa", "./bin/release" @@ -267,7 +271,8 @@ "./bin/validate-packages", "./tempest discovery:clear --no-interaction", "composer phpunit", - "composer phpstan" + "composer phpstan", + "composer exceptions:build" ] } } diff --git a/docs/0-getting-started/02-installation.md b/docs/0-getting-started/02-installation.md index fc0955a65c..6ab267cc3a 100644 --- a/docs/0-getting-started/02-installation.md +++ b/docs/0-getting-started/02-installation.md @@ -5,7 +5,7 @@ description: Tempest can be installed as a standalone PHP project, as well as a ## Prerequisites -Tempest requires PHP [8.4+](https://www.php.net/downloads.php) and [Composer](https://getcomposer.org/) to be installed. Optionally, you may install either [Bun](https://bun.sh) or [Node](https://nodejs.org) if you chose to bundle front-end assets. +Tempest requires PHP [8.5+](https://www.php.net/downloads.php) and [Composer](https://getcomposer.org/) to be installed. Optionally, you may install either [Bun](https://bun.sh) or [Node](https://nodejs.org) if you chose to bundle front-end assets. For a better experience, it is recommended to have a complete development environment, such as [ServBay](https://www.servbay.com), [Herd](https://herd.laravel.com/docs), or [Valet](https://laravel.com/docs/valet). However, Tempest can serve applications using PHP's built-in server just fine. @@ -24,7 +24,7 @@ If you have a dedicated development environment, you may then access your applic ```sh {:hl-keyword:php:} tempest serve -{:hl-comment:PHP 8.4.5 Development Server (http://localhost:8000) started:} +{:hl-comment:PHP 8.5.1 Development Server (http://localhost:8000) started:} ``` ### Scaffolding front-end assets @@ -73,7 +73,7 @@ Tempest won't impose any file structure on you: one of its core features is that For instance, Tempest is able to differentiate between a controller method and a console command by looking at the code, instead of relying on naming conventions or configuration files. :::info -This concept is called [discovery](../4-internals/02-discovery), and is one of Tempest's most powerful features. +This concept is called [discovery](../1-essentials/05-discovery), and is one of Tempest's most powerful features. ::: The following project structures work the same way in Tempest, without requiring any specific configuration: @@ -98,9 +98,11 @@ The following project structures work the same way in Tempest, without requiring ## About discovery -Discovery works by scanning your project code, and looking at each file and method individually to determine what that code does. In production environments, [Tempest will cache the discovery process](../4-internals/02-discovery#discovery-in-production), avoiding any performance overhead. +Discovery works by scanning your project code and looking at each file and method individually to determine what that code does. In production environments, [Tempest caches the discovery process](../1-essentials/05-discovery#discovery-in-production), avoiding any performance overhead. -As an example, Tempest is able to determine which methods are controller methods based on their route attributes, such as `#[Get]` or `#[Post]`: +As an example, Tempest is able to determine which methods are controller methods based on their [route attributes](../1-essentials/01-routing.md), or to detect console commands based on methods annotated with {b`#[Tempest\Console\ConsoleCommand]`}: + +:::code-group ```php app/BlogPostController.php use Tempest\Router\Get; @@ -119,18 +121,22 @@ final readonly class BlogPostController } ``` -Likewise, it is able to detect console commands based on the `#[ConsoleCommand]` attribute: - ```php app/RssSyncCommand.php use Tempest\Console\HasConsole; use Tempest\Console\ConsoleCommand; final readonly class RssSyncCommand { - use HasConsole; - #[ConsoleCommand('rss:sync')] public function __invoke(bool $force = false): void - { /* … */ } + { + // … + } } ``` + +::: + +:::tip{tabler:link} +Learn more about discovery in the [dedicated documentation](../1-essentials/05-discovery.md). +::: diff --git a/docs/1-essentials/01-routing.md b/docs/1-essentials/01-routing.md index b6cc31b00f..9f52b3e793 100644 --- a/docs/1-essentials/01-routing.md +++ b/docs/1-essentials/01-routing.md @@ -5,21 +5,21 @@ description: "Learn how to route requests to controllers. In Tempest, this is do ## Overview -In Tempest, you may associate a route to any class method. Usually, this is done in dedicated controller classes, but it could be any class of your choice. +In Tempest, routes can be associated with any class method. This is typically done in dedicated controller classes, but any class can be used. -Tempest provides many attributes, named after HTTP verbs, to attach URIs to controller actions. These attributes implement the {`Tempest\Router\Route`} interface, so you can write your own if you need to. +Tempest provides attributes, named after HTTP verbs, to attach URIs to controller actions. These attributes implement the {b`Tempest\Router\Route`} interface, allowing custom route attributes to be created. ```php app/HomeController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class HomeController { #[Get(uri: '/home')] public function __invoke(): View { - return view('home.view.php'); + return view('./home.view.php'); } } ``` @@ -28,12 +28,12 @@ Out of the box, an attribute for every HTTP verb is available: {b`Tempest\Router ## Route parameters -You may define dynamic segments in your route URIs by wrapping them in curly braces. The segment name inside the braces will be passed as a parameter to your controller method. +Dynamic segments can be defined in route URIs by wrapping them in curly braces. The segment name inside the braces is passed as a parameter to the controller method. ```php app/AircraftController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class AircraftController { @@ -44,21 +44,21 @@ final readonly class AircraftController $aircraft = $this->aircraftRepository->getAircraftById($id); // Pass the aircraft to the view - return view('aircraft.view.php', aircraft: $aircraft); + return view('./aircraft.view.php', aircraft: $aircraft); } } ``` ### Optional parameters -Sometimes you may want a route to match both with and without a parameter. For instance, you might want `/aircraft` to show all aircraft, and `/aircraft/123` to show a specific aircraft. This can be achieved by marking route parameters as optional. +A route can match both with and without a parameter. For instance, `/aircraft` can show all aircraft, while `/aircraft/123` shows a specific aircraft. This is achieved by marking route parameters as optional. To mark a parameter as optional, prefix it with a question mark `?` inside the curly braces. The corresponding method parameter must either be nullable or have a default value. ```php app/AircraftController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class AircraftController { @@ -66,10 +66,8 @@ final readonly class AircraftController public function index(?string $id): View { if ($id === null) { - // Show all aircraft $aircraft = $this->aircraftRepository->all(); } else { - // Show specific aircraft $aircraft = $this->aircraftRepository->find($id); } @@ -78,32 +76,32 @@ final readonly class AircraftController } ``` -In this example, both `/aircraft` and `/aircraft/123` will match the same route. When the parameter is not provided, the method parameter receives `null`. +In this example, both `/aircraft` and `/aircraft/123` match the same route. When the parameter is not provided, the method parameter receives `null`. -Alternatively, you may provide a default value instead of using a nullable type: +Alternatively, a default value can be provided instead of using a nullable type: ```php app/AircraftController.php #[Get(uri: '/aircraft/{?type}')] public function filter(string $type = 'all'): View { - // When /aircraft is requested, $type will be 'all' - // When /aircraft/commercial is requested, $type will be 'commercial' + // $type defaults to 'all' when not provided + // $type is set to the provided value otherwise } ``` -You may also combine required and optional parameters. Optional parameters should come after required ones: +Required and optional parameters can be combined. Optional parameters must come after required ones: ```php app/FlightController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class FlightController { - #[Get(uri: '/flights/{id}/{?segment}')] - public function show(string $id, ?string $segment): View + #[Get(uri: '/flights/{flightNumber}/{?segment}')] + public function show(string $flightNumber, ?string $segment): View { - // Matches both /flights/AA123 and /flights/AA123/departure + // Matches both /flights/JFA123 and /flights/JFA123/departure } } ``` @@ -114,11 +112,11 @@ Multiple optional parameters are also supported: #[Get(uri: '/aircraft/{?manufacturer}/{?model}')] public function search(?string $manufacturer, ?string $model): View { - // Matches /aircraft, /aircraft/cessna, and /aircraft/cessna/172 + // Matches /aircraft, /aircraft/pilatus, and /aircraft/pilatus/pc24 } ``` -Optional parameters work seamlessly with [regular expression constraints](#regular-expression-constraints). Simply add the regex pattern after the parameter name: +Optional parameters work with [regular expression constraints](#regular-expression-constraints). Add the regular expression after the parameter name: ```php app/AircraftController.php #[Get(uri: '/aircraft/{?id:\d+}')] @@ -130,14 +128,14 @@ public function show(?int $id): View ### Regular expression constraints -You may constrain the format of a route parameter by specifying a regular expression after its name. +The format of a route parameter can be constrained by specifying a regular expression after its name. -For instance, you may only accept numeric identifiers for an `id` parameter by using the following dynamic segment: `{regex}{id:[0-9]+}`. In practice, a route may look like this: +For instance, to accept only numeric identifiers for an `id` parameter, use the following dynamic segment: `{regex}{id:[0-9]+}`. In practice, a route looks like this: ```php app/AircraftController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class AircraftController { @@ -151,7 +149,7 @@ final readonly class AircraftController ### Route binding -In controller actions, you may want to receive an object instead of a scalar value such as an identifier. This is especially useful in the case of [models](./03-database.md#models) to avoid having to write the fetching logic in each controller. +Controller actions can receive objects instead of scalar values such as identifiers. This is particularly useful for [models](./03-database.md#models) to avoid writing fetching logic in each controller. ```php app/AircraftController.php use Tempest\Router\Get; @@ -165,7 +163,7 @@ final class AircraftController } ``` -Route binding may be enabled for any class that implements the {`Tempest\Router\Bindable`} interface, which requires a `resolve()` method responsible for returning the correct instance. +Route binding can be enabled for any class that implements the {b`Tempest\Router\Bindable`} interface, which requires a static `resolve()` method responsible for returning the correct instance. ```php use Tempest\Router\Bindable; @@ -173,36 +171,47 @@ use Tempest\Database\IsDatabaseModel; final class Aircraft implements Bindable { - use IsDatabaseModel; - public static function resolve(string $input): self { - return self::findById(id: $input); + return query(self::class)->resolve($input); } } ``` -By default, `Bindable` objects will be cast to strings when they are passed into the `uri()` function as a route parameter. You can override this default behaviour by tagging a public property on the object with the {b`\Tempest\Router\IsBindingValue`} attribute: +By default, {b`Tempest\Router\Bindable`} objects are cast to strings when passed into the {b`Tempest\Router\uri()`} function as a route parameter. This means that these objects should implement `Stringable`. -```php +This default behaviour can be overridden by annotating a public property on the object with the {b`\Tempest\Router\IsBindingValue`} attribute: + +:::code-group + +```php app/Aircraft.php use Tempest\Router\Bindable; use Tempest\Router\IsBindingValue; final class Aircraft implements Bindable { #[IsBindingValue] - public string $callSign; + public string $registrationNumber; public static function resolve(string $input): self { - return self::findById(id: $input); + return query(self::class) + ->where('registrationNumber', $input) + ->first(); } } ``` +```php "URI generation" +uri(ShowAircraftController::class, aircraft: $aircraft); +// → /aircraft/lxjfa +``` + +::: + ### Backed enum binding -You may inject string-backed enumerations to controller actions. Tempest will try to map the corresponding parameter from the URI to an instance of that enum using the [`tryFrom`](https://www.php.net/manual/en/backedenum.tryfrom.php) enum method. +String-backed enumerations can be injected into controller actions. Tempest maps the corresponding parameter from the URI to an instance of that enum using the [`tryFrom`](https://www.php.net/manual/en/backedenum.tryfrom.php) enum method. ```php app/AircraftController.php use Tempest\Router\Get; @@ -216,7 +225,7 @@ final readonly class AircraftController } ``` -In the example above, we inject an `AircraftType` enumeration. If the request's `type` parameter has a value specified in that enumeration, it will be passed to the controller action. Otherwise, a HTTP 404 response will be returned without entering the controller method. +In the example above, an `AircraftType` enumeration is injected. If the request's `type` parameter has a value specified in that enumeration, it is passed to the controller action. Otherwise, an HTTP 404 response is returned without entering the controller method. ```php app/AircraftType.php enum AircraftType: string @@ -227,36 +236,24 @@ enum AircraftType: string } ``` -### Regex parameters - -You may use regular expressions to match route parameters. This can be useful to create catch-all routes or to match a route parameter to any kind of regex pattern. Add a colon `:` followed by a pattern to the parameter's name to indicate that it should be matched using a regular expression. - -```php -#[Get('/main/{path:.*}')] -public function docsRedirect(string $path): Redirect -{ - // … -} -``` - ## Generating URIs -Tempest provides a `\Tempest\Router\uri` function that can be used to generate a URI to a controller method. This function accepts the FQCN of the controller or a callable to a method as its first argument, and named parameters as [the rest of its arguments](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list). +Tempest provides a {b`\Tempest\Router\uri()`} function to generate URIs to controller methods. This function accepts the fully-qualified class name of the controller or a callable to a method as its first argument, and named parameters as [the rest of its arguments](https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list). ```php use function Tempest\Router\uri; // Invokable classes can be referenced directly: uri(HomeController::class); -// /home +// → /home // Classes with named methods are referenced using an array uri([AircraftController::class, 'store']); -// /aircraft +// → /aircraft // Additional URI parameters are passed in as named arguments: uri([AircraftController::class, 'show'], id: $aircraft->id); -// /aircraft/1 +// → /aircraft/1 ``` :::info @@ -265,9 +262,9 @@ URI-related methods are also available by injecting the {b`Tempest\Router\UriGen ### Signed URIs -A signed URI may be used to ensure that the URI was not modified after it was created. This is useful for implementing login links, or other endpoints that need protection against tampering. +A signed URI ensures that the URI was not modified after it was created. This is useful for implementing login or unsubscribe links, or other endpoints that need protection against tampering. -To create a signed URI, you may use the `signed_uri` function. This function accepts the same arguments as `uri`, and returns the URI with a `signature` parameter: +To create a signed URI, use the {b`\Tempest\Router\signed_uri()`} function. This function accepts the same arguments as {b`\Tempest\Router\uri()`} and returns the URI with a `signature` parameter: ```php use function Tempest\Router\signed_uri; @@ -278,7 +275,7 @@ signed_uri( ); ``` -Alternatively, you may use `temporary_signed_uri` to provide a duration after which the signed URI will expire, providing an extra layer of security. +Alternatively, {b`\Tempest\Router\temporary_signed_uri()`} can be used to provide a duration after which the signed URI expires, providing an extra layer of security. ```php use function Tempest\Router\temporary_signed_uri; @@ -290,7 +287,7 @@ temporary_signed_uri( ); ``` -To ensure the validity of a signed URL, you should call the `hasValidSignature` method on the {`Tempest\Router\UriGenerator`} class. +To ensure the validity of a signed URL, call the `hasValidSignature` method on the {b`Tempest\Router\UriGenerator`} class. ```php final class PasswordlessAuthenticationController @@ -302,23 +299,21 @@ final class PasswordlessAuthenticationController public function __invoke(Request $request): Response { if (! $this->uri->hasValidSignature($request)) { - return new Invalid(); + throw new HttpRequestFailed(Status::UNPROCESSABLE_CONTENT); } - // ... + // … } } ``` ### Matching the current URI -To determine whether the current request matches a specific controller action, Tempest provides the `is_current_uri` function. This function accepts the same arguments as `uri`, and returns a boolean. +To determine whether the current request matches a specific controller action, Tempest provides the {b`\Tempest\Router\is_current_uri()`} function. This function accepts the same arguments as `uri`, and returns a boolean. -```php +```php "GET /aircraft/1" use function Tempest\Router\is_current_uri; -// Current URI is: /aircraft/1 - // Providing no argument to the right controller action will match is_current_uri(AircraftController::class); // true @@ -331,15 +326,19 @@ is_current_uri(AircraftController::class, id: 2); // false ## Accessing request data -A core pattern of any web application is to access data from the current request. You may do so by injecting {`Tempest\Http\Request`} to a controller action. This class provides access to the request's body, query parameters, method, and other attributes through dedicated class properties. +Web applications need to process user input—whether it is form submissions, search queries, API payloads, or filter parameters. + +Tempest handles this by injecting {b`Tempest\Http\Request`} objects into controller actions, giving access to the request's body, query parameters, method, and headers through dedicated class properties. ### Using request classes -In most situations, the data you expect to receive from a request is structured. You expect clients to send specific values, and you want them to follow specific rules. +In most situations, the data expected from a request is structured. Clients are expected to send specific values and follow specific rules. + +The idiomatic approach is to use request classes. These are classes with public properties that correspond to the data to retrieve from the request. Tempest automatically validates these properties using PHP's type system, in addition to optional [validation attributes](../2-features/03-validation) when needed. -The idiomatic way to achieve this is by using request classes. They are classes with public properties that correspond to the data you want to retrieve from the request. Tempest will automatically validate these properties using PHP's type system, in addition to optional [validation attributes](../2-features/03-validation) if needed. +A request class must implement {b`Tempest\Http\Request`} and use the {b`Tempest\Http\IsRequest`} trait, which provides the default implementation. -A request class must implement {`Tempest\Http\Request`} and should use the {`Tempest\Http\IsRequest`} trait, which provides the default implementation. +:::code-group ```php app/RegisterAirportRequest.php use Tempest\Http\Request; @@ -353,22 +352,21 @@ final class RegisterAirportRequest implements Request #[HasLength(min: 10, max: 120)] public string $name; - public ?DateTimeImmutable $registeredAt = null; - + #[HasLength(min: 2)] public string $servedCity; -} -``` -:::info Interfaces with default implementations -Tempest uses this pattern a lot. Most classes that interact with the framework need to implement an interface, and a corresponding trait with a default implementation will be provided. -::: + #[HasLength(min: 4, max: 4)] + public string $icaoCode; -Once you have created a request class, you may simply inject it into a controller action. Tempest will take care of filling its properties and validating them, leaving you with a properly-typed object to work with. + public ?DateTime $registeredAt = null; +} +``` ```php app/AirportController.php use Tempest\Router\Post; use Tempest\Http\Responses\Redirect; -use function Tempest\map; + +use function Tempest\Mapper\map; use function Tempest\Router\uri; final readonly class AirportController @@ -376,26 +374,45 @@ final readonly class AirportController #[Post(uri: '/airports/register')] public function store(RegisterAirportRequest $request): Redirect { - $airport = map($request)->to(Airport::class)->save(); + $airport = map($request) + ->to(Airport::class) + ->save(); return new Redirect(uri([self::class, 'show'], id: $airport->id)); } } ``` +```php app/Airport.php +#[Table('airports')] +final class Airport +{ + public string $name; + public string $servedCity; + public string $icaoCode; + public ?DateTime $registeredAt = null; +} +``` + +::: + +Once a request class is created, it can be injected into a controller action. Tempest fills its properties and validates them, providing a properly-typed object. + :::info A note on data mapping The `map()` function allows mapping any data from any source into objects of your choice. You may read more about them in [their documentation](../2-features/01-mapper.md). ::: ### Sensitive fields -When handling sensitive data such as passwords or tokens, you may not want these values to be stored in the session or re-displayed in forms after validation errors. You can mark request properties as sensitive using the {b`#[Tempest\Http\SensitiveField]`} attribute: +When a validation error occurs, Tempest filters out sensitive fields from the original values stored in the session. This prevents sensitive data from being re-populated in forms after a redirect. + +Request properties can be marked as sensitive using the {b`#[Tempest\Http\SensitiveField]`} attribute: ```php app/ResetPasswordRequest.php use Tempest\Http\Request; use Tempest\Http\IsRequest; use Tempest\Http\SensitiveField; -use Tempest\Validation\Rules\HasMinLength; +use Tempest\Validation\Rules\HasLength; final class ResetPasswordRequest implements Request { @@ -404,19 +421,14 @@ final class ResetPasswordRequest implements Request public string $email; #[SensitiveField] - #[HasMinLength(8)] + #[HasLength(min: 8)] public string $password; - - #[SensitiveField] - public string $password_confirmation; } ``` -When a validation error occurs, Tempest will filter out sensitive fields from the original values stored in the session. This prevents sensitive data from being re-populated in forms after a redirect. - ### Retrieving data directly -For simpler use cases, you may simply retrieve a value from the body or the query parameter using the request's `get` method. +For simpler use cases, a value can be retrieved from the body or the query parameter using the {b`Tempest\Http\Request`}'s `get` method. Other methods, such as `hasBody` or `hasQuery`, are also available. ```php app/AircraftController.php use Tempest\Router\Get; @@ -435,30 +447,32 @@ final readonly class AircraftController ## Form validation -Oftentimes you'll want to submit form data from a website to be processed in the backend. In the previous section we've explained that Tempest will automatically map and validate request data unto request objects, but how do you then show validation errors back on the frontend? +When users submit forms—like updating profile settings, or posting comments—the data needs validation before processing. Tempest automatically validates request objects using type hints and validation attributes, then provides errors back to users when something is wrong. -Whenever a validation error occurs, Tempest will redirect back to the page the request was submitted on, or send a 400 invalid response (in case you're sending API requests). The validation errors can be found in two places: +On validation failure, Tempest either redirects back to the form (for web pages) or returns a 422 response (for stateless requests). Validation errors are available in two places: - As a JSON encoded string in the `{txt}X-Validation` header -- Within the session with the `Session::VALIDATION_ERRORS` key +- Through the `b{Tempest\Http\Session\FormSession}` class -The JSON encoded header is available for when you're building APIs with Tempest. The session errors are available for when you're building web pages. For web pages, you also need a way to show the errors when they occur; Tempest comes with some built-in view components to help you with that. +For web pages, Tempest also provides built-in view components to display errors when they occur. ```html - - - - - + + + ``` -`{html}` is a view component that will automatically include the CSRF token, as well as default to sending `POST` requests. `{html}` is a view component that renders a label, input field, and validation errors all at once. In practice, you'll likely want to make changes to these built-in view components. That's why you can run `./tempest install view-components` and select the components you want to pull into your project. You can [read more about installing view components here](../1-essentials/02-views.md#built-in-components). +`{html}` is a view component that defaults to sending `POST` requests. `{html}` is a view component that renders a label, input field, and validation errors all at once. + +:::info +These built-in view components can be customized. Run `./tempest install view-components` and select the components to pull into the project. [Read more about installing view components here](../1-essentials/02-views.md#built-in-components). +::: ## Route middleware -Middleware can be applied to handle tasks in between receiving a request and sending a response. To specify a middleware for a route, add it to the `middleware` argument of a route attribute. +Middleware can be applied to handle tasks between receiving a request and sending a response. To specify middleware for a route, add it to the `middleware` argument of a route attribute. ```php app/ReceiveInteractionController.php use Tempest\Router\Get; @@ -474,11 +488,11 @@ final readonly class ReceiveInteractionController } ``` -The middleware class must be an invokable class that implements the {`Tempest\Router\HttpMiddleware`} interface. This interface has an `{:hl-property:__invoke:}()` method that accepts the current request as its first parameter and {`Tempest\Router\HttpMiddlewareCallable`} as its second parameter. +The middleware class must be an invokable class that implements the {b`Tempest\Router\HttpMiddleware`} interface. This interface has an `{:hl-property:__invoke:}()` method that accepts the current request as its first parameter and {b`Tempest\Router\HttpMiddlewareCallable`} as its second parameter. -`HttpMiddlewareCallable` is an invokable class that forwards the `$request` to its next step in the pipeline. +{b`Tempest\Router\HttpMiddlewareCallable`} is an invokable class that forwards the `$request` to its next step in the pipeline. -```php +```php app/ValidateWebhook.php use Tempest\Router\HttpMiddleware; use Tempest\Router\HttpMiddlewareCallable; use Tempest\Http\Request; @@ -504,7 +518,7 @@ final readonly class ValidateWebhook implements HttpMiddleware ### Middleware priority -All middleware classes get sorted based on their priority. By default, each middleware gets the "normal" priority, but you can override it using the `#[Priority]` attribute: +All middleware classes are sorted based on their priority. By default, each middleware has the "normal" priority, which can be overridden using the {b`#[Tempest\Core\Priority]`} attribute: ```php use Tempest\Core\Priority; @@ -514,11 +528,11 @@ final readonly class ValidateWebhook implements HttpMiddleware { /* … */ } ``` -Note that priority is defined using an integer. You can however use one of the built-in `Priority` constants: `Priority::FRAMEWORK`, `Priority::HIGHEST`, `Priority::HIGH`, `Priority::NORMAL`, `Priority::LOW`, `Priority::LOWEST`. +Priority is defined using an integer. However, for consistency reasons, it is recommended to use of the built-in {b`Tempest\Core\Priority`} constants. ### Middleware discovery -Global middleware classes are discovered and sorted based on their priority. You can make a middleware class non-global by adding the {b`#[Tempest\Discovery\SkipDiscovery]`} attribute: +Global middleware classes are discovered and sorted based on their priority. A middleware class can be made non-global by annotating it with the {b`#[Tempest\Discovery\SkipDiscovery]`} attribute: ```php use Tempest\Discovery\SkipDiscovery; @@ -528,31 +542,42 @@ final readonly class ValidateWebhook implements HttpMiddleware { /* … */ } ``` +### Cross-site request forgery protection + +Tempest provides [cross-site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection based on the presence and values of the [`{txt}Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) and [`{txt}Sec-Fetch-Mode`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode) headers through the {b`Tempest\Router\PreventCrossSiteRequestsMiddleware`} middleware, included by default in all requests. + +Unlike traditional CSRF tokens, this approach uses browser-generated headers that cannot be forged by external websites: + +- [`{txt}Sec-Fetch-Site`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site) indicates whether the request came from the same domain, subdomain, a different site or if it was user-initiated, such as typing the URL directly, +- [`{txt}Sec-Fetch-Mode`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode) allows distinguishing between requests originating from a user navigating between HTML pages, and requests to load images and other resources. + +:::info +This middleware requires browsers that support `{txt}Sec-Fetch-*` headers, which is the case for all modern browsers. You may [exclude this middleware](#excluding-route-middleware) and implement traditional CSRF protection using tokens if you need to support older browsers. +::: + ### Excluding route middleware -Some routes may not require specific global middleware to be applied. For instance, API routes do not need CSRF protection. You may skip specific middleware by using the `without` argument of the route attribute. +Some routes do not require specific global middleware to be applied. For instance, a publicly accessible health check endpoint could bypass rate limiting that's applied to other routes. Specific middleware can be skipped by using the `without` argument of the route attribute. -```php app/Slack/ReceiveInteractionController.php -use Tempest\Router\Post; +```php app/HealthCheckController.php +use Tempest\Router\Get; use Tempest\Http\Response; -final readonly class ReceiveInteractionController +final readonly class HealthCheckController { - #[Post('/slack/interaction', without: [VerifyCsrfMiddleware::class, SetCookieMiddleware::class])] + #[Get('/health', without: [RateLimitMiddleware::class])] public function __invoke(): Response { - // … + return new Ok(['status' => 'healthy']); } } ``` -## Route decorators (route groups) - -Route decorators are Tempest's way to manage routes in bulk; it's a feature similar to route groups in other frameworks. Route decorators are attributes that implement the {b`\Tempest\Router\RouteDecorator`} interface. A route decorator's task is to make changes or add functionality to whether route it's associated with. Tempest comes with a few built-in route decorators, and you can make your own as well. +## Route decorators -In most cases, you'll want to add route decorators to a controller class, so that they are applied to all actions of that class: +When building an API or an administration panel, routes often share common configuration—like a URL prefix (`/api`), authentication middleware, or stateless behavior. Route decorators are attributes that can be annotated to controller classes or methods to apply common configuration. -```php +```php app/Books/ApiController.php use Tempest\Router\Prefix; use Tempest\Router\Get; @@ -567,27 +592,15 @@ final readonly class ApiController } ``` -However, route decorators may also be applied to individual controller actions: - -```php -use Tempest\Router\Stateless; -use Tempest\Router\Get; - -final readonly class BlogPostController -{ - #[Stateless] - #[Get('/rss')] - public function rss(): Response { /* … */ } -} -``` - ### Built-in route decorators -These route decorators are provided by Tempest: +Tempest includes several route decorators to handle common scenarios—like providing routes without session overhead, organizing routes under a common prefix, or applying authentication across an entire controller. + +These decorators save you from creating custom implementations for frequently-needed patterns. #### `#[Stateless]` -When you're building API endpoints, RSS feeds, or any other kind of page that does not require any cookie or session data, you may use the {b`#[Tempest\Router\Stateless]`} attribute, which will remove all state-related logic: +For API endpoints, RSS feeds, or any other kind of page that does not require cookie or session data, use the {b`#[Tempest\Router\Stateless]`} attribute to remove all state-related logic: ```php use Tempest\Router\Stateless; @@ -603,7 +616,7 @@ final readonly class BlogPostController #### `#[Prefix]` -Adds a prefix to all associated routes. +Adds a prefix to the URI for all associated routes. ```php use Tempest\Router\Prefix; @@ -628,7 +641,7 @@ Adds middleware to all associated routes. use Tempest\Router\WithMiddleware; use Tempest\Router\Get; -#[Middleware(AuthMiddleware::class, AdminMiddleware::class)] +#[WithMiddleware(AuthMiddleware::class, AdminMiddleware::class)] final readonly class AdminController { /* … */ } ``` @@ -639,14 +652,15 @@ Explicitly removes middleware to all associated routes. ```php use Tempest\Router\WithoutMiddleware; use Tempest\Router\Get; +use Tempest\Router\PreventCrossSiteRequestsMiddleware; -#[WithoutMiddleware(VerifyCsrfMiddleware::class, SetCookieMiddleware::class)] +#[WithoutMiddleware(PreventCrossSiteRequestsMiddleware::class)] final readonly class StatelessController { /* … */ } ``` ### Custom route decorators -Building your own route decorators is done by implementing the {b`\Tempest\Router\RouteDecorator`} interface and marking your decorator as an attribute. +Custom route decorators are built by implementing the {b`\Tempest\Router\RouteDecorator`} interface and marking the decorator as an attribute. The `decorate()` method receives the current {b`Tempest\Router\Route`} as a parameter, and must return the modified route. ```php use Attribute; @@ -657,7 +671,7 @@ final readonly class Auth implements RouteDecorator { public function decorate(Route $route): Route { - $route->middleare[] = AuthMiddleware::class; + $route->middleware[] = AuthMiddleware::class; return $route; } @@ -666,16 +680,18 @@ final readonly class Auth implements RouteDecorator ## Responses -All requests to a controller action expect a response to be returned to the client. This is done by returning a `{php}View` or a `{php}Response` object. +All requests to a controller action expect a response to be returned to the client. This is done by returning a {b`Tempest\View\View`} or a {b`Tempest\Http\Response`} object. + +For simpler use cases or debugging purposes, scalar values and arrays can also be returned directly. Tempest automatically converts these values into proper responses. ### View responses -Returning a view is a shorthand for returning a successful response with that view. You may as well use the `{php}view()` function directly to construct a view. +Returning a view is a shorthand for returning a successful response with that view. The {b`Tempest\view()`} function can be used directly to construct a view. ```php app/Aircraft/AircraftController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class AircraftController { @@ -690,23 +706,22 @@ final readonly class AircraftController } ``` -Tempest has a powerful templating system inspired by modern front-end frameworks. You may read more about views in their [dedicated chapter](./02-views.md). +Tempest has a templating system inspired by modern front-end frameworks like [Vue](https://vuejs.org). Read more about views in the [dedicated chapter](./02-views.md). ### Using built-in response classes -Tempest provides several classes, all implementing the {`Tempest\Http\Response`} interface, mostly named after HTTP statuses. +Tempest provides several response classes for common use cases, all implementing the {b`Tempest\Http\Response`} interface, mostly named after HTTP statuses. -- `{php}Ok` — the 200 response. Accepts an optional body. -- `{php}Created` — the 201 response. Accepts an optional body. -- `{php}Redirect` — redirects to the specified URI. -- `{php}Back` — redirects to previous page, accepts a fallback. -- `{php}Download` — downloads a file from the browser. -- `{php}File` — shows a file in the browser. -- `{php}Invalid` — a response with form validation errors, redirecting to the previous page. -- `{php}NotFound` — the 404 response. Accepts an optional body. -- `{php}ServerError` — a 500 server error response. +- {b`Tempest\Http\Responses\Ok`} — the 200 response. Accepts an optional body. +- {b`Tempest\Http\Responses\Created`} — the 201 response. Accepts an optional body. +- {b`Tempest\Http\Responses\Redirect`} — redirects to the specified URI. +- {b`Tempest\Http\Responses\Back`} — redirects to previous page, accepts a fallback. +- {b`Tempest\Http\Responses\Download`} — downloads a file from the browser. +- {b`Tempest\Http\Responses\File`} — shows a file in the browser. +- {b`Tempest\Http\Responses\NotFound`} — the 404 response. Accepts an optional body. +- {b`Tempest\Http\Responses\ServerError`} — a 500 server error response. -The following example conditionnally returns a `Redirect`, otherwise letting the user download a file by sending a `Download` response: +The following example conditionally returns a {b`Tempest\Http\Responses\Redirect`}, otherwise letting the user download a file by sending a {b`Tempest\Http\Responses\Download`} response: ```php app/FlightPlanController.php use Tempest\Router\Get; @@ -719,9 +734,7 @@ final readonly class FlightPlanController #[Get('/{flight}/flight-plan/download')] public function download(Flight $flight): Response { - $allowed = /* … */; - - if (! $allowed) { + if (! $this->accessControl->isGranted('view', $flight)) { return new Redirect('/'); } @@ -732,9 +745,7 @@ final readonly class FlightPlanController ### Sending generic responses -It might happen that you need to dynamically compute the response's status code, and would rather not use a condition to send the corresponding response object. - -You may then return an instance of {`Tempest\Http\GenericResponse`}, specifying the status code and an optional body. +When the response's status code needs to be dynamically computed without using a condition to send the corresponding response object, return an instance of {b`Tempest\Http\GenericResponse`} and specify the status code and an optional body. ```php app/CreateFlightController.php use Tempest\Router\Get; @@ -761,9 +772,9 @@ final readonly class CreateFlightController ### Using custom response classes -There are situations where you might send the same kind of response in a lot of places, or you might want to have a proper API for sending a structured response. +There are situations where the same kind of response is sent in multiple places, or where a proper API is needed for sending a structured response. -You may create your own response class by implementing {`Tempest\Http\Response`}, which default implementation is provided by the {`Tempest\Http\IsResponse`} trait: +Custom response classes can be created by implementing {b`Tempest\Http\Response`}, which default implementation is provided by the {b`Tempest\Http\IsResponse`} trait: ```php app/AircraftRegistered.php use Tempest\Http\IsResponse; @@ -787,13 +798,13 @@ final class AircraftRegistered implements Response ### Specifying content types -Tempest is able to automatically infer the response's content type, usually inferred from the request's `Accept` header. +Tempest automatically infers the response's content type, typically from the request's `{txt}Accept` header. -However, you may override the content type manually by specifying the `setContentType` method on `Response` clases. This method accepts a case of {`Tempest\Router\ContentType`}. +However, the content type can be overridden manually by using the `setContentType` method on {b`Tempest\Http\Response`} classes. This method accepts a case of {b`Tempest\Http\ContentType`}. ```php app/JsonController.php use Tempest\Router\Get; -use Tempest\Router\ContentType; +use Tempest\Http\ContentType; use Tempest\Http\Response; use Tempest\Http\Responses\Ok; @@ -811,14 +822,14 @@ final readonly class JsonController ### Post-processing responses -There are some situations in which you may need to act on a response right before it is sent to the client. For instance, you may want to display custom error error pages when an exception occurred, or redirect somewhere instead of displaying the [built-in HTTP 404](/hello-from-the-void){:ssg-ignore="true"} page. +There are situations where actions need to be taken on a response right before it is sent to the client. For instance, custom error pages can be displayed when an exception occurred, or a redirect can be performed instead of displaying the [built-in HTTP 404](/hello-from-the-void){:ssg-ignore="true"} page. -This may be done using a response processor. Similar to [view processors](./02-views.md#pre-processing-views), they are classes that implement the {`Tempest\Response\ResponseProcessor`} interface. In the `process()` method, you may mutate and return the response object: +This can be done using a response processor. Similar to [view processors](./02-views.md#pre-processing-views), these are classes that implement the {b`Tempest\Router\ResponseProcessor`} interface. In the `process()` method, the response object can be mutated and returned: ```php app/ErrorResponseProcessor.php -use function Tempest\view; +use function Tempest\View\view; -final class ErrorResponseProcessor implements ResponseProcessor +final readonly class ErrorResponseProcessor implements ResponseProcessor { public function process(Response $response): Response { @@ -831,41 +842,9 @@ final class ErrorResponseProcessor implements ResponseProcessor } ``` -## Custom route attributes - -It is often a requirement to have a bunch of routes following the same specifications—for instance, using the same middleware, or the same URI prefix. - -To achieve this, you may create your own route attribute, implementing the {`Tempest\Router\Route`} interface. The constructor of the attribute may hold the logic you want to apply to the routes using it. - -```php app/RestrictedRoute.php -use Attribute; -use Tempest\Http\Method; -use Tempest\Router\Route; - -#[Attribute] -final readonly class RestrictedRoute implements Route -{ - public function __construct( - public string $uri, - public Method $method, - public array $middleware, - ) { - $this->uri = $uri; - $this->method = $method; - $this->middleware = [ - AuthorizeUserMiddleware::class, - LogUserActionsMiddleware::class, - ...$middleware, - ]; - } -} -``` - -This attribute can be used in place of the usual route attributes, on controller action methods. - ## Session management -Sessions in Tempest are managed by the {b`Tempest\Http\Session\Session`} class. You can inject it anywhere you need it. As soon as the `Session` is injected, it will be started behind the scenes. +Sessions in Tempest are managed by the {b`Tempest\Http\Session\Session`} class. It can be injected anywhere needed. As soon as the {b`Tempest\Http\Session\Session`} is injected, it is started behind the scenes. ```php use Tempest\Http\Session\Session; @@ -876,7 +855,7 @@ final readonly class TodoController private Session $session, ) {} - #[Post('/select/{todo}'] + #[Post('/select/{todo}')] public function select(Todo $todo): View { if ($this->session->get('selected_todo') === $todo->id) { @@ -892,12 +871,14 @@ final readonly class TodoController ### Flashing values -When you need to "flash" something to the user — in other words: show something once and clear if after refresh — you can use the `flash()` method on the session: +After saving data or performing an action, it is often needed to show users a success message, error notification, or status update that appears once and then disappears after they refresh the page. + +Use the `flash()` method on the {b`Tempest\Http\Session\Session`} to store a value that lasts for the next request only: ```php public function store(Todo $todo): Redirect { - $this->session->flash('message', 'Save was successful'); + $this->session->flash('message', value: 'Save was successful'); return new Redirect('/'); } @@ -909,9 +890,9 @@ Tempest supports file and database-based sessions, the former being the default #### File sessions -When using file-based sessions, which is the default, session data will be stored in files within the specified directory, relative to `.tempest`. You may configure the path and expiration duration like so: +When using file-based sessions, which is the default, session data is stored in files within the specified directory, relative to `.tempest`. The path and expiration duration can be configured as follows: -```php app/Config/session.config.php +```php app/session.config.php use Tempest\Http\Session\Config\FileSessionConfig; use Tempest\DateTime\Duration; @@ -923,15 +904,15 @@ return new FileSessionConfig( #### Database sessions -Tempest provides a database-based session driver, particularly useful for applications that run on multiple servers, as the session data can be shared across all instances. +Tempest provides a database-based session driver, particularly useful for applications that run on multiple servers, as session data can be shared across all instances. -Before using database sessions, a dedicated table is needed. Tempest provides a migration, which may be installed in your project using its installer: +Before using database sessions, a dedicated table is needed. Tempest provides a migration that can be installed using its installer: ```sh ./tempest install sessions:database ``` -This installer will also suggest creating the configuration file that sets up database sessions, with a default expiration of 30 days: +This installer also suggests creating the configuration file that sets up database sessions, with a default expiration of 30 days: ```php app/Sessions/session.config.php use Tempest\Http\Session\Config\DatabaseSessionConfig; @@ -944,15 +925,15 @@ return new DatabaseSessionConfig( ### Session cleaning -Sessions expire based on the last activity time. This means that as long as a user is actively using your application, their session will remain valid. +Sessions expire based on the last activity time. This means that as long as a user is actively using the application, their session remains valid. -Outdated sessions must occasionally be cleaned up. Tempest comes with a built-in command to do so, `session:clean`. This command makes use of the [scheduler](../2-features/11-scheduling.md). If you have scheduling enabled, it will automatically run behind the scenes. +Outdated sessions must occasionally be cleaned up. Tempest provides a built-in command to do so, `session:clean`. This command uses the [scheduler](../2-features/11-scheduling.md): with scheduling enabled, it automatically runs behind the scenes. ## Deferring tasks -It is sometimes needed, during requests, to perform tasks that would take a few seconds to complete. This could be sending an email, or keeping track of a page visit. +During requests, tasks that take a few seconds to complete are sometimes needed. This could be sending an email or keeping track of a page visit. -Tempest provides a way to perform that task after the response has been sent, so the client doesn't have to wait until its completion. This is done by passing a callback to the `defer` function: +Tempest provides a way to perform that task after the response has been sent, so the client does not have to wait until its completion. This is done by passing a callback to the `defer` function: ```php app/TrackVisitMiddleware.php use Tempest\Router\HttpMiddleware; @@ -974,7 +955,7 @@ final readonly class TrackVisitMiddleware implements HttpMiddleware } ``` -The `defer` callback may accept any parameter that the container can inject. +The `defer` callback can accept any parameter that the container can inject. :::warning Task deferring only works if [`fastcgi_finish_request()`](https://www.php.net/manual/en/function.fastcgi-finish-request.php) is available within your PHP installation. If it's not available, deferred tasks will still be run, but the client response will only complete after all tasks have been finished. @@ -982,9 +963,9 @@ Task deferring only works if [`fastcgi_finish_request()`](https://www.php.net/ma ## Testing -Tempest provides a router testing utility accessible through the `http` property of the [`IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php) test case. You may learn more about testing in the [dedicated chapter](./07-testing.md). +Tempest provides a router testing utility accessible through the `http` property of the [`IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php) test case. Learn more about testing in the [dedicated chapter](./07-testing.md). -The router testing utility provides methods for all HTTP verbs. These method return an instance of [`TestResponseHelper`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/Http/TestResponseHelper.php), giving access to multiple assertion methods. +The router testing utility provides methods for all HTTP verbs. These methods return an instance of [`TestResponseHelper`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/Http/TestResponseHelper.php), giving access to multiple assertion methods. ```php tests/ProfileControllerTest.php final class ProfileControllerTest extends IntegrationTestCase diff --git a/docs/1-essentials/02-views.md b/docs/1-essentials/02-views.md index 442f2ec430..ca89e9f51c 100644 --- a/docs/1-essentials/02-views.md +++ b/docs/1-essentials/02-views.md @@ -8,7 +8,7 @@ keywords: "Experimental" Views in Tempest are parsed by Tempest View, our own templating engine. Tempest View uses a syntax that can be thought of as a superset of HTML. If you prefer using a templating engine with more widespread support, [you may also use Blade, Twig, or any other](#using-other-engines) — as long as you provide a way to initialize it. -If you'd like to Tempest View as a standalone component in your project, you can read the documentation on how to do so [here](../5-extra-topics/02-standalone-components.md#tempest-view). +If you'd like to Tempest View as a standalone component in your project, you can read the documentation on how to do so [here](../5-extra-topics/02-standalone-components.md#tempest-view). ### Syntax overview @@ -45,7 +45,7 @@ As specified in the documentation about [sending responses](./01-routing.md#view ```php app/AircraftController.php use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class AircraftController { @@ -73,7 +73,7 @@ return view('views/home.view.php'); A view object is a dedicated class that represent a specific view. -Using view objects will improve static insights in your controllers and view files, and may offer more flexibiltiy regarding how the data may be constructed before being passed on to a view file. +Using view objects will improve static insights in your controllers and view files, and may offer more flexibility regarding how the data may be constructed before being passed on to a view file. ```php final class AircraftController @@ -92,6 +92,8 @@ To create a view object, implement the {`Tempest\View\View`} interface, and add use Tempest\View\View; use Tempest\View\IsView; +use function Tempest\root_path; + final class AircraftView implements View { use IsView; @@ -375,7 +377,7 @@ When a single slot is not enough, names can be attached to them. When using a co ``` -The above example uses a slot named `styles` in its `` element. The `` element has a default, unnamed slot. A view component may use `` and optionally refer to the `styles` slot using the syntax mentionned above, or simply provide content that will be injected in the default slot: +The above example uses a slot named `styles` in its `` element. The `` element has a default, unnamed slot. A view component may use `` and optionally refer to the `styles` slot using the syntax mentioned above, or simply provide content that will be injected in the default slot: ```html index.view.php @@ -447,7 +449,7 @@ $title = 'foo'; - + ``` ```php @@ -463,7 +465,6 @@ final class HomeController ``` ```html x-base.view.php -

{{ $siteTitle }}

``` @@ -634,7 +635,7 @@ As a bare minimum setup, you can create an instance of the renderer by calling ` ```php use Tempest\View\Renderers\TempestViewRenderer; -use function Tempest\view; +use function Tempest\View\view; $renderer = TempestViewRenderer::make(); @@ -673,14 +674,14 @@ You can choose whichever way you prefer. Chances are that, if you use the minima ### A note on caching -When you're using the minimal setup, view caching can be enabled by passing in a `$viewCache` paremeter into `TempestViewRenderer::make()`: +When you're using the minimal setup, view caching can be enabled by passing in a `$viewCache` parameter into `TempestViewRenderer::make()`: ```php use Tempest\View\Renderers\TempestViewRenderer; use Tempest\View\ViewCache; $renderer = TempestViewRenderer::make( - cache: ViewCache::enabled(), + viewCache: ViewCache::create(), ); ``` @@ -690,12 +691,11 @@ It's recommended to turn view caching on in production environments. To clear th use Tempest\View\Renderers\TempestViewRenderer; use Tempest\View\ViewCache; -$viewCache = ViewCache::enabled(); - +$viewCache = ViewCache::create(); $viewCache->clear(); $renderer = TempestViewRenderer::make( - cache: $viewCache, + viewCache: $viewCache, ); ``` @@ -825,21 +825,27 @@ use Tempest\Container\Container; use Tempest\Container\DynamicInitializer; use Tempest\Container\Singleton; use Tempest\Reflection\ClassReflector; +use UnitEnum; final readonly class BladeInitializer implements DynamicInitializer { - public function canInitialize(ClassReflector $class): bool + public function canInitialize(ClassReflector $class, null|string|UnitEnum $tag): bool { + if (! class_exists(Blade::class)) { + return false; + } + return $class->getName() === Blade::class; } #[Singleton] - public function initialize(ClassReflector $class, Container $container): object + public function initialize(ClassReflector $class, null|string|UnitEnum $tag, Container $container): object { $bladeConfig = $container->get(BladeConfig::class); return new Blade( viewPaths: $bladeConfig->viewPaths, + cachePath: Tempest\internal_storage_path($bladeConfig->cachePath ?? 'cache/blade'), ); } } diff --git a/docs/1-essentials/03-database.md b/docs/1-essentials/03-database.md index 966938d5c8..793273d159 100644 --- a/docs/1-essentials/03-database.md +++ b/docs/1-essentials/03-database.md @@ -1,6 +1,6 @@ --- title: Database -description: "Tempest's database component allows you to persist data to SQLite, MySQL and PostgreSQL databases. You can use our powerful query builder, or build truly decoupled models to interact with your database of choice." +description: "Tempest's database component provides data persistence to SQLite, MySQL, and PostgreSQL databases through a query builder and decoupled model architecture." keywords: ["experimental", "orm", "database", "sqlite", "postgresql", "pgsql", "mysql", "query", "sql", "connection", "models"] --- @@ -10,7 +10,7 @@ Tempest's database component is currently experimental and is not covered by our ## Connecting to a database -By default, Tempest will connect to a local SQLite database located in its internal storage, `.tempest/database.sqlite`. You may override the default database connection by creating a [configuration file](../1-essentials/06-configuration.md#configuration-files): +By default, Tempest connects to a local SQLite database located in its internal storage, `.tempest/database.sqlite`. The default database connection can be overridden by creating a [configuration file](../1-essentials/06-configuration.md#configuration-files): ```php app/Config/database.config.php use Tempest\Database\Config\SQLiteConfig; @@ -21,7 +21,7 @@ return new SQLiteConfig( ); ``` -Alternatively, you can connect to another database by returning another configuration object from file. You may choose between {b`Tempest\Database\Config\SQLiteConfig`}, {b`Tempest\Database\Config\MysqlConfig`}, or {b`Tempest\Database\Config\PostgresConfig`}: +Alternatively, connect to another database by returning a different configuration object from the file. Available configuration classes include {b`Tempest\Database\Config\SQLiteConfig`}, {b`Tempest\Database\Config\MysqlConfig`}, and {b`Tempest\Database\Config\PostgresConfig`}: ```php app/Config/database.config.php use Tempest\Database\Config\PostgresConfig; @@ -38,7 +38,7 @@ return new PostgresConfig( ## Querying the database -There are multiple ways to query the database, but all of them eventually do the same thing: execute a {b`Tempest\Database\Query`} on the {b`Tempest\Database\Database`} class. The most straight-forward way to query the database is thus by injecting {b`Tempest\Database\Database`}: +Multiple approaches exist for querying the database, all of which execute a {b`Tempest\Database\Query`} on the {b`Tempest\Database\Database`} class. The most straightforward approach is to inject {b`Tempest\Database\Database`}: ```php use Tempest\Database\Database; @@ -60,7 +60,7 @@ final class BookRepository } ``` -Manually building and executing queries gives you the most flexibility. However, using Tempest's query builder is more convenient—it gives you fluent methods to build queries without needing to worry about database-specific syntax differences. +Manually building and executing queries provides maximum flexibility. Tempest's query builder offers a more convenient approach with fluent methods that abstract database-specific syntax differences. ```php use function Tempest\Database\query; @@ -71,13 +71,13 @@ final class BookRepository { return query('books') ->select('id', 'title') - ->where('id = ?', $id) + ->where('id', $id) ->first(); } } ``` -If preferred, you can combine both methods and use the query builder to build a query that's executed on a database: +Both methods can be combined by using the query builder to construct a query that is then executed on a database: ```php use Tempest\Database\Database; @@ -100,33 +100,11 @@ final class BookRepository } ``` -### Query builders - -There are multiple types of query builders, all of them are available via the `query()` function. If you prefer to manually create a query builder though, you can also instantiate them directly: - -```php -use Tempest\Database\Builder\QueryBuilders\SelectQueryBuilder; - -$builder = new SelectQueryBuilder('books'); -``` - -Currently, there are five query builders shipped with Tempest: - -- {`Tempest\Database\Builder\QueryBuilders\SelectQueryBuilder`} -- {`Tempest\Database\Builder\QueryBuilders\InsertQueryBuilder`} -- {`Tempest\Database\Builder\QueryBuilders\UpdateQueryBuilder`} -- {`Tempest\Database\Builder\QueryBuilders\DeleteQueryBuilder`} -- {`Tempest\Database\Builder\QueryBuilders\CountQueryBuilder`} - -Each of them has their own unique methods that work within their scope. You can discover them via your IDE, or check them out on [GitHub](https://github.com/tempestphp/tempest-framework/tree/main/packages/database/src/Builder/QueryBuilders). - -Finally, you can make your own query builders if you want by implementing the {b`Tempest\Database\Builder\QueryBuilders\BuildsQuery`} interface. - ## Models -A common use case in many applications is to represent persisted data as objects within your codebase. This is where model classes come in. Tempest tries to decouple models as best as possible from the database, so any object with public typed properties can represent a model. +A common use case in many applications is to represent persisted data as objects within the codebase. Model classes fulfill this purpose. Tempest decouples models from the database as much as possible, allowing any object with public typed properties to represent a table. -These objects don't have to implement any interface—they may be plain-old PHP objects: +These objects do not require implementing any interface—they can be plain PHP objects: ```php app/Book.php use Tempest\Validation\Rules\HasLength; @@ -144,22 +122,20 @@ final class Book } ``` -Because model objects aren't tied to the database specifically, Tempest's [mapper](../2-features/01-mapper.md) can map data from many different sources to them. For instance, you can persist your models as JSON instead of a database, if you want to: +Because model objects are not tied specifically to the database, Tempest's [mapper](../2-features/01-mapper.md) can map data from many different sources to them. For instance, models can be persisted as JSON: ```php -use function Tempest\map; +use function Tempest\Mapper\map; $books = map($json)->collection()->to(Book::class); // from JSON source to Book collection $json = map($books)->toJson(); // from Book collection to JSON ``` -That being said, persistence most often happens on the database level, so let's take a look at how to deal with models that persist to the database. - ### Models and query builders -The easiest way of persisting models to a database is by using the query builder. Tempest's query builder cannot just deal with tables and arrays, but also knows how to map data from and to model objects. All you need to do is specify which class you want to query, and Tempest will do the rest. +The query builder provides a straightforward approach to persisting models to a database. It can work with tables and arrays as well as map data to and from model objects. Specify the class to query, and Tempest handles the mapping. -In the following example, we'll query the table related to the `Book` model, we'll select all fields, load its related `chapters` and `author` as well, specify the ID of the book we're searching, and then return the first result: +The following example selects all fields from the table related to the `Book` model, loads the related `chapters` and `author`, filters by the book ID, and returns the first result: ```php use App\Models\Book; @@ -172,23 +148,25 @@ final class BookRepository return query(Book::class) ->select() ->with('chapters', 'author') - ->where('id = ?', $id) + ->where('id', $id) ->first(); } } ``` -Tempest will infer all relation-type information from the model class, specifically by looking at the property types. For example, a property with the `Author` type is assumed to be a "belongs to" relation, while a property with the `/** @var \App\Chapter[] */` docblock is assumed to be a "has many" relation on the `Chapter` model. +Tempest infers all relation-type information from the model class by analyzing property types. For example, a property with the `Author` type is assumed to be a "belongs to" relation, while a property with the `/** @var \App\Books\Chapter[] */` docblock is assumed to be a "has many" relation on the `Chapter` model. -Apart from selecting models, it's of course possible to use any other query builder with them as well: +Beyond selecting models, any query builder can be used with model objects: ```php use App\Models\Book; -use function Tempest\Database\query; +use Tempest\Database\PrimaryKey; + +;use function Tempest\Database\query; final class BookRepository { - public function create(Book $book): Id + public function create(Book $book): PrimaryKey { return query(Book::class) ->insert($book) @@ -197,37 +175,33 @@ final class BookRepository } ``` -:::info -Currently it's not possible to insert or update {b`Tempest\Database\HasMany`} or {b`Tempest\Database\HasOne`} relations directly by inserting or updating the parent model. You should first insert or update the parent model and then insert or update the child models separately. This shortcoming will be fixed in [the future](https://github.com/tempestphp/tempest-framework/issues/1087). -::: - ### Model relations -As mentioned before, Tempest will infer relations based on type information it gets from the model class. A public property with a reference to another class will be assumed to be a {b`Tempest\Database\BelongsTo`} relation, while a property with a docblock that defines an array type is assumed to be a {b`Tempest\Database\HasMany`} relation. +Tempest infers relations based on type information from the model class. A public property with a reference to another class is assumed to be a {b`Tempest\Database\BelongsTo`} relation, while a property with a docblock that defines an array type is assumed to be a {b`Tempest\Database\HasMany`} relation. ```php use App\Author; final class Book { - // This is a BelongsTo relation: public ?Author $author = null; + // ^ BelongsTo relation - // This is a HasMany relation: - /** @var \App\Chapter[] */ + /** @var \App\Books\Chapter[] */ public array $chapters = []; + // ^ HasMany relation } ``` :::warning -Relation types in docblocks must always be fully qualified, and not use short class names. +Due to a restriction with reflection, relation types in docblocks must always be fully qualified. Short class names are not supported. ::: -Tempest will infer all the information it needs to build the right queries for you. However, there might be cases where property names and type information don't map one-to-one on your database schema. In that case you can use dedicated attributes to define relations. - ### Relation attributes -Tempest will infer relation names based on property names and types. However, you can override these names with the {b`#[Tempest\Database\HasMany]`}, {b`#[Tempest\Database\HasOne]`}, and {b`#[Tempest\Database\BelongsTo]`} attributes. These attributes all take two optional arguments: +Tempest infers all information needed to build queries. When property names and type information do not map one-to-one to the database schema, dedicated attributes can be used to define relations. + +Available attributes are {b`#[Tempest\Database\HasMany]`}, {b`#[Tempest\Database\HasOne]`}, and {b`#[Tempest\Database\BelongsTo]`}. They accept two arguments: - `ownerJoin`, which is used to build the owner's side of join query, - `relationJoin`, which is used to build the relation's side of the join query. @@ -243,17 +217,19 @@ final class Book public ?Author $author = null; /** @var \App\Chapter[] */ - #[HasMany(relationJoin: 'chapters.uuid', ownerJoin: 'books.chapter_uuid')] + #[HasMany(ownerJoin: 'chapters.book_uuid', relationJoin: 'books.uuid')] public array $chapters = []; - #[HasOne(relationJoin: 'books.uuid', ownerJoin: 'isbns.book_uuid')] - public Isbn $isbn = []; + #[HasOne(ownerJoin: 'isbns.book_uuid', relationJoin: 'books.uuid')] + public ?Isbn $isbn = null; } ``` -The **owner** part of the relation resembles the table that _owns_ the relation. In other words: the table which has a column referencing another table. The **relation** part resembles the table that's _being referenced by another table_. This is why the {b`Tempest\Database\BelongsTo`} relation starts with _the owner join_, while both {b`Tempest\Database\HasMany`} and {b`Tempest\Database\HasOne`} start with _the relation join_. +The _owner_ part of the relation represents the table that _owns_ the relation—the table with a column referencing another table. The _relation_ part represents the table that is _being referenced by another table_. -Finally, it's important to mention that you don't have to write the full owner or relation join including both the table and the field. You can also use the field name without the table name, in which case the table name is inferred from the related model: +The {b`Tempest\Database\BelongsTo`} relation starts with _the owner join_, while both {b`Tempest\Database\HasMany`} and {b`Tempest\Database\HasOne`} start with _the relation join_. + +The full owner or relation join does not need to include both the table and field names. Field names can be specified without the table name, in which case the table name is inferred from the related model: ```php use Tempest\Database\BelongsTo; @@ -266,82 +242,62 @@ final class Book public ?Author $author = null; /** @var \App\Chapter[] */ - #[HasMany(relationJoin: 'uuid', ownerJoin: 'chapter_uuid')] + #[HasMany(ownerJoin: 'chapter_uuid', relationJoin: 'uuid')] public array $chapters = []; - #[HasOne(relationJoin: 'uuid', ownerJoin: 'book_uuid')] - public Isbn $isbn = []; -} -``` - -### Hashed properties - -The {`#[Tempest\Database\Hashed]`} attribute will hash the model's property during serialization. If the property was already hashed, Tempest will detect that and avoid re-hashing it. - -```php -final class User -{ - public PrimaryKey $id; - - public string $email; - - #[Hashed] - public ?string $password; + #[HasOne(ownerJoin: 'book_uuid', relationJoin: 'uuid')] + public ?Isbn $isbn = null; } ``` -Hashing requires the `SIGNING_KEY` environment variable to be set, as it's used as the hashing key. +### Using UUIDs as primary keys -### Encrypted properties +By default, Tempest uses auto-incrementing integers as primary keys. UUIDs can be used as primary keys instead by annotating the {b`Tempest\Database\PrimaryKey`} property with the {b`#[Tempest\Database\Uuid]`} attribute. Tempest automatically generates a UUID v7 when a new model is created: -The {`#[Tempest\Database\Encrypted]`} attribute will encrypt the model's property during serialization and decrypt it during deserialization. If the property was already encrypted, Tempest will detect that and avoid re-encrypting it. +```php app/Books/Book.php +use Tempest\Database\PrimaryKey; +use Tempest\Database\Uuid; -```php -final class User +final class Book { - // ... + #[Uuid] + public PrimaryKey $uuid; - #[Encrypted] - public ?string $accessToken; + public function __construct( + public string $title, + public string $author_name, + ) {} } ``` -The encryption key is taken from the `SIGNING_KEY` environment variable. - -### DTO properties +Within migrations, specify `uuid: true` to the `primary()` method, or use `uuid()` directly: -Sometimes, you might want to store data objects as-is in a table, without there needing to be a relation to another table. To do so, it's enough to add a serializer and caster to the data object's class, and Tempest will know that these objects aren't meant to be treated as database models. Next, you can store the object's data as a json field on the table (see [migrations](#migrations) for more info). - -```php -use Tempest\Database\IsDatabaseModel; -use Tempest\Mapper\CastWith; -use Tempest\Mapper\SerializeWith; -use Tempest\Mapper\Casters\DtoCaster; -use Tempest\Mapper\Serializers\DtoSerializer; - -final class DebugItem -{ - use IsDatabaseModel; - - /* … */ - - public Backtrace $backtrace, -} +```php app/Books/CreateBooksTable.php +use Tempest\Database\MigratesUp; +use Tempest\Database\QueryStatement; +use Tempest\Database\QueryStatements\CreateTableStatement; -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] -final class Backtrace +final class CreateBooksTable implements MigratesUp { - // This object won't be considered a relation, - // but rather serialized and stored in a JSON column. + public string $name = '2024-08-12_create_books_table'; - public array $frames = []; + public function up(): QueryStatement + { + return new CreateTableStatement('books') + ->primary('uuid', uuid: true) + ->text('title') + ->text('author_name'); + } } ``` +:::warning +Currently, the [`IsDatabaseModel`](#the-is-database-model-trait) trait already provides a primary `$id` property. It is therefore not possible to use UUIDs alongside `IsDatabaseModel`. +::: + ### Table names -Tempest will infer the table name for a model class based on the model's classname. By default the table name will by the pluralized, `snake_cased` version of that classname. You can override this name by using the {b`Tempest\Database\Table`} attribute: +Tempest infers the table name for a model class based on the model's classname. By default, the table name is the pluralized, `snake_cased` version of the base class name. This can be overridden using the {b`Tempest\Database\Table`} attribute: ```php use Tempest\Database\Table; @@ -353,9 +309,13 @@ final class Book } ``` -You can also configure a completely new naming strategy for all your models at once by creating a {b`Tempest\Database\Tables\NamingStrategy`} and attaching it to your database config: +It is possible to define your own convention for naming tables without specifying the {b`Tempest\Database\Table`} attribute on all your models. To do so, set the `namingStrategy` parameter of your database configuration to a {b`Tempest\Database\Tables\NamingStrategy`} instance. -```php +By default, Tempest provides a {b`Tempest\Database\Tables\PascalCaseStrategy`} and {b`Tempest\Database\Tables\PluralizedSnakeCaseStrategy`} strategy, the latter being the default. Of course, custom strategies can be implemented as needed: + +:::code-group + +```php app/Database/PrefixedPascalCaseStrategy.php use Tempest\Database\Tables\NamingStrategy; use function Tempest\Support\str; @@ -371,7 +331,7 @@ final class PrefixedPascalCaseStrategy implements NamingStrategy } ``` -```php app/Config/database.config.php +```php app/database.config.php use Tempest\Database\Config\SQLiteConfig; return new SQLiteConfig( @@ -380,9 +340,94 @@ return new SQLiteConfig( ); ``` +::: + +### Data transfer object properties + +Arbitrary objects can be stored in a `json` column when they are not part of the relational schema. Annotate the class with {b`#[Tempest\Mapper\SerializeAs]`} and provide a unique identifier to represent the object. The identifier must map to a single, distinct class. + +:::code-group + +```php app/User.php +use Tempest\Mapper\SerializeAs; + +final class User implements Authenticatable +{ + public PrimaryKey $id; + + public function __construct( + public string $email, + #[Hashed, SensitiveParameter] + public ?string $password, + public Settings $settings, + ) {} +} +``` + +```php app/Settings.php +#[SerializeAs('user_settings')] +final class Settings +{ + public function __construct( + public readonly Theme $theme, + public readonly bool $hide_sidebar_by_default, + ) {} +} +``` + +```php app/Theme.php +enum Theme: string +{ + case DARK = 'dark'; + case LIGHT = 'light'; + case AUTO = 'auto'; +} +``` + +::: + +### Hashed properties + +The {b`#[Tempest\Database\Hashed]`} attribute hashes the model's property during serialization. If the property is already hashed, Tempest detects this and avoids re-hashing. Common use cases include passwords, tokens, and other sensitive values. + +```php app/User.php +final class User +{ + public PrimaryKey $id; + + public function __construct( + public string $email, + #[Hashed, SensitiveParameter] + public ?string $password, + ) {} +} +``` + +:::info +Hashing requires the `SIGNING_KEY` environment variable to be set, as it is used as the hashing key. +::: + +### Encrypted properties + +The {b`#[Tempest\Database\Encrypted]`} attribute encrypts the model's property during serialization and decrypts it during deserialization. If the property is already encrypted, Tempest detects this and avoids re-encrypting. + +```php app/User.php +final class User +{ + // ... + + #[Encrypted] + public ?string $accessToken; +} +``` + +:::info +Encryption uses the `SIGNING_KEY` environment variable as the encryption key. +::: + ### Virtual properties -By default, all public properties are considered to be part of the model's query fields. To exclude a field from the database mapper, you may use the {b`Tempest\Database\Virtual`} attribute. +By default, all public properties are considered part of the model's query fields. To exclude a field from the database mapper, use the {b`#[Tempest\Database\Virtual]`} attribute. ```php use Tempest\Database\Virtual; @@ -397,16 +442,18 @@ final class Book #[Virtual] public DateTime $saleExpiresAt { - get => $this->publishedAt->add(Duration::days(5))); + get => $this->publishedAt->add(Duration::days(5)); } } ``` ### The `IsDatabaseModel` trait -People who are used to Eloquent might prefer a more "active record" style to handling their models. In that case, there's the {b`Tempest\Database\IsDatabaseModel`} trait which you can use in your model classes: +The {b`Tempest\Database\IsDatabaseModel`} trait provides an active record pattern. This trait enables database interaction via static methods on the model class itself. -```php +:::code-group + +```php app/Book.php use Tempest\Database\IsDatabaseModel; use Tempest\Validation\Rules\HasLength; use App\Author; @@ -425,9 +472,7 @@ final class Book } ``` -Thanks to the {b`Tempest\Database\IsDatabaseModel`} trait, you can directly interact with the database via the model class: - -```php +```php "Query examples" $book = Book::create( title: 'Timeline Taxi', author: $author, @@ -439,8 +484,8 @@ $book = Book::create( ); $books = Book::select() - ->where('publishedAt > ?', new DateTimeImmutable()) - ->orderBy('title DESC') + ->whereAfter('publishedAt', DateTime::now()) + ->orderBy('title', Direction::DESC) ->limit(10) ->with('author') ->all(); @@ -448,22 +493,28 @@ $books = Book::select() $books[0]->chapters[2]->delete(); ``` +::: + ## Migrations -When you're persisting objects to the database, you'll need table to store its data in. A migration is a file instructing the framework how to manage that database schema. Tempest uses migrations to create and update databases across different environments. +When persisting objects to the database, a table is required to store the data. A migration is a file that instructs the framework how to manage the database schema. + +Tempest uses migrations to create and update databases across different environments in a consistent way. ### Writing migrations -Thanks to [discovery](../4-internals/02-discovery), `.sql` files and classes implementing the {b`Tempest\Database\DatabaseMigration`} interface are automatically registered as migrations, which means they can be stored anywhere. +Classes implementing the {b`Tempest\Database\MigratesUp`} or {b`Tempest\Database\MigratesDown`} interface and `.sql` files are automatically [discovered](../1-essentials/05-discovery) and registered as migrations. These files can be stored anywhere in the application. -```php +:::code-group + +```php app/CreateBooksTable.php use Tempest\Database\MigratesUp; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; -final class CreateBookTable implements MigratesUp +final class CreateBooksTable implements MigratesUp { - public string $name = '2024-08-12_create_book_table'; + public string $name = '2024-08-12_create_books_table'; public function up(): QueryStatement { @@ -485,15 +536,19 @@ CREATE TABLE Publisher ); ``` +::: + :::info -The file name of `{txt}.sql` migrations and the `{txt}{:hl-type:$name:}` property of `DatabaseMigration` classes are used to determine the order in which they are applied. A good practice is to use their creation date as a prefix. +The file name of `{txt}.sql` migrations and the `{txt}{:hl-type:$name:}` property of `DatabaseMigration` classes determine the order in which migrations are applied. Using the creation date as a prefix ensures chronological ordering. ::: -Note that when using migration classes combined with query statements, Tempest will take care of the SQL dialect for you, there's support for MySQL, Postgresql, and SQLite. When using raw sql files, you'll have to pick a hard-coded SQL dialect, depending on your database requirements. +When using migration classes, Tempest handles the SQL dialect automatically with support for MySQL, PostgreSQL, and SQLite. When using raw SQL files, a hard-coded SQL dialect must be chosen based on database requirements. -### Up- and down migrations +### Up and down migrations -Tempest's recommendation is to only use up-migrations, which move the database's schema forward. There is also the option to create down-migrations, migrations that can roll back the schema of the database to a previous state. Dealing with down migrations is tricky, though, especially in production environments. That's why you need to explicitly implement another interface to do so: {`Tempest\Database\MigratesDown`}. +Up-migrations move the database schema forward. Down-migrations roll back the database schema to a previous state. + +Down migrations are complex to test and manage, especially in production environments. For this reason, they require explicitly implementing the {`Tempest\Database\MigratesDown`} interface. ```php use Tempest\Database\MigratesDown; @@ -513,53 +568,52 @@ final class CreateBookTable implements MigratesDown ### Applying migrations -A few [console commands](../3-console/02-building-console-commands) are provided to work with migrations. They are used to apply, rollback, or erase and re-apply them. When deploying your application to production, you should use the `php tempest migrate:up` to apply the latest migrations. +Several [console commands](../3-console/02-building-console-commands) are provided to work with migrations. These commands apply, roll back, or erase and re-apply migrations. + +When deploying the application to production, use `php tempest migrate:up` to apply the latest migrations. ```sh -{:hl-comment:# Applies migrations that have not been run in the current environment:} +{:hl-comment:# Apply migrations not yet run in the current environment} ./tempest migrate:up -{:hl-comment:# Execute the down migrations:} -./tempest migrate:down - -{:hl-comment:# Drops all tables and rerun migrate:up:} +{:hl-comment:# Drop all tables and rerun migrate:up} ./tempest migrate:fresh -{:hl-comment:# Validates the integrity of migration files:} +{:hl-comment:# Validate the integrity of migration files} ./tempest migrate:validate ``` ### Validating migrations -By default, an integrity check is done before applying database migrations with the `migrate:up` and `migrate:fresh` commands. This validation works by comparing the current migration hash with the one stored in the `migrations` table, if it was already applied in your environment. +By default, an integrity check is performed before applying database migrations with the `migrate:up` and `migrate:fresh` commands. This validation compares the current migration hash with the one stored in the `migrations` table, if it was already applied in the environment. -If a migration file has been tampered with, the command will report it as a validation failure. Note that you may opt-out of this behavior by using the `--no-validate` argument. +If a migration file has been tampered with, the command reports it as a validation failure. This behavior can be disabled using the `--no-validate` argument. -Additionally, you may use the `migrate:validate` command to validate the integrity of migrations at any point, in any environment: +The `migrate:validate` command can be used to validate the integrity of migrations at any point in any environment: ```sh ./tempest migrate:validate ``` -:::tip -Only the actual SQL query of a migration, minified and stripped of comments, is hashed during validation. This means that code-style changes, such as indentation, formatting, and comments will not impact the validation process. +:::info +Only the actual SQL query of a migration, minified and stripped of comments, is hashed during validation. Code-style changes, such as indentation, formatting, and comments do not impact the validation process. ::: ### Rehashing migrations -You may use the `migrate:rehash` command to bypass migration integrity checks and update the hashes of migrations in the database. +The `migrate:rehash` command can be used to bypass migration integrity checks and update the hashes of migrations in the database. ```sh ./tempest migrate:rehash ``` :::warning -Note that deliberately bypassing migration integrity checks may result in a broken database state. Only use this command when necessary if you are confident that your migration files are correct and consistent across environments. +Bypassing migration integrity checks may result in a broken database state. Use this command only when migration files are confirmed to be correct and consistent across environments. ::: ## Database seeders -Whenever you need to fill your database with dummy data, you can provide database seeders. These are classes that are used to fill your database with whatever data you want. To get started, you should implement the {`\Tempest\Database\DatabaseSeeder`} interface. +Database seeders populate the database with data. These classes can fill the database with any required data. To create a seeder, implement the {b`\Tempest\Database\DatabaseSeeder`} interface. ```php use Tempest\Database\DatabaseSeeder; @@ -570,18 +624,16 @@ final class BookSeeder implements DatabaseSeeder public function run(null|string|UnitEnum $database): void { query(Book::class) - ->insert( - title: 'Timeline Taxi', - ) + ->insert(title: 'Timeline Taxi') ->onDatabase($database) ->execute(); } } ``` -Note how the `$database` property is passed into the `run()` method. In case a user has specified a database for this seeder to run on, this property will reflect that choice. +The `$database` property is passed into the `run()` method. If a database has been specified for the seeder, this property reflects that choice. -Running database seeders can be done in two ways: either via the `database:seed` command, or via the `migrate:fresh` command. Not that `database:seed` will always append the seeded data on the existing database. +Database seeders can be run in two ways: via the `database:seed` command or via the `migrate:fresh` command. Note that `database:seed` always _appends_ the seeded data to the existing database. ```console ./tempest database:seed @@ -590,9 +642,9 @@ Running database seeders can be done in two ways: either via the `database:seed` ### Multiple seeders -If you want to, you can create multiple seeder classes. Each seeder class could be used to bring the database into a specific state, or you could use multiple seeder classes to seed specific parts of your database. +Multiple seeder classes can be created. Each seeder class can bring the database into a specific state or seed specific parts of the database. -Whenever you have multiple seeder classes, Tempest will prompt you which ones to run: +When multiple seeder classes exist, Tempest prompts for selection: ```console ./tempest database:seed @@ -615,7 +667,7 @@ Both the `database:seed` and `migrate:fresh` commands also allow to pick one spe ### Seeding on multiple databases -Seeders have built-in support for multiple databases, which you can specify with the `--database` option. Continue reading to learn more about multiple databases. +Seeders support multiple databases via the `--database` option. See the [Multiple databases](#multiple-databases) section for more information. ```console ./tempest database:seed --database="backup" @@ -624,17 +676,15 @@ Seeders have built-in support for multiple databases, which you can specify with ## Multiple databases -Tempest supports connecting to multiple databases at once. This can, for example, be useful to transfer data between databases or build multi-tenant systems. - -:::warning -Multiple database support on Windows is currently untested. We welcome anyone who wants to [contribute](https://github.com/tempestphp/tempest-framework/issues/1271). -::: +Tempest supports connecting to multiple databases simultaneously. This is useful for transferring data between databases or building multi-tenant systems. ### Connecting to multiple databases -If you want to connect to multiple databases, you should make multiple database config files and attach a tag to each database config object: +To connect to multiple databases, create multiple database config files and attach a tag to each database config object: -```php app/Config/database.config.php +:::code-group + +```php app/database.config.php use Tempest\Database\Config\SQLiteConfig; return new SQLiteConfig( @@ -643,7 +693,7 @@ return new SQLiteConfig( ); ``` -```php app/Config/database-backup.config.php +```php app/database-backup.config.php use Tempest\Database\Config\SQLiteConfig; return new SQLiteConfig( @@ -652,9 +702,11 @@ return new SQLiteConfig( ); ``` -When preferred, you can use a self-defined enum as the tag as well: +::: + +Enums provide better refactorability when used as tags: -```php app/Config/database-backup.config.php +```php app/database-backup.config.php use Tempest\Database\Config\SQLiteConfig; use App\Database\DatabaseType; @@ -665,12 +717,12 @@ return new SQLiteConfig( ``` :::info -Note that the _default_ connection will always be the connection without a tag. +The default connection is the connection without a tag. ::: ### Querying multiple databases -With multiple databases configured, how do you actually use them when working with queries or models? There are several ways of doing so. The first approach is to manually inject separate database instances by using their tag: +With multiple databases configured, several approaches exist for using them when working with queries or models. The first approach is to inject separate database instances using their tags: ```php use Tempest\Database\Database; @@ -700,7 +752,7 @@ final class DatabaseBackupCommand } ``` -It might be quite cumbersome to write so much code everywhere if you're working with multiple databases though. That's why there's a shorthand available that doesn't require you to inject multiple database instances: +A shorthand approach is available that does not require injecting multiple database instances: ```php use App\Database\DatabaseType; @@ -724,7 +776,7 @@ final class DatabaseBackupCommand } ``` -Note that the same is possible when using active-record style models: +The same approach works with active-record style models: ```php use App\Database\DatabaseType; @@ -757,45 +809,41 @@ To run migrations on a specific database, you must specify the `database` flag t ``` :::info -When no database is provided, the default database will be used, this is the database that doesn't have a specific tag attached to it. +When no database is specified, the default database is used. The default database is the one without a tag. ::: ### Database-specific migrations -Sometimes you might only want to run specific migrations on specific databases. Any database migration class may implement the {b`Tempest\Database\ShouldMigrate`}, which adds a `shouldMigrate()` method to determine whether a migration should run or not, based on the database: +Some migrations may need to run only on specific databases. Any database migration class can implement {b`Tempest\Database\ShouldMigrate`}, which adds a `shouldMigrate()` method to determine whether a migration should run based on the database: ```php use Tempest\Database\Database; -use Tempest\Database\DatabaseMigration; +use Tempest\Database\MigratesUp; use Tempest\Database\ShouldMigrate; -final class MigrationForBackup implements DatabaseMigration, ShouldMigrate +final class MigrationForBackup implements MigratesUp, ShouldMigrate { public string $name = '…'; public function shouldMigrate(Database $database): bool { - return $database->tag === 'backup'; + return $database->tag === DatabaseType::BACKUP; } public function up(): QueryStatement { /* … */ } - - public function down(): QueryStatement - { /* … */ } } ``` ### Dynamic databases -In systems with dynamic databases, like, for example, multi-tenant systems; you might not always have a hard-coded tag available to configure and resolve the right database. In those cases, it's trivial to add as many dynamic databases as you'd like: +In systems with dynamic databases, such as multi-tenant systems, a hard-coded tag may not always be available to configure and resolve the correct database. In these cases, dynamic databases can be added as needed: ```php final class ConnectTenant { public function __invoke(string $tenantId): void { - // Use any database config you'd like: $this->container->config(new SQLiteConfig( path: __DIR__ . "/tenant-{$tenantId}.sqlite", tag: $tenantId, @@ -804,7 +852,7 @@ final class ConnectTenant } ``` -Furthermore, you can run migrations programmatically on such dynamically defined databases using the {`Tempest\Database\Migrations\MigrationManager`}: +Migrations can be run programmatically on dynamically defined databases using the {b`Tempest\Database\Migrations\MigrationManager`}: ```php use Tempest\Database\Migrations\MigrationManager; @@ -819,7 +867,7 @@ final class OnboardTenant { $setupMigrations = [ new CreateMigrationsTable(), - // … + // Additional migrations ]; foreach ($setupMigrations as $migration) { @@ -829,7 +877,7 @@ final class OnboardTenant } ``` -Finally, you should register your dynamic database connections as well within the entry points of your application. This could be done with [middleware](/main/essentials/routing#route-middleware), or with a [kernel event hook](/main/extra-topics/package-development#provider-classes); that's up to you: +Dynamic database connections should be registered within the application's entry points. This can be accomplished with [middleware](/main/essentials/routing#route-middleware) or with a [kernel event hook](/main/extra-topics/package-development#provider-classes): ```php use Tempest\Container\Container; @@ -845,9 +893,9 @@ final class ConnectTenantMiddleware implements HttpMiddleware public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { - $tenantId = // Resolve tenant ID from the request + $tenantId = // Tenant ID resolution from request - (new ConnectTennant)($tenantId); + (new ConnectTenant)($tenantId); return $next($request); } diff --git a/docs/1-essentials/04-console-commands.md b/docs/1-essentials/04-console-commands.md index 31254e37ee..be30b55d98 100644 --- a/docs/1-essentials/04-console-commands.md +++ b/docs/1-essentials/04-console-commands.md @@ -5,7 +5,7 @@ description: "Learn how to write console commands with a modern, minimal syntax. ## Overview -Tempest leverages [discovery](../4-internals/02-discovery.md) to find class methods tagged with the {b`#[Tempest\Console\ConsoleCommand]`} attribute. Such methods will automatically be available as console commands through the `./tempest` executable. +Tempest leverages [discovery](./05-discovery.md) to find class methods tagged with the {b`#[Tempest\Console\ConsoleCommand]`} attribute. Such methods will automatically be available as console commands through the `./tempest` executable. Additionally, Tempest supports [console middleware](#middleware), which makes it easier to build some console features. @@ -120,7 +120,7 @@ final readonly class TrackOperatingAircraft } ``` -Argument description are visible when using the `--help` flag during command invokation. +Argument description are visible when using the `--help` flag during command invocation. ```console ./tempest aircraft:track --help @@ -238,6 +238,41 @@ Tempest console comes with a range of interactive components that can be used to Interactive components are only supported on Mac and Linux. On Windows, Tempest will fall back to non-interactive versions of these components. ::: +## Shell completion + +Tempest provides shell completion for Zsh and Bash. This allows you to press `Tab` to autocomplete command names and options. + +### Installing completions + +Run the install command and follow the prompts: + +```console +./tempest completion:install +``` + +The installer will detect your current shell, copy the completion script to the appropriate location, and provide instructions for enabling it. + +For Zsh, you'll need to ensure the completions directory is in your `fpath` and reload completions: + +```zsh +# Add to ~/.zshrc +fpath=(~/.zsh/completions $fpath) +autoload -Uz compinit && compinit +``` + +For Bash, source the completion file in your `~/.bashrc`: + +```bash +source ~/.bash_completion.d/tempest.bash +``` + +### Additional commands + +You may also use these related commands: + +- `completion:show` — Output the completion script to stdout (useful for custom installation) +- `completion:uninstall` — Remove the installed completion script + ## Middleware Console middleware can be applied globally or on a per-command basis. Global console middleware will be discovered and applied automatically, by priority order. @@ -285,7 +320,7 @@ final readonly class InspireMiddleware implements ConsoleMiddleware { /* … */ } ``` -Note that priority is defined using an integer. However, the {b`Tempest\Core\Priority`} class provides a few constant with predefined priorities: `Priority::FRAMEWORK`, `Priority::HIGHEST`, `Priority::HIGH`, `Priority::NORMAL`, `Priority::LOW`, `Priority::LOWEST`. +Note that priority is defined using an integer. However, the {b`Tempest\Core\Priority`} class provides a few constants with predefined priorities: `Priority::FRAMEWORK`, `Priority::HIGHEST`, `Priority::HIGH`, `Priority::NORMAL`, `Priority::LOW`, `Priority::LOWEST`. #### Middleware discovery @@ -312,7 +347,7 @@ Tempest provides a few built-in middleware that you may use on your console comm ## Scheduling -Console commands—or any public class method—may be scheduled by using the {b`#[Tempest\Console\Schedule]`} attribute, which accepts an {b`Tempest\Console\Scheduler\Interval`} or {b`Tempest\Console\Scheduler\Every`} value. Methods with this attributes are automatically [discovered](../4-internals/02-discovery.md), so there is nothing more to add. +Console commands—or any public class method—may be scheduled by using the {b`#[Tempest\Console\Schedule]`} attribute, which accepts an {b`Tempest\Console\Scheduler\Interval`} or {b`Tempest\Console\Scheduler\Every`} value. Methods with this attributes are automatically [discovered](./05-discovery.md), so there is nothing more to add. You may read more on the [dedicated chapter](../2-features/11-scheduling.md). @@ -331,4 +366,4 @@ $this->console ->assertSee('caution') ->submit() ->assertSuccess(); -``` \ No newline at end of file +``` diff --git a/docs/1-essentials/05-container.md b/docs/1-essentials/05-container.md index f87734ea8c..71e12affae 100644 --- a/docs/1-essentials/05-container.md +++ b/docs/1-essentials/05-container.md @@ -47,10 +47,10 @@ The `{php}\Tempest\invoke()` function serves the same purpose when the container ### Locating a dependency -There are situations where it may not be possible to inject a dependency on a constructor. To work around this, Tempest provides the `{php}\Tempest\get()` function, which can resolve an object from the container. +There are situations where it may not be possible to inject a dependency on a constructor. To work around this, Tempest provides the `{php}\Tempest\Container\get()` function, which can resolve an object from the container. ```php -use function Tempest\get; +use function Tempest\Container\get; $config = get(AppConfig::class); ``` @@ -67,7 +67,7 @@ Initializers are classes that know how to construct a specific class or interfac ### Implementing an initializer -Initializers are classes that implement the {`Tempest\Container\Initializer`} interface. The `initialize()` method receives the container as its only parameter, and returns an instanciated object. +Initializers are classes that implement the {`Tempest\Container\Initializer`} interface. The `initialize()` method receives the container as its only parameter, and returns an instantiated object. **Most importantly**, Tempest knows which object this initializer is tied to thanks to the return type of the `initialize()` method, which needs to be typed. @@ -156,15 +156,17 @@ The {`Tempest\Container\DynamicInitializer`} interface provides a `canInitialize ```php app/RouteBindingInitializer.php use Tempest\Container\Container; use Tempest\Container\DynamicInitializer; +use Tempest\Reflection\ClassReflector; +use UnitEnum; final class RouteBindingInitializer implements DynamicInitializer { - public function canInitialize(string $className): bool + public function canInitialize(ClassReflector $class, null|string|UnitEnum $tag): bool { - return is_a($className, Model::class, true); + return $class->getType()->matches(Model::class); } - public function initialize(string $className, Container $container): object + public function initialize(ClassReflector $class, null|string|UnitEnum $tag, Container $container): object { // … } @@ -243,7 +245,7 @@ final readonly class MarkdownInitializer implements Initializer In some cases, you want more control over singleton definitions. -Let's say you want an instance of `{php}\Tempest\Highlight\Highlighter` that would be configured for web highlighting, and one that would be configured CLI highlighting. In this situation, you can differenciate them using the `tag` parameter of the `#[Singleton]` attribute: +Let's say you want an instance of `{php}\Tempest\Highlight\Highlighter` that would be configured for web highlighting, and one that would be configured CLI highlighting. In this situation, you can differentiate them using the `tag` parameter of the `#[Singleton]` attribute: ```php app/WebHighlighterInitializer.php use Tempest\Container\Container; @@ -325,7 +327,7 @@ use Tempest\Container\Tag; final readonly class BookController { - public function __constructor( + public function __construct( #[Tag('book-validators')] private readonly array $contentValidators, ) { /* … */ } } @@ -427,7 +429,7 @@ final readonly class CacheRepository implements Repository When you request the `Repository` from the container, Tempest will automatically wrap the original implementation with your decorator. The decorated object (the original `Repository`) is injected into the decorator's constructor. :::info -Decorators are discovered automatically through Tempest's [discovery](../4-internals/02-discovery.md), so you don't need to manually register them. +Decorators are discovered automatically through Tempest's [discovery](./05-discovery.md), so you don't need to manually register them. ::: ## Proxy loading diff --git a/docs/1-essentials/05-discovery.md b/docs/1-essentials/05-discovery.md new file mode 100644 index 0000000000..d6a4bc3e1e --- /dev/null +++ b/docs/1-essentials/05-discovery.md @@ -0,0 +1,220 @@ +--- +title: Discovery +description: "Tempest automatically locates controller actions, event handlers, console commands, and other components of your application, without needing any configuration from you." +--- + +## Overview + +Tempest introduces a unique approach to bootstrapping applications. Instead of requiring manual registration of project code and packages, Tempest automatically scans the codebase and detects the components that should be loaded. This process is called **discovery**. + +Discovery is powered by composer metadata. Every package that depends on Tempest, along with your application's own code, are included in the discovery process. + +Tempest applies [various rules](#built-in-discovery-classes) to determine the purpose of different pieces of code—it can analyze file names, attributes, interfaces, return types, and more. For instance, web routes are discovered when methods are annotated with route attributes: + +```php app/HomeController.php +final readonly class HomeController +{ + #[Get(uri: '/home')] + public function __invoke(): View + { + return view('home.view.php'); + } +} +``` + +:::tip +Read the [getting started with discovery](/blog/discovery-explained) guide if you want to know more about the philosophy of discovery and how it works. +::: + +## Discovery in production + +Discovery comes with performance considerations. In production, it is always cached to avoid scanning files on every request. + +To ensure that the discovery cache is up-to-date, add the `discovery:generate` command before any other Tempest command in your deployment pipeline. + +```console ">_ ./tempest discovery:generate --no-interaction" +Clearing discovery cache ..................................... 2025-12-30 15:51:46 +Clearing discovery cache ..................................... DONE +Generating discovery cache using the `full` strategy ......... 2025-12-30 15:51:46 +Generating discovery cache using the `full` strategy ......... DONE +``` + +## Discovery for local development + +During development, discovery is only enabled for application code. This implies that the cache should be regenerated whenever a package is installed or updated. + +It is recommended to add the `discovery:generate` command to the `post-package-update` script in `composer.json`: + +```json composer.json +{ + "scripts": { + "post-package-update": [ + "@php tempest discovery:generate" + ] + } +} +``` + +### Disabling discovery cache + +In some situations, you may want to enable discovery even for vendor code. For instance, if you are working on a third-party package that is being developed alongside your application, you may want to have discovery enabled all the time. + +To achieve this, set the `DISCOVERY_CACHE` environment variable to `false`: + +```env .env +{:hl-property:DISCOVERY_CACHE:}={:hl-keyword:false:} +``` + +### Troubleshooting + +The `discovery:clear` command clears the discovery cache, which will be rebuilt the next time the framework boots. `discovery:generate` can be used to manually regenerate the cache. + +If the discovery cache gets corrupted and even `discovery:clear` is not enough, the `.tempest/cache/discovery` may be manually deleted from your project. + +## Implementing your own discovery + +While Tempest provides a variety of [built-in discovery classes](#built-in-discovery-classes), you may want to implement your own to extend the framework's capabilities in your application or in a package you are building. + +### Discovering code in classes + +Tempest discovers classes that implement {b`Tempest\Discovery\Discovery`}, which requires implementing the `discover()` and `apply()` methods. The {b`Tempest\Discovery\IsDiscovery`} trait provides the rest of the implementation. + +The `discover()` method accepts a {b`Tempest\Discovery\DiscoveryLocation`} and a {b`Tempest\Reflection\ClassReflector`} parameter. The reflector can be used to loop through a class' attributes, methods, parameters or anything else. If the class matches your expectations, you may register it using `$this->discoveryItems->add()`. + +As an example, the following is a simplified version of the event bus discovery: + +```php EventBusDiscovery.php +use Tempest\Discovery\Discovery; +use Tempest\Discovery\IsDiscovery; + +final class EventBusDiscovery implements Discovery +{ + // This provides the default implementation for `Discovery`'s internals + use IsDiscovery; + + public function __construct( + // Discovery classes are autowired, + // so you can inject all dependencies you need + private EventBusConfig $eventBusConfig, + ) { + } + + public function discover(DiscoveryLocation $location, ClassReflector $class): void + { + foreach ($class->getPublicMethods() as $method) { + $eventHandler = $method->getAttribute(EventHandler::class); + + // Extra checks to determine whether + // we can actually use the current method as an event handler + + // … + + // Finally, we add all discovery-related data into `$this->discoveryItems`: + $this->discoveryItems->add($location, [$eventName, $eventHandler, $method]); + } + } + + // Next, the `apply` method is called whenever discovery is ready to be + // applied into the framework. In this case, we want to loop over all + // registered discovery items, and add them to the event bus config. + public function apply(): void + { + foreach ($this->discoveryItems as [$eventName, $eventHandler, $method]) { + $this->eventBusConfig->addClassMethodHandler( + event: $eventName, + handler: $eventHandler, + reflectionMethod: $method, + ); + } + } +} +``` + +### Discovering files + +It is possible to discover files instead of classes. For instance, view files, front-end entrypoints or SQL migrations are not PHP classes, but still need to be discovered. + +In this case, you may implement the additional {b`\Tempest\Discovery\DiscoversPath`} interface. It requires a `discoverPath()` method that accepts a {b`Tempest\Discovery\DiscoveryLocation`} and a string path. + +The example below shows a simplified version of the Vite entrypoint discovery: + +```php ViteDiscovery.php +use Tempest\Discovery\Discovery; +use Tempest\Discovery\DiscoversPath; +use Tempest\Discovery\IsDiscovery; +use Tempest\Support\Str; + +final class ViteDiscovery implements Discovery, DiscoversPath +{ + use IsDiscovery; + + public function __construct( + private readonly ViteConfig $viteConfig, + ) {} + + // We are not discovering any class, so we return immediately. + public function discover(DiscoveryLocation $location, ClassReflector $class): void + { + return; + } + + // This method is called for every file in registered discovery locations. + // We can use the `$path` to determine whether we are interested in it. + public function discoverPath(DiscoveryLocation $location, string $path): void + { + // We are interested in `.ts`, `.css` and `.js` files only. + if (! Str\ends_with($path, ['.ts', '.css', '.js'])) { + return; + } + + // These files need to be specifically marked as `.entrypoint`. + if (! str($path)->beforeLast('.')->endsWith('.entrypoint')) { + return; + } + + $this->discoveryItems->add($location, [$path]); + } + + // When discovery is cached, `discover` and `discoverPath` are not called. + // Instead, `discoveryItems` is already fed with serialized data, which + // we can use. In this case, we add the paths to the Vite config. + public function apply(): void + { + foreach ($this->discoveryItems as [$path]) { + $this->viteConfig->addEntrypoint($path); + } + } +} +``` + +## Excluding files and classes from discovery + +Files and classes may be excluded from discovery by providing a {b`Tempest\Core\DiscoveryConfig`} [configuration](./06-configuration.md) file. + +```php src/discovery.config.php +use Tempest\Core\DiscoveryConfig; + +return new DiscoveryConfig() + ->skipClasses(GlobalHiddenDiscovery::class) + ->skipPaths(__DIR__ . '/../../Fixtures/GlobalHiddenPathDiscovery.php'); +``` + +## Built-in discovery classes + +Most of Tempest's features are built on top of discovery. The following is a non-exhaustive list that describes which discovery class is associated to which feature. + +- {b`Tempest\Core\DiscoveryDiscovery`} discovers other discovery classes. This class is run manually by the framework when booted. +- {b`Tempest\CommandBus\CommandBusDiscovery`} discovers methods with the {b`#[Tempest\CommandBus\CommandHandler]`} attribute and registers them into the [command bus](../2-features/10-command-bus.md). +- {b`Tempest\Console\Discovery\ConsoleCommandDiscovery`} discovers methods with the {b`#[Tempest\Console\ConsoleCommand]`} attribute and registers them as [console commands](../1-essentials/04-console-commands.md). +- {b`Tempest\Console\Discovery\ScheduleDiscovery`} discovers methods with the {b`#[Tempest\Console\Schedule]`} attribute and registers them as [scheduled tasks](../2-features/11-scheduling.md). +- {b`Tempest\Container\InitializerDiscovery`} discovers classes that implement {b`\Tempest\Container\Initializer`} or {b`\Tempest\Container\DynamicInitializer`} and registers them as [dependency initializers](./05-container.md#dependency-initializers). +- {b`Tempest\Database\MigrationDiscovery`} discovers classes that implement {b`Tempest\Database\MigratesUp`} or {b`Tempest\Database\MigratesDown`} and registers them as [migrations](./03-database.md#migrations). +- {b`Tempest\EventBus\EventBusDiscovery`} discovers methods with the {b`#[Tempest\EventBus\EventHandler]`} attribute and registers them in the [event bus](../2-features/08-events.md). +- {b`Tempest\Router\RouteDiscovery`} discovers route attributes on methods and registers them as [controller actions](./01-routing.md) in the router. +- {b`Tempest\Mapper\MapperDiscovery`} discovers classes that implement {b`Tempest\Mapper\Mapper`} and registers them for [mapping](../2-features/01-mapper.md#mapper-discovery). +- {b`Tempest\Mapper\CasterDiscovery`} discovers classes that implement {b`Tempest\Mapper\DynamicCaster`} and registers them as [casters](../2-features/01-mapper.md#casters-and-serializers). +- {b`Tempest\Mapper\SerializerDiscovery`} discovers classes that implement {b`Tempest\Mapper\DynamicSerializer`} and registers them as [serializers](../2-features/01-mapper.md#casters-and-serializers). +- {b`Tempest\View\ViewComponentDiscovery`} discovers `x-*.view.php` files and registers them as [view components](../1-essentials/02-views.md#view-components). +- {b`Tempest\Vite\ViteDiscovery`} discovers `*.entrypoint.{ts,js,css}` files and register them as [entrypoints](../2-features/02-asset-bundling.md#entrypoints). +- {b`Tempest\Auth\AccessControl\PolicyDiscovery`} discovers methods annotated with the {b`#[Tempest\Auth\AccessControl\Policy]`} attribute and registers them as [access control policies](../2-features/04-authentication.md#access-control). +- {b`Tempest\Core\InsightsProviderDiscovery`} discovers classes that implement {b`Tempest\Core\InsightsProvider`} and registers them as insights providers, which power the `tempest about` command. diff --git a/docs/1-essentials/06-configuration.md b/docs/1-essentials/06-configuration.md index 6d7cf10d75..8cc1d6dbbb 100644 --- a/docs/1-essentials/06-configuration.md +++ b/docs/1-essentials/06-configuration.md @@ -11,7 +11,7 @@ Even though the framework is designed to use as little configuration as possible ## Configuration files -Files ending with `*.config.php` are recognized by Tempest's [discovery](../4-internals/02-discovery) as configuration objects, and will be registered as [singletons](./01-container#singletons) in the container. +Files ending with `*.config.php` are recognized by Tempest's [discovery](../1-essentials/05-discovery) as configuration objects, and will be registered as [singletons](./01-container#singletons) in the container. ```php app/postgres.config.php use Tempest\Database\Config\PostgresConfig; @@ -28,30 +28,30 @@ return new PostgresConfig( The configuration object above instructs Tempest to use PostgreSQL as its database, replacing the framework's default database, SQLite. -## Accessing configuration objects +### Accessing configuration objects To access a configuration object, you may inject it from the container like any other dependency. ```php -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; -final readonly class HomeController +final readonly class AboutController { public function __construct( - private AppConfig $config, + private Environment $environment, ) {} #[Get('/')] public function __invoke(): View { - return view('home.view.php', environment: $this->config->environment); + return view('about.view.php', environment: $this->environment); } } ``` -## Updating configuration objects +### Updating configuration objects -To update a property in a configuration object, you may simply assign a new value. Due to the object being a singleton, the modification will be persisted throught the rest of the application's lifecycle. +To update a property in a configuration object, you may simply assign a new value. Due to the object being a singleton, the modification will be persisted through the rest of the application's lifecycle. ```php use Tempest\Support\Random; @@ -81,7 +81,7 @@ final class SlackConfig public string $token, public string $baseUrl, public string $applicationId, - public string $userAgent, + public ?string $userAgent = null, ) {} } ``` @@ -120,7 +120,7 @@ final class SlackConnector extends HttpConnector ## Per-environment configuration -Whenever possible, you should have a single configuration file per feature. You may use the `Tempest\env()` function inside that file to reference credentials and environment-specific values. +Whenever possible, you should have a single configuration file per feature. You may use the {b`Tempest\env()`} function inside that file to reference credentials and environment-specific values. However, it's sometimes needed to have completely different configurations in development and in production. For instance, you may use S3 for your [storage](../2-features/05-file-storage.md) in production, but use the local filesystem during development. @@ -135,6 +135,13 @@ return new S3StorageConfig( ); ``` +The following suffixes are supported: + +- `.prd.config.php`, `.prod.config.php`, and `.production.config.php` for the production environment. +- `.stg.config.php` and `.staging.config.php` for the staging environment. +- `.dev.config.php` and `.local.config.php` for the development environment. +- `.test.config.php` and `.testing.config.php` for the testing environment. + ## Disabling the configuration cache During development, Tempest will discover configuration files every time the framework is booted. In a production environment, configuration files are automatically cached. diff --git a/docs/1-essentials/07-testing.md b/docs/1-essentials/07-testing.md index f2095d477f..f4f98fe5ed 100644 --- a/docs/1-essentials/07-testing.md +++ b/docs/1-essentials/07-testing.md @@ -6,90 +6,52 @@ keywords: ["phpunit", "pest"] ## Overview -Tempest uses [PHPUnit](https://phpunit.de) for testing and provides an integration through the [`Tempest\Framework\Testing\IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php) test case. This class boots the framework with configuration suitable for testing, and provides access to multiple utilities. +Tempest uses [PHPUnit](https://phpunit.de) for testing and provides an integration through the [`IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php) test case. This class boots the framework with configuration suitable for testing, and provides access to multiple utilities. Testing utilities specific to components are documented in their respective chapters. For instance, testing the router is described in the [routing documentation](./01-routing.md#testing). ## Running tests -Any test class that wants to interact with Tempest should extend from [`IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php). Next, any test class should end with the suffix `Test`. +Any test class that needs to interact with Tempest must extend [`IntegrationTest`](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Framework/Testing/IntegrationTest.php). -Running the test suite is done by running `composer phpunit`. +By default, Tempest ships with a `phpunit.xml` file that configures PHPUnit to find test files in the `tests` directory. You may run tests using the following command: ```sh -composer phpunit -``` - -## Test-specific discovery locations - -Tempest will only discover non-dev namespaces defined in composer.json automatically. That means that `{:hl-keyword:require-dev:}` namespaces aren't discovered automatically. Whenever you need Tempest to discover test-specific locations, you may specify them within the `discoverTestLocations()` method of the provided `IntegrationTest` class. - -On top of that, Tempest _will_ look for files in the `tests/Fixtures` directory and discover them by default. You can override this behavior by providing your own implementation of `discoverTestLocations()`, where you can return an array of `DiscoveryLocation` objects (or nothing). - -```php tests/HomeControllerTest.php -use Tempest\Core\DiscoveryLocation; -use Tempest\Framework\Testing\IntegrationTest; - -final class HomeControllerTest extends IntegrationTest -{ - protected function discoverTestLocations(): array - { - return [ - new DiscoveryLocation('Tests\\OtherFixtures', __DIR__ . '/OtherFixtures'), - ]; - } -} +./vendor/bin/phpunit ``` ## Using the database -If you want to test code that interacts with the database, your test class can call the `setupDatabase()` method. This method will create and migrate a clean database for you on the fly. +By default, tests don't interact with the database. You may manually set up the database for testing in test files by using the `setup()` method on the `database` testing utility. -```php -final class TodoControllerTest extends IntegrationTest +```php tests/ShowAircraftControllerTest.php +final class ShowAircraftControllerTest extends IntegrationTest { - protected function setUp(): void + #[PreCondition] + protected function configure(): void { - parent::setUp(); - - $this->setupDatabase(); + $this->database->setup(); } } ``` -Most likely, you'll want to use a test-specific database connection. You can create a `database.config.php` file anywhere within test-specific discovery locations, and Tempest will use that connection instead of the project's default. For example, you can create a file `tests/Fixtures/database.config.php` like so: - -```php tests/Fixtures/database.config.php -use Tempest\Database\Config\SQLiteConfig; +:::info +The [`PreCondition`](https://docs.phpunit.de/en/12.5/attributes.html#precondition) attribute instructs PHPUnit to run the associated method after the `setUp()` method. We recommend using it instead of overriding `setUp()` directly. +::: -return new SQLiteConfig( - path: __DIR__ . '/database-testing.sqlite' -); -``` +### Running migrations -By default, no tables will be migrated. You can choose to provide a list of migrations that will be run for every test that calls `setupDatabase()`, or you can run specific migrations on a per-test basis. +By default, all migrations are run when setting up the database. However, you may choose to run only specific migrations by using the `migrate()` method instead of `setup()`. -```php -final class TodoControllerTest extends IntegrationTest +```php tests/ShowAircraftControllerTest.php +final class ShowAircraftControllerTest extends IntegrationTest { - protected function migrateDatabase(): void + #[Test] + public function shows_aircraft(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, - CreateTodosTable::class, - ); - } -} -``` - -```php -final class TodoControllerTest extends IntegrationTest -{ - public function test_create_todo(): void - { - $this->migrate( - CreateMigrationsTable::class, - CreateTodosTable::class, + CreateAircraftTable::class, ); // … @@ -97,33 +59,31 @@ final class TodoControllerTest extends IntegrationTest } ``` -## Tester utilities +### Using a dedicated testing database -The `IntegrationTest` provides several utilities to make testing easier. You can read the details about each tester utility on the documentation page of its respective component. For example, there's the [http tester](../1-essentials/01-routing.md#testing) that helps you test HTTP requests: +To ensure your tests run in isolation and do not affect your main database, you may configure a dedicated test database connection. -```php -$this->http - ->get('/account/profile') - ->assertOk() - ->assertSee('My Profile'); +To do so, create a `database.testing.config.php` file anywhere—Tempest will [use it](./06-configuration.md#per-environment-configuration) to override the default database settings. + +```php tests/database.testing.config.php +use Tempest\Database\Config\SQLiteConfig; + +return new SQLiteConfig( + path: __DIR__ . '/testing.sqlite' +); ``` -There's the [console tester](../1-essentials/04-console-commands.md#testing): +## Spoofing the environment -```php tests/ExportUsersCommandTest.php -$this->console - ->call(ExportUsersCommand::class) - ->assertSuccess() - ->assertSee('12 users exported'); +By default, Tempest provides a `phpunit.xml` that sets the `ENVIRONMENT` variable to `testing`. This is needed so that Tempest can adapt its boot process and load the proper configuration files for the testing environment. -$this->console - ->call(WipeDatabaseCommand::class) - ->assertSee('caution') - ->submit() - ->assertSuccess(); -``` +During tests, you may want to test different paths of your application depending on the environment. For instance, you may want to test that certain features are only available in production. To do this, you may override the {b`Tempest\Core\Environment`} singleton: -And many, many more. +```php +use Tempest\Core\Environment; + +$this->container->singleton(Environment::class, Environment::PRODUCTION); +``` ## Changing the location of tests @@ -140,6 +100,27 @@ For instance, you may colocate test files and their corresponding class by chang ``` +## Discovering test-specific fixtures + +Non-test files created in the `tests` directory are automatically discovered by Tempest when running the test suite. + +You can override this behavior by providing your own implementation of `discoverTestLocations()`: + +```php tests/Aircraft/ShowAircraftControllerTest.php +use Tempest\Discovery\DiscoveryLocation; +use Tempest\Framework\Testing\IntegrationTest; + +final class ShowAircraftControllerTest extends IntegrationTest +{ + protected function discoverTestLocations(): array + { + return [ + new DiscoveryLocation('Tests\\Aircraft', __DIR__ . '/Aircraft'), + ]; + } +} +``` + ## Using Pest as a test runner [Pest](https://pestphp.com/) is a test runner built on top of PHPUnit. It provides a functional way of writing tests similar to JavaScript testing frameworks like [Vitest](https://vitest.dev/), and features an elegant console reporter. diff --git a/docs/1-essentials/08-primitive-utilities.md b/docs/1-essentials/08-primitive-utilities.md index 261fc6f594..95aee03dd8 100644 --- a/docs/1-essentials/08-primitive-utilities.md +++ b/docs/1-essentials/08-primitive-utilities.md @@ -17,14 +17,14 @@ Most utilities provided by Tempest have a function-based implementation under th - [Filesystem paths](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Path/functions.php) - [Json manipulation](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Json/functions.php) - [Random values](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Random/functions.php) -- [Pluralization](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Language/functions.php) +- [Pluralization](https://github.com/tempestphp/tempest-intl) - [PHP namespaces](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Namespace/functions.php) -Tempest also provids the {`Tempest\Support\IsEnumHelper`} trait to work with enumerations, since a functional API is not useful in this case. +Tempest also provides the {`Tempest\Support\IsEnumHelper`} trait to work with enumerations, since a functional API is not useful in this case. ## String utilities -Tempest provides string utilities through [namespaced functions](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Support/src/Str/functions.php) or a fluent, object-oriented API, which comes in an immutable and a mutable flavor. +Tempest provides string utilities through [namespaced functions](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Str/functions.php) or a fluent, object-oriented API, which comes in an immutable and a mutable flavor. Providing a string value, you may create an instance of {`\Tempest\Support\Str\ImmutableString`} or {`\Tempest\Support\Str\MutableString`}: @@ -48,7 +48,7 @@ Note that you may use the `str()` function as a shorthand to create an {b`\Tempe ## Array utilities -Tempest provides array utilities through [namespaced functions](https://github.com/tempestphp/tempest-framework/blob/main/src/Tempest/Support/src/Arr/functions.php) or a fluent, object-oriented API, which comes in an immutable and a mutable flavor. +Tempest provides array utilities through [namespaced functions](https://github.com/tempestphp/tempest-framework/blob/main/packages/support/src/Arr/functions.php) or a fluent, object-oriented API, which comes in an immutable and a mutable flavor. Providing an iterable value, you may create an instance of {`\Tempest\Support\Arr\ImmutableArray`} or {`\Tempest\Support\Arr\MutableArray`}: diff --git a/docs/2-features/01-mapper.md b/docs/2-features/01-mapper.md index aab2dba8b7..3421655522 100644 --- a/docs/2-features/01-mapper.md +++ b/docs/2-features/01-mapper.md @@ -5,28 +5,28 @@ description: "The mapper component is capable of mapping data to objects and the ## Overview -Tempest comes with a mapper component that can be used to map all sorts of data to objects and back. For instance, it may map the request data to a request class, or the result of an SQL query to a model class. +Tempest provides a mapper component for mapping data to objects and back. The component maps request data to request classes, SQL query results to model classes, and other data transformations. -This component is used internally to handle persistence between models and the database, map PSR objects to internal requests, map request data to objects, and more. It is flexible enough to be used as-is, or you can build your own mappers. +This component is used internally for persistence between models and the database, it maps PSR objects to internal requests, request data to objects, and more. ## Mapping data -You may map data from a source to a target using the `map()` function. This function accepts the source data you want to map as its sole parameter, and returns a mapper instance. +To map data from a source to a target, use the {b`\Tempest\Mapper\map()`} function. This function accepts the source data as its sole parameter and returns a mapper instance. -Calling the `to()` method on this instance will return a new instance of the target class, populated with the mapped data: +Calling the `to()` method on this instance returns a new instance of the target class, populated with the mapped data: ```php -use function Tempest\map; +use function Tempest\Mapper\map; $book = map($rawBookAsJson)->to(Book::class); ``` ### Mapping to collections -When the source data is an array, you may instruct the mapper to map each item of the collection to an instance of the target class by calling the `collection()` method. +When the source data is an array, calling the `collection()` method instructs the mapper to map each item to an instance of the target class. ```php -use function Tempest\map; +use function Tempest\Mapper\map; $books = map($rawBooksAsJson) ->collection() @@ -35,7 +35,7 @@ $books = map($rawBooksAsJson) ### Choosing specific mappers -By default, Tempest finds out which mapper to use based on the source and target types. However, you can also specify which mapper to use by calling the `with()` method on the mapper instance. This method accepts one or multiple mapper class names, which will be used for the mapping. +By default, Tempest determines which mapper to use based on the source and target types. To specify which mapper to use explicitly, call the `with()` method on the mapper instance. This method accepts one or multiple mapper class names to use for the mapping. ```php $psrRequest = map($request) @@ -43,7 +43,7 @@ $psrRequest = map($request) ->do(); ``` -Alternatively, you may also provide closures to the `with()` method. These closures expect the mapper as their first parameter, and the source data as the second. By using closures you get access to the `$from` parameter as well, allowing you to do more advanced mapping via the `with()` method: +Alternatively, provide closures to the `with()` method. These closures expect the mapper as their first parameter and the source data as the second. Using closures provides access to the `$from` parameter for more advanced mapping operations: ```php $result = map($rawBooksAsJson) @@ -54,7 +54,7 @@ $result = map($rawBooksAsJson) Of course, `with()` can also be combined with `collection()` and `to()`. ```php -use function Tempest\map; +use function Tempest\Mapper\map; $books = map($rawBooksAsJson) ->collection() @@ -64,7 +64,7 @@ $books = map($rawBooksAsJson) ### Serializing to arrays or JSON -You may call `toArray()` or `toJson()` on the mapper instance to serialize the mapped data to an array or JSON string, respectively. +To serialize the mapped data to an array or JSON string, call `toArray()` or `toJson()` on the mapper instance, respectively. ```php $array = map($book)->toArray(); @@ -73,7 +73,7 @@ $json = map($book)->toJson(); ### Overriding field names -When mapping from an array to an object, Tempest will use the property names of the target class to map the data. If a property name doesn't match a key in the source array, you can use the {b`#[Tempest\Mapper\MapFrom]`} attribute to specify the key to map to the property. +When mapping from an array to an object, Tempest uses the property names of the target class to map the data. If a property name doesn't match a key in the source array, use the {b`#[Tempest\Mapper\MapFrom]`} attribute to specify the source key to map to the property. ```php use Tempest\Mapper\MapFrom; @@ -91,7 +91,7 @@ In the following example, the `book_title` key from the source array will be map $book = map(['book_title' => 'Timeline Taxi'])->to(Book::class); ``` -Similarly, you can use the {b`#[Tempest\Mapper\MapTo]`} attribute to specify the key that will be used when serializing the object to an array or a JSON string. +Similarly, use the {b`#[Tempest\Mapper\MapTo]`} attribute to specify the key used when serializing the object to an array or a JSON string. ```php use Tempest\Mapper\MapTo; @@ -105,9 +105,9 @@ final class Book ### Strict mapping -By default, the mapper allows building objects with missing data. For instance, if you have a class with two properties, and you only provide data for one of them, the mapper will still create an instance of the class. +By default, the mapper allows building objects with missing data. For instance, if a class has two properties and data is provided for only one, the mapper still creates an instance of the class. -This is useful for cases where you want to build objects incrementally. Similarly, protected and private properties are ignored and will not be populated. +This behavior supports building objects incrementally. Protected and private properties are ignored and not populated. ```php final class Book @@ -116,14 +116,17 @@ final class Book public string $contents; } -$book = map(['title' => 'Timeline Taxi'])->to(Book::class); // This is allowed +// Allowed +$book = map(['title' => 'Timeline Taxi'])->to(Book::class); ``` -Of course, accessing missing properties after the object has been constructed will result in an uninitialized property error. If you prefer to have the mapper throw an exception when properties are missing, you may mark the class or a specific property with the {`#[Tempest\Mapper\Strict]`} attribute. +Accessing missing properties after the object has been constructed results in an uninitialized property error. To have the mapper throw an exception when properties are missing, mark the class or a specific property with the {b`#[Tempest\Mapper\Strict]`} attribute. ```php use Tempest\Mapper\Strict; +use function Tempest\Mapper\map; + #[Strict] final class Book { @@ -131,13 +134,13 @@ final class Book public string $contents; } -// Not allowed anymore, MissingValuesException will be thrown +// MappingValuesWereMissing is thrown $book = map(['title' => 'Timeline Taxi'])->to(Book::class); ``` ## Custom mappers -You may create your own mappers by implementing the {`\Tempest\Mapper\Mapper`} interface. This interface expects a `canMap()` and a `map()` method. +To create custom mappers, implement the {`\Tempest\Mapper\Mapper`} interface. This interface requires a `canMap()` and a `map()` method. ```php final readonly class PsrRequestToRequestMapper implements Mapper @@ -158,15 +161,17 @@ final readonly class PsrRequestToRequestMapper implements Mapper ### Mapper discovery -Tempest will try its best to find the right mapper for you. All classes that implement the {b`\Tempest\Mapper\Mapper`} interface will be automatically discovered and registered. +Tempest automatically discovers and registers all classes that implement the {b`\Tempest\Mapper\Mapper`} interface. -Mapper discovery relies on the result of the `canMap()` method. When a mapper is dedicated to mapping a source to a specific class, the `$to` parameter may not necessarily be used. +Mapper discovery relies on the result of the `canMap()` method. When a mapper is dedicated to mapping a source to a specific class, the `$to` parameter is not necessarily used. ## Casters and serializers -Casters are responsible for mapping serialized data to a complex type. Similarly, serializers convert complex types to a serialized representation. +Casters map serialized data to a complex type. Serializers convert complex types to a serialized representation. + +To create custom casters and serializers, implement the {`\Tempest\Mapper\Caster`} and {`\Tempest\Mapper\Serializer`} interfaces, respectively. -You may create your own casters and serializers by implementing the {`\Tempest\Mapper\Caster`} and {`\Tempest\Mapper\Serializer`} interfaces, respectively. +:::code-group ```php app/AddressCaster.php use Tempest\Mapper\Caster; @@ -185,6 +190,7 @@ final readonly class AddressCaster implements Caster ``` ```php app/AddressSerializer.php +use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; final readonly class AddressSerializer implements Serializer @@ -192,7 +198,7 @@ final readonly class AddressSerializer implements Serializer public function serialize(mixed $input): array|string { if (! $input instanceof Address) { - throw new CannotSerializeValue(Address::class); + throw new ValueCouldNotBeSerialized(Address::class); } return $input->toArray(); @@ -200,11 +206,47 @@ final readonly class AddressSerializer implements Serializer } ``` +::: + Of course, Tempest provides casters and serializers for the most common data types, including arrays, booleans, dates, enumerations, integers and value objects. +### Registering casters and serializers globally + +To register casters and serializers globally without specifying them for every property, implement the {b`\Tempest\Mapper\DynamicCaster`} or {b`\Tempest\Mapper\DynamicSerializer`} interface, which require an `accepts` method: + +```php app/AddressSerializer.php +use Tempest\Mapper\Serializer; +use Tempest\Mapper\DynamicSerializer; + +final readonly class AddressSerializer implements Serializer, DynamicSerializer +{ + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(Address::class); + } + + public function serialize(mixed $input): array|string + { + if (! $input instanceof Address) { + throw new ValueCouldNotBeSerialized(Address::class); + } + + return $input->toArray(); + } +} +``` + +:::info +Dynamic serializers and casters will automatically be discovered by Tempest. +::: + ### Specifying casters or serializers for properties -You may use a specific caster or serializer for a property by using the {b`#[Tempest\Mapper\CastWith]`} or {b`#[Tempest\Mapper\SerializeWith]`} attribute, respectively. +To use a specific caster or serializer for a property, apply the {b`#[Tempest\Mapper\CastWith]`} or {b`#[Tempest\Mapper\SerializeWith]`} attribute, respectively. Of course, both attributes can be used together on the same property. ```php use Tempest\Mapper\CastWith; @@ -212,27 +254,150 @@ use Tempest\Mapper\CastWith; final class User { #[CastWith(AddressCaster::class)] + #[SerializeWith(AddressSerializer::class)] public Address $address; } ``` -You may of course use {b`#[Tempest\Mapper\CastWith]`} and {b`#[Tempest\Mapper\SerializeWith]`} together. +## Mapping contexts -### Registering casters and serializers globally +Contexts enable using different casters, serializers, and mappers depending on the situation. For example, dates can be serialized differently for an API response versus database storage, or different validation rules can be applied for different contexts. + +### Specifying a context + +To specify a context when mapping, use the `in()` method on the mapper instance. Contexts can be provided as a string, an enum, or a {b`\Tempest\Mapper\Context`} object. + +```php +use App\SerializationContext; +use function Tempest\Mapper\map; + +$json = map($book) + ->in(SerializationContext::API) + ->toJson(); +``` + +To create a caster or serializer that only applies in a specific context, use the {b`#[Tempest\Mapper\Attributes\Context]`} attribute on your class and provide it with a context name: + +```php app/ApiDateSerializer.php +use Tempest\DateTime\DateTime; +use Tempest\DateTime\FormatPattern; +use Tempest\Mapper\Attributes\Context; +use Tempest\Mapper\Serializer; +use Tempest\Mapper\DynamicSerializer; + +#[Context(SerializationContext::API)] +final readonly class ApiDateSerializer implements Serializer, DynamicSerializer +{ + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(DateTime::class); + } -You may register casters and serializers globally, so you don't have to specify them for every property. This is useful for value objects that are used frequently. + public function serialize(mixed $input): string + { + return $input->format(FormatPattern::ISO8601); + } +} +``` + +This serializer is only used when mapping with `->in(SerializationContext::API)`. Without a context specified, or in other contexts, the default serializers are used. + +### Injecting context into casters and serializers + +To adapt behavior dynamically, inject the current context into the caster or serializer constructor by naming its property `$context`. Other dependencies from the container can also be injected. ```php -use Tempest\Mapper\Casters\CasterFactory; -use Tempest\Mapper\Serializers\SerializerFactory; +use Tempest\Mapper\Attributes\Context; +use Tempest\Mapper\Serializer; -// Register a caster globally for a specific type -$container->get(CasterFactory::class) - ->addCaster(Address::class, AddressCaster::class); +#[Context(DatabaseContext::class)] +final class BooleanSerializer implements Serializer, DynamicSerializer +{ + public function __construct( + private DatabaseContext $context, + ) {} + + public static function accepts(PropertyReflector|TypeReflector $type): bool + { + $type = $type instanceof PropertyReflector + ? $type->getType() + : $type; -// Register a serializer globally for a specific type -$container->get(SerializerFactory::class) - ->addSerializer(Address::class, AddressSerializer::class); + return $type->getName() === 'bool' || $type->getName() === 'boolean'; + } + + public function serialize(mixed $input): string + { + return match ($this->context->dialect) { + DatabaseDialect::POSTGRESQL => $input ? 'true' : 'false', + default => $input ? '1' : '0', + }; + } +} ``` -If you're looking for the right place where to put this logic, [provider classes](/docs/extra-topics/package-development#provider-classes) is our recommendation. +## Configurable casters and serializers + +Casters or serializers sometimes need configuration based on the property they're applied to. For example, an enum caster needs to know which enum class to use, and an object caster needs to know the target type. + +To create casters or serializers that are configured per property, implement the {b`\Tempest\Mapper\ConfigurableCaster`} or {b`\Tempest\Mapper\ConfigurableSerializer`} interface: + +```php +use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; + +final readonly class EnumCaster implements Caster, DynamicCaster, ConfigurableCaster +{ + /** + * @param class-string $enum + */ + public function __construct( + private string $enum, + ) {} + + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(UnitEnum::class); + } + + public static function configure(PropertyReflector $property, Context $context): self + { + // Create a new instance configured for this property + return new self(enum: $property->getType()->getName()); + } + + public function cast(mixed $input): ?object + { + if ($input === null) { + return null; + } + + // Use the configured enum class + return $this->enum::from($input); + } +} +``` + +The `configure()` method receives the property being mapped and the current context, enabling the creation of a caster instance tailored to that specific property. + +Note that `ConfigurableSerializer::configure()` can receive either a `PropertyReflector`, `TypeReflector`, or `string`, depending on whether it's used for property mapping or value serialization. + +Configurable casters and serializers are appropriate when: + +- The caster or serializer behavior depends on the specific property type (e.g., enum class, object class), +- Access to property attributes or metadata is required, +- Different properties of the same base type require different handling, +- Creating many similar caster or serializer classes needs to be avoided. + +For static behavior that doesn't depend on property information, regular casters and serializers are sufficient. diff --git a/docs/2-features/02-asset-bundling.md b/docs/2-features/02-asset-bundling.md index 6972920133..be2372109a 100644 --- a/docs/2-features/02-asset-bundling.md +++ b/docs/2-features/02-asset-bundling.md @@ -189,7 +189,7 @@ export default defineConfig({ ## Testing -By default, Tempest is intructed to not generate any tag during tests. This behavior is in place to prevent triggering `ManifestNotFoundException` exceptions in your test suite. +By default, Tempest is instructed to not generate any tag during tests. This behavior is in place to prevent triggering `ManifestNotFoundException` exceptions in your test suite. If, for any reason, you wish to restore tag resolution in a test, you may call the `{php}allowTagResolution()` method on the `ViteTester` instance: diff --git a/docs/2-features/03-validation.md b/docs/2-features/03-validation.md index 62b3c49563..d0d3900ea0 100644 --- a/docs/2-features/03-validation.md +++ b/docs/2-features/03-validation.md @@ -120,12 +120,13 @@ When validation fails, a list of fields and their respective failing rules is re ```php use Tempest\Support\Arr; +use Tempest\Validation\Rules\IsEmail; // Validate some value -$failures = $this->validator->validateValue('jon@doe.co', new Email()); +$failures = $this->validator->validateValue('jon@doe.co', new IsEmail()); // Map failures to their message -$errors = Arr\map($failures, fn (Rule $failure) => $this->validator->getErrorMessage($failure)); +$errors = Arr\map_iterable($failures, fn (FailingRule $failure) => $this->validator->getErrorMessage($failure)); ``` You may also specify the field name of the validation failure to get a localized message for that field. @@ -145,3 +146,17 @@ validation_error: .input {$field :string} {$field} must be a valid email address. ``` + +Sometimes though, you may want to have a specific error message for a rule, without overriding the default translation message for that rule. + +This can be done by using the {b`#[Tempest\Validation\TranslationKey]`} attribute on the property being validated. For instance, you may have the following object: + +```php +final class Book { + #[Rules\HasLength(min: 5, max: 50)] + #[TranslationKey('book_management.book_title')] + public string $title; +} +``` + +When this rule fails, the `getErrorMessage()` method from the validator will use `validation_error.has_length.book_management.book_title` as the translation key, instead of `validation_error.has_length`. diff --git a/docs/2-features/04-authentication.md b/docs/2-features/04-authentication.md index 06b0185ccf..943e7d7976 100644 --- a/docs/2-features/04-authentication.md +++ b/docs/2-features/04-authentication.md @@ -98,7 +98,7 @@ You may access the currently authenticated model by injecting the {b`Tempest\Aut use Tempest\Auth\Authentication\Authenticator; use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class ProfileController { @@ -152,7 +152,7 @@ final readonly class LdapAuthenticatableResolver implements AuthenticatableResol private LdapClient $ldap, ) {} - public function resolve(int|string $id): ?Authenticatable + public function resolve(int|string $id, string $class): ?Authenticatable { $attributes = $this->ldap->findUserByIdentifier($id); @@ -308,7 +308,7 @@ final readonly class PostController } ``` -Alternatively, you may use the `isGranted()` method. It will return a boolean indicating whether the action is granted for the resource and subject. +Alternatively, you may use the `isGranted()` method. It will return an {b`Tempest\Auth\AccessControl\AccessDecision`} instance. Check the `granted` property to determine access for the resource and subject. :::info Note that the subject is optional in both methods—if omitted, the [authenticated model](#authentication) is automatically provided. diff --git a/docs/2-features/05-file-storage.md b/docs/2-features/05-file-storage.md index e8a445d842..20bbd40e23 100644 --- a/docs/2-features/05-file-storage.md +++ b/docs/2-features/05-file-storage.md @@ -67,12 +67,12 @@ $storage->write($location, $contents); $storage->read($location); /** - * Deletes the contents of the file at the specified `$location`. + * Deletes the file or directory at the specified `$location`. */ $storage->delete($location); /** - * Determines whether a file exists at the specified `$location`. + * Determines whether a file or a directory exists at the specified `$location`. */ $storage->fileOrDirectoryExists($location); ``` diff --git a/docs/2-features/06-cache.md b/docs/2-features/06-cache.md index 31a7c5b9e3..74abfcc1a0 100644 --- a/docs/2-features/06-cache.md +++ b/docs/2-features/06-cache.md @@ -36,7 +36,7 @@ final readonly class OrderService { return $this->cache->resolve( key: 'orders_count', - resolve: fn () => $this->fetchOrdersCountFromDatabase(), + callback: fn () => $this->fetchOrdersCountFromDatabase(), expiration: Duration::hours(12) ); } @@ -195,5 +195,5 @@ $cache = $this->cache->fake(); // Call some application code // … -$this->cache->assertNotLocked('processing'); +$cache->assertNotLocked('processing'); ``` diff --git a/docs/2-features/07-mail.md b/docs/2-features/07-mail.md index 4d09f31677..3c0a597793 100644 --- a/docs/2-features/07-mail.md +++ b/docs/2-features/07-mail.md @@ -7,7 +7,7 @@ description: "Tempest provides a convenient layer built on top of Symfony's exce Sending emails starts with picking an email transport. Tempest comes with built-in support for SMTP, Amazon SES, and Postmark; but it's trivial to add any other transport you'd like. We'll start with plain SMTP, and explain how to switch to other transports later. -By default, Tempest is configured to use SMTP mailing. You'll need to add these environment variables and the mailer will be ready for use: +By default, Tempest is configured to use SMTP mailing. You'll need to add these environment variables and the mailer will be ready for use: ```dotenv MAIL_SMTP_HOST=mail.my_provider.com @@ -41,8 +41,6 @@ final class UserEventHandlers user: $userCreated->user, ), )); - - $this->success('Done'); } } ``` @@ -63,8 +61,6 @@ final class UserEventHandlers public function onCreated(UserCreated $userCreated): void { $this->mailer->send(new WelcomeEmail($userCreated->user)); - - $this->success('Done'); } } ``` @@ -75,7 +71,7 @@ Here's what that `WelcomeEmail` would look like: use Tempest\Mail\Email; use Tempest\Mail\Envelope; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final class WelcomeEmail implements Email { @@ -145,7 +141,9 @@ final class WelcomeEmail implements Email, HasTextContent { // … - public string|View|null $text = view('welcome-text.view.php', user: $this->user); + public string|View|null $text { + get => view('welcome-text.view.php', user: $this->user); + } } ``` @@ -196,7 +194,7 @@ $attachment = new Attachment(function () { ## Other transports -As mentioned, Tempest has built-in support for SMTP, Amazon SES, and Postmark. It is however trivial to use a range of other transports as well. First let's talk about switching to one of the built-in transports. +As mentioned, Tempest has built-in support for SMTP, Amazon SES, and Postmark. It is however trivial to use a range of other transports as well. First let's talk about switching to one of the built-in transports. The first step in using any transport is to install the transport-specific driver. You can find a list of all supported transports on [Symfony's documentation](https://symfony.com/doc/current/mailer.html#using-a-3rd-party-transport). If we take Postmark as an example, you should install these two dependencies: @@ -295,4 +293,4 @@ public function test_welcome_mail() } ``` -Note that mails sent within tests using the {b`\Tempest\Mail\Testing\MailTester`} will never be actually sent. Read more about testing [here](/docs/essentials/testing). \ No newline at end of file +Note that mails sent within tests using the {b`\Tempest\Mail\Testing\MailTester`} will never be actually sent. Read more about testing [here](/docs/essentials/testing). diff --git a/docs/2-features/08-events.md b/docs/2-features/08-events.md index bcc154bda6..d98e3911f7 100644 --- a/docs/2-features/08-events.md +++ b/docs/2-features/08-events.md @@ -96,7 +96,7 @@ final readonly class SyncUsersCommand $this->console->header('Synchronizing users'); // Listen for the UserSynced to write to the console when it happens - $this->eventBus->listen(UserSynced::class, function (UserSynced $event) { + $this->eventBus->listen(function (UserSynced $event) { $this->console->keyValue($event->fullName, 'SYNCED'); }); @@ -176,6 +176,7 @@ use Tempest\EventBus\EventHandler; final class MyHandler { + #[EventHandler] #[StopsPropagation] public function handle(OtherEvent $event): void { @@ -194,11 +195,14 @@ Other events include migration-related ones, such as {b`Tempest\Database\Migrati ## Testing -By extending {`Tempest\Framework\Testing\IntegrationTest`} from your test case, you may gain access to the event bus testing utilities using the `eventBus` property. +By extending {b`Tempest\Framework\Testing\IntegrationTest`} from your test case, you gain access to the event bus testing utilities through the `eventBus` property. These utilities include a way to replace the event bus with a testing implementation, as well as a few assertion methods to ensure that events have been dispatched or are being listened to. ```php +// Record dispatched events for assertion +$this->eventBus->recordEventDispatches(); + // Prevents events from being handled $this->eventBus->preventEventHandling(); @@ -222,16 +226,22 @@ $this->eventBus->assertNotDispatched(AircraftRegistered::class); $this->eventBus->assertListeningTo(AircraftRegistered::class); ``` -### Preventing event handling +### Recording event dispatches When testing code that dispatches events, you may want to prevent Tempest from handling them. This can be useful when the event’s handlers are tested separately, or when the side-effects of these handlers are not desired for this test case. -To disable event handling, the event bus instance must be replaced with a testing implementation in the container. This may be achieved by calling the `preventEventHandling()` method on the `eventBus` property. +To disable event handling, the event bus instance must be replaced with a testing implementation in the container. This is achieved by calling the `preventEventHandling()` method on the `eventBus` property. -```php tests/MyServiceTest.php +```php $this->eventBus->preventEventHandling(); ``` +If you want to be able to make assertions while still allowing events to be dispatched, you may instead call the `recordEventDispatches()` method. + +```php +$this->eventBus->recordEventDispatches(); +``` + ### Testing a method-based handler When handlers are registered as methods, instead of dispatching the corresponding event to test the handler logic, you may simply call the method to test it in isolation. @@ -252,7 +262,7 @@ final readonly class AircraftObserver This handler may be tested by resolving the service class from the container, and calling the method with an instance of the event created for this purpose. ```php app/AircraftObserverTest.php -// Replace the event bus in the container +// Prevent events from being handled while allowing assertions $this->eventBus->preventEventHandling(); // Resolve the service class diff --git a/docs/2-features/09-logging.md b/docs/2-features/09-logging.md index 0b98496ca3..c60748eee6 100644 --- a/docs/2-features/09-logging.md +++ b/docs/2-features/09-logging.md @@ -1,96 +1,197 @@ --- title: Logging +description: "Learn how to use Tempest's logging features to monitor and debug your application." --- -Logging is an essential part of any developer's job. Whether it's for debugging or for production monitoring. Tempest has a powerful set of tools to help you access the relevant information you need. +## Overview -## Debug log +Tempest provides a logging implementation built on top of [Monolog](https://github.com/Seldaek/monolog) that follows PSR-3 and the [RFC 5424 specification](https://datatracker.ietf.org/doc/html/rfc5424). This gives you access to eight standard log levels and the ability to send log messages to multiple destinations simultaneously. -First up are Tempest's debug functions: `ld()` (log, die), `lw()` (log, write), and `ll()` (log, log). These three functions are similar to Symfony's var dumper and Laravel's `dd()`, although there's an important difference. +The system supports file logging, Slack integration, system logs, and custom channels. You can configure different loggers for different parts of your application using Tempest's [tagged singletons](../1-essentials/05-container.md#tagged-singletons) feature. -You can think of `ld()` or `lw()` as Laravel's `dd()` and `dump()` variants. In fact, Tempest uses Symfony's var-dumper under the hood, just like Laravel. Furthermore, if you haven't installed Tempest in a project that already includes Laravel, Tempest will also provide `dd()` and `dump()` as aliases to `ld()` and `lw()`. +## Writing logs -The main difference is that Tempest's debug functions will **also write to the debug log**, which can be tailed with tempest's built-in `tail` command. This is its default output: +To start logging messages, you may inject the {b`Tempest\Log\Logger`} interface in any class. By default, log messages will be written to a daily rotating log file stored in `.tempest/logs`. This may be customized by providing a different [logging configuration](#configuration). -```console -./tempest tail +```php app/Services/UserService.php +use Tempest\Log\Logger; -

Project

Listening at /Users/brent/Dev/tempest-docs/log/tempest.log -

Server

No server log configured in LogConfig -

Debug

Listening at /Users/brent/Dev/tempest-docs/log/debug.log +final readonly class UserService +{ + public function __construct( + private Logger $logger, + ) {} +} ``` -Wherever you call `ld()` or `lw()` from, the output will also be written to the debug log, and tailed automatically with the `./tempest tail` command. On top of that, `tail` also monitors two other logs: +Tempest supports all eight levels described in the [RFC 5424](https://tools.ietf.org/html/rfc5424) specification. It is possible to configure channels to only log messages at or above a certain level. -- The **project log**, which contains everything the default logger writes to -- The **server log**, which should be manually configured in `LogConfig`: +```php +$logger->emergency('System is unusable'); +$logger->alert('Action required immediately'); +$logger->critical('Important, unexpected error'); +$logger->error('Runtime error that should be monitored'); +$logger->warning('Exceptional occurrence that is not an error'); +$logger->notice('Uncommon event'); +$logger->info('Miscellaneous event'); +$logger->debug('Detailed debug information'); +``` + +### Providing context + +All log methods accept an optional context array for additional information. This data is formatted as JSON and included with your log message: ```php -// app/Config/log.config.php +$logger->error('Order processing failed', context: [ + 'user_id' => $order->userId, + 'order_id' => $order->id, + 'total_amount' => $order->total, + 'payment_method' => $order->paymentMethod, + 'error_code' => $exception->getCode(), + 'error_message' => $exception->getMessage(), +]); +``` -use Tempest\Log\LogConfig; +## Configuration -return new LogConfig( - serverLogPath: '/path/to/nginx.log' +By default, Tempest uses a daily rotating log configuration that creates a new log file each day and retains up to 31 files: - // … +```php config/logging.config.php +use Tempest\Log\Config\DailyLogConfig; +use Tempest; + +return new DailyLogConfig( + path: Tempest\internal_storage_path('logs', 'tempest.log'), + maxFiles: Tempest\env('LOG_MAX_FILES', default: 31) ); ``` -If you're only interested in tailing one or more specific logs, you can filter the `tail` output like so: +To configure a different logging channel, you may create a `logging.config.php` file anywhere and return one of the [available configuration classes](#available-configurations-and-channels). + +### Specifying a minimum log level + +Every configuration class and log channel accept a `minimumLogLevel` property, which defines the lowest severity level that will be logged. Messages below this level will be ignored. -```console -./tempest tail --debug +```php config/logging.config.php +use Tempest\Log\Config\MultipleChannelsLogConfig; +use Tempest\Log\Channels\DailyLogChannel; +use Tempest\Log\Channels\SlackLogChannel; +use Tempest; -

Debug

Listening at /Users/brent/Dev/tempest-docs/log/debug.log +return new MultipleChannelsLogConfig( + channels: [ + new DailyLogChannel( + path: Tempest\internal_storage_path('logs', 'tempest.log'), + maxFiles: Tempest\env('LOG_MAX_FILES', default: 31), + minimumLogLevel: LogLevel::DEBUG, + ), + new SlackLogChannel( + webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'), + channelId: '#alerts', + minimumLogLevel: LogLevel::CRITICAL, + ), + ], + prefix: null, +); ``` -Finally, the `ll()` function will do exactly the same as `lw()`, but **only write to the debug log, and not output anything in the browser or terminal**. +### Using multiple loggers -## Logging channels +In situations where you would like to log different types of information to different places, you may create multiple tagged configurations to create separate loggers for different purposes. -On top of debug logging, Tempest includes a monolog implementation which allows you to log to one or more channels. Writing to the logger is as simple as injecting `\Tempest\Log\Logger` wherever you'd like: +For instance, you could have a logger dedicated to critical alerts, while each of your application's module have its own logger: -```php -// app/Rss.php +```php src/Monitoring/logging.config.php +use Tempest\Log\Config\SlackLogConfig; +use Modules\Monitoring\Logging; +use Tempest; + +return new SlackLogConfig( + webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'), + channelId: '#alerts', + minimumLogLevel: LogLevel::CRITICAL, + tag: Logging::SLACK, +); +``` + +```php src/Orders/logging.config.php +use Tempest\Log\Config\DailyLogConfig; +use Modules\Monitoring\Logging; +use Tempest; -use Tempest\Console\Console; -use Tempest\Console\ConsoleCommand; +return new DailyLogConfig( + path: Tempest\internal_storage_path('logs', 'orders.log'), + tag: Logging::ORDERS, +); +``` + +Using this approach, you can inject the appropriate logger using [tagged singletons](../1-essentials/05-container.md#tagged-singletons). This gives you the flexibility to customize logging behavior in different parts of your application. + +```php src/Orders/ProcessOrder.php use Tempest\Log\Logger; -final readonly class Rss +final readonly class ProcessOrder { public function __construct( - private Console $console, + #[Tag(Logging::ORDERS)] private Logger $logger, ) {} - #[ConsoleCommand] - public function sync() + public function __invoke(Order $order): void { - $this->logger->info('Starting RSS sync'); - - // … + $this->logger->info('Processing new order', ['order' => $order]); + + // ... } } ``` -If you're familiar with [monolog](https://seldaek.github.io/monolog/), you know how it supports multiple handlers to handle a log message. Tempest adds a small layer on top of these handlers called channels, they can be configured within `LogConfig`: +### Available configurations and channels -```php -// app/Config/log.config.php +Tempest provides a few log channels that correspond to common logging needs: -use Tempest\Log\LogConfig; -use Tempest\Log\Channels\AppendLogChannel; +- {b`Tempest\Log\Channels\AppendLogChannel`} — append all messages to a single file without rotation, +- {b`Tempest\Log\Channels\DailyLogChannel`} — create a new file each day and remove old files automatically, +- {b`Tempest\Log\Channels\WeeklyLogChannel`} — create a new file each week and remove old files automatically, +- {b`Tempest\Log\Channels\SlackLogChannel`} — send messages to a Slack channel via webhook, +- {b`Tempest\Log\Channels\SysLogChannel`} — write messages to the system log. -return new LogConfig( - channels: [ - new AppendLogChannel(path: __DIR__ . '/../log/project.log'), - ] -); -``` +As a convenient abstraction, a configuration class for each channel is provided: + +- {b`Tempest\Log\Config\SimpleLogConfig`} +- {b`Tempest\Log\Config\DailyLogConfig`} +- {b`Tempest\Log\Config\WeeklyLogConfig`} +- {b`Tempest\Log\Config\SlackLogConfig`} +- {b`Tempest\Log\Config\SysLogConfig`} + +These configuration classes also accept a `channels` property, which allows for providing multiple channels for a single logger. Alternatively, you may use the {b`Tempest\Log\Config\MultipleChannelsLogConfig`} configuration class to achieve the same result more explicitly. -**Please note:** +## Debugging -- Currently, Tempest only supports the `AppendLogChannel` and `DailyLogChannel`, but we're adding more channels in the future. You can always add your own channels by implementing `\Tempest\Log\LogChannel`. -- Also, it's currently not possible to configure environment-specific logging channels, this we'll also support in the future. Again, you're free to make your own channels that take the current environment into account. +Tempest includes several global functions for debugging. Typically, these functions are for quick debugging and should not be committed to production. + +- `ll()` — writes values to the debug log without displaying them, +- `lw()` (also `dump()`) — logs values and displays them, +- `ld()` (also `dd()`) — logs values, displays them, and stops execution, +- `le()` — logs values and emits an {b`Tempest\Debug\ItemsDebugged`} event. + +### Tailing debug logs + +Debug logs are written with console formatting, so they can be tailed with syntax highlighting. You may use `./tempest tail:debug` to monitor the debug log in real time. + +:::warning +By default, debug logs are cleared every time the `tail:debug` command is run. If you want to keep previous log entries, you may pass the `--no-clear` flag. +::: + +### Configuring the debug log + +By default, the debug log is written to `.tempest/debug.log`. This is configurable by creating a `debug.config.php` file that returns a {b`Tempest\Debug\DebugConfig`} with a different `path`: + +```php config/debug.config.php +use Tempest\Debug\DebugConfig; +use Tempest; + +return new DebugConfig( + logPath: Tempest\internal_storage_path('logs', 'debug.log') +); +``` diff --git a/docs/2-features/10-command-bus.md b/docs/2-features/10-command-bus.md index 1112b541af..2a21ded5b0 100644 --- a/docs/2-features/10-command-bus.md +++ b/docs/2-features/10-command-bus.md @@ -62,7 +62,7 @@ Alternatively to using the `command()` function, you can inject the `CommandBus` use Tempest\CommandBus\CommandBus; -final readonly class UserController() +final readonly class UserController { public function __construct( private CommandBus $commandBus, diff --git a/docs/2-features/11-localization.md b/docs/2-features/11-localization.md index 3ff24b2bc5..234ca691f7 100644 --- a/docs/2-features/11-localization.md +++ b/docs/2-features/11-localization.md @@ -65,7 +65,7 @@ final readonly class SetLocaleMiddleware implements HttpMiddleware ## Defining translation messages -Translation messages are usually stored in translation files. Tempest automatically [discovers](../4-internals/02-discovery.md) YAML and JSON translation files that use the `..{yaml,json}` naming format, where `` may be any string, and `` must be an [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code. +Translation messages are usually stored in translation files. Tempest automatically [discovers](../1-essentials/05-discovery.md) YAML and JSON translation files that use the `..{yaml,json}` naming format, where `` may be any string, and `` must be an [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) language code. For instance, you may store translation files in a `lang` directory: @@ -157,7 +157,7 @@ final class DateTimeFunction implements FormattingFunction { public string $name = 'datetime'; - public function evaluate(mixed $value, array $parameters): FormattedValue + public function format(mixed $value, array $parameters): FormattedValue { $datetime = DateTime::parse($value); $formatted = $datetime->format(Arr\get_by_key($parameters, 'pattern')); diff --git a/docs/2-features/11-scheduling.md b/docs/2-features/11-scheduling.md index 3fb63d0fc7..1ad9b4c34d 100644 --- a/docs/2-features/11-scheduling.md +++ b/docs/2-features/11-scheduling.md @@ -5,7 +5,7 @@ description: 'Tempest provides a modern and convenient way of scheduling tasks, ## Overview -Dealing with repeating, scheduled tasks is as simple as adding the {`#[Tempest\Console\Schedule]`} attribute to any class method. As with console commands, [discovery](../4-internals/02-discovery.md) takes care of finding these methods and registering them. +Dealing with repeating, scheduled tasks is as simple as adding the {`#[Tempest\Console\Schedule]`} attribute to any class method. As with console commands, [discovery](../1-essentials/05-discovery.md) takes care of finding these methods and registering them. ## Using the scheduler diff --git a/docs/2-features/13-static-pages.md b/docs/2-features/13-static-pages.md index 501b911dad..aaaf92e332 100644 --- a/docs/2-features/13-static-pages.md +++ b/docs/2-features/13-static-pages.md @@ -11,7 +11,7 @@ When a controller action is tagged with {b`#[Tempest\Router\StaticPage]`}, it ca use Tempest\Router\Get; use Tempest\Router\StaticPage; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class FrontPageController { @@ -61,7 +61,7 @@ In this case, the {b`#[Tempest\Router\StaticPage]`} attribute gets a reference t ```php app/Documentation/ChapterDataProvider.php use Tempest\Router\DataProvider; -final readonly class DocsDataProvider implements DataProvider +final readonly class ChapterDataProvider implements DataProvider { public function provide(): Generator { @@ -79,7 +79,7 @@ In other words: we want to generate a page for every documentation chapter. We c ```php app/Documentation/ChapterDataProvider.php use Tempest\Router\DataProvider; -final readonly class DocsDataProvider implements DataProvider +final readonly class ChapterDataProvider implements DataProvider { public function __construct( private ChapterRepository $chapters diff --git a/docs/2-features/14-exception-handling.md b/docs/2-features/14-exception-handling.md index e4bcd2ed94..8567aebd7e 100644 --- a/docs/2-features/14-exception-handling.md +++ b/docs/2-features/14-exception-handling.md @@ -1,66 +1,58 @@ --- title: Exception handling -description: "Learn how to gracefully handle exceptions in your application by writing exception processors." +description: "Learn how exception handling works, how to manually report exceptions, and how to customize exception rendering for HTTP responses." --- ## Overview -Tempest comes with its own exception handler, which provides a simple way to catch and process exceptions. During local development, Tempest uses [Whoops](https://github.com/filp/whoops) to display detailed error pages. In production, it will show a generic error page. +Tempest comes with an exception handler that provides a simple way to report exceptions and render error responses. -When an exception is thrown, it will be caught and piped through the registered exception processors. By default, the only registered exception processor, {b`Tempest\Core\LogExceptionProcessor`}, will simply log the exception. +Custom [exception reporters](#writing-exception-reporters) can be created by implementing the {b`Tempest\Core\Exceptions\ExceptionReporter`} interface, and custom [exception renderers](#customizing-exception-rendering) can be created by implementing {b`Tempest\Router\Exceptions\ExceptionRenderer`}. These classes are automatically [discovered](../1-essentials/05-discovery.md) and do not require manual registration. -Of course, you may create your own exception processors. This is done by creating a class that implements the {`Tempest\Core\ExceptionProcessor`} interface. Classes implementing this interface are automatically [discovered](../4-internals/02-discovery.md), so you don't need to register them manually. +## Processing exceptions -## Reporting exceptions +Exceptions can be reported without throwing them using the `process()` method of the {b`Tempest\Core\Exceptions\ExceptionProcessor`} interface. This allows putting exceptions through the reporting process without stopping the application's execution. -Sometimes, you may want to report an exception without necessarily throwing it. For example, you may want to log an exception, but not stop the execution of the application. To do this, you can use the `Tempest\report()` function. +```php app/CreateUser.php +use Tempest\Core\Exceptions\ExceptionProcessor; -```php -use function Tempest\report; - -try { - // Some code that may throw an exception -} catch (SomethingFailed $e) { - report($e); -} -``` - -## Disabling default logging - -Exception processors are discovered when Tempest boots, then stored in the `exceptionProcessors` property of {`Tempest\Core\AppConfig`}. The default logging processor, {b`Tempest\Core\LogExceptionProcessor`}, is automatically added to the list of processors. - -To disable exception logging, you may remove it in a `KernelEvent::BOOTED` event handler: - -```php -use Tempest\Core\AppConfig; -use Tempest\Core\KernelEvent; -use Tempest\Core\LogExceptionProcessor; -use Tempest\EventBus\EventHandler; -use Tempest\Support\Arr; - -final readonly class DisableExceptionLogging +final readonly class CreateUser { public function __construct( - private AppConfig $appConfig, - ) { - } + private ExceptionProcessor $exceptions + ) {} - #[EventHandler(KernelEvent::BOOTED)] public function __invoke(): void { - Arr\forget_values($this->appConfig->exceptionProcessors, LogExceptionProcessor::class); + try { + // Some code that may throw an exception + } catch (SomethingFailed $somethingFailed) { + $this->exceptions->process($somethingFailed); + } } } ``` +## Disabling exception logging + +The default logging reporter, {b`Tempest\Core\Exceptions\LoggingExceptionReporter`}, is automatically added to the list of reporters. To disable it, create a {b`Tempest\Core\Exceptions\ExceptionsConfig`} [configuration file](../1-essentials/06-configuration.md#configuration-files) and set `logging` to `false`: + +```php app/exceptions.config.php +use Tempest\Core\Exceptions\ExceptionsConfig; + +return new ExceptionsConfig( + logging: false, +); +``` + ## Adding context to exceptions -Sometimes, an exception may have information that you would like to be logged. By implementing the {`Tempest\Core\HasContext`} interface on an exception class, you can provide additional context that will be logged—and available to other processors. +Exceptions can provide additional information for logging by implementing the {`Tempest\Core\ProvidesContext`} interface. The context data becomes available to exception processors. ```php -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; -final readonly class UserWasNotFound extends Exception implements HasContext +final readonly class UserWasNotFound extends Exception implements ProvidesContext { public function __construct(private string $userId) { @@ -76,47 +68,153 @@ final readonly class UserWasNotFound extends Exception implements HasContext } ``` -## Customizing the error page +## Writing exception reporters -In production, when an uncaught exception occurs, Tempest displays a minimalistic, generic error page. You may customize this behavior by adding a middleware dedicated to catching {b`Tempest\Http\HttpRequestFailed`} exceptions. +Exception reporters allow defining custom reporting logic for exceptions, such as sending them to external error tracking services like Sentry or logging them to specific destinations. -For instance, you may display a branded error page by providing a view: +To create a custom reporter, implement the {b`Tempest\Core\Exceptions\ExceptionReporter`} interface and define a `report()` method: -```php +```php app/SentryExceptionReporter.php +use Tempest\Core\Exceptions\ExceptionReporter; +use Throwable; + +final class SentryExceptionReporter implements ExceptionReporter +{ + public function __construct( + private SentryClient $sentry, + ) {} + + public function report(Throwable $throwable): void + { + $this->sentry->captureException($throwable); + } +} +``` + +Exception reporters are automatically [discovered](../4-internals/02-discovery.md) and registered. All registered reporters are invoked whenever an exception is processed, allowing multiple reporters to handle the same exception. + +For example, the default logging reporter logs to a file, while the reporter above sends the error to Sentry. + +If an exception reporter throws an exception during execution, it is silently caught to prevent infinite loops. This ensures that a failing reporter doesn't prevent other reporters from running. + +### Accessing exception context + +Exceptions can implement the {b`Tempest\Core\ProvidesContext`} interface, which reporters can leverage to provide additional context data during reporting: + +```php app/SentryExceptionReporter.php +use Tempest\Core\Exceptions\ExceptionReporter; +use Tempest\Core\ProvidesContext; +use Sentry\State\HubInterface as Sentry; +use Sentry\State\Scope; + +final class SentryExceptionReporter implements ExceptionReporter +{ + public function __construct( + private readonly Sentry $sentry, + ) {} + + public function report(Throwable $throwable): void + { + $this->sentry->withScope(function (Scope $scope) use ($throwable) { + if ($throwable instanceof ProvidesContext) { + $scope->withContext($throwable->context()); + } + + $scope->captureException($throwable); + }); + } +} +``` + +### Conditional reporting + +Reporters can implement conditional logic to only report specific exception types or under certain conditions. There is no built-in filtering mechanism; reporters are responsible for determining when to report an exception. + +```php app/CriticalErrorReporter.php +use Tempest\Core\Exceptions\ExceptionReporter; +use Throwable; + +final class CriticalErrorReporter implements ExceptionReporter +{ + public function __construct( + private AlertService $alerts, + ) {} + + public function report(Throwable $throwable): void + { + if (! $throwable instanceof CriticalException) { + return; + } + + $this->alerts->sendCriticalAlert( + message: $throwable->getMessage(), + ); + } +} +``` + +## Customizing exception rendering + +Exception renderers provide control over how exceptions are rendered in HTTP responses. Custom renderers can be used to display specialized error pages for specific exception types, format errors differently based on content type (JSON, HTML, XML), or provide user-friendly error messages for common scenarios like 404 or validation failures. + +To create a custom renderer, implement the {b`Tempest\Router\Exceptions\ExceptionRenderer`} interface. It requires a `canRender()` method to determine if the renderer can handle the given exception and request, and a `render()` method to produce the response: + +```php app/NotFoundExceptionRenderer.php +use Tempest\Http\ContentType; use Tempest\Http\HttpRequestFailed; -use Tempest\Router\HttpMiddleware; -use function Tempest\view; +use Tempest\Http\Request; +use Tempest\Http\Response; +use Tempest\Http\Responses\NotFound; +use Tempest\Http\Status; +use Tempest\Router\Exceptions\ExceptionRenderer; +use Throwable; -final class CatchHttpRequestFailuresMiddleware implements HttpMiddleware +use function Tempest\View\view; + +final class NotFoundExceptionRenderer implements ExceptionRenderer { - public function __invoke(Request $request, HttpMiddlewareCallable $next): Response + public function canRender(Throwable $throwable, Request $request): bool { - try { - return $next($request); - } catch (HttpRequestFailed $failure) { - return new GenericResponse( - status: $failure->status, - body: view('./error.view.php', failure: $failure), - ); + if (! $request->accepts(ContentType::HTML)) { + return false; } + + if (! $throwable instanceof HttpRequestFailed) { + return false; + } + + return $throwable->status === Status::NOT_FOUND; + } + + public function render(Throwable $throwable): Response + { + return new NotFound( + body: view('./404.view.php'), + ); } } ``` +:::info +Exception renderers are automatically [discovered](../4-internals/02-discovery.md) and checked in {b`#[Tempest\Core\Priority]`} order. +::: + ## Testing -By extending {`Tempest\Framework\Testing\IntegrationTest`} from your test case, you gain access to the exception testing utilities, which allow you to make assertions about reported exceptions. +By extending {`Tempest\Framework\Testing\IntegrationTest`} from a test case, exception testing utilities may be accessed for making assertions about processed exceptions. ```php -// Prevents exceptions from being actually processed -$this->exceptions->preventReporting(); +// Allows exceptions to be processed during tests +$this->exceptions->allowProcessing(); -// Asserts that the exception was reported -$this->exceptions->assertReported(UserNotFound::class); +// Assert that the exception was processed +$this->exceptions->assertProcessed(UserNotFound::class); -// Asserts that the exception was not reported -$this->exceptions->assertNotReported(UserNotFound::class); +// Assert that the exception was not processed +$this->exceptions->assertNotProcessed(UserNotFound::class); -// Asserts that no exceptions were reported -$this->exceptions->assertNothingReported(); +// Assert that no exceptions were processed +$this->exceptions->assertNothingProcessed(); ``` + +By default, Tempest disables exception processing during tests. It is recommended to unit-test your own {b`Tempest\Core\Exceptions\ExceptionReporter`} implementations. diff --git a/docs/2-features/15-datetime.md b/docs/2-features/15-datetime.md index 66bf69c196..e07bf8aea5 100644 --- a/docs/2-features/15-datetime.md +++ b/docs/2-features/15-datetime.md @@ -92,10 +92,10 @@ $duration = $date1->between($date2); The {b`Tempest\DateTime\DateTime`} instance provides multiple methods to compare dates against each other, or against the current time. For instance, you may check if a date is before or after another date using the `isBefore()` and `isAfter()` methods, respectively. ```php -// Check if a date is before another date, inclusively +// Check if a date is before another date (exclusive - does not include the comparison date) $date->isBefore($other); -// Check if a date is before another date, exclusively +// Check if a date is before or at the same time as another date (inclusive - includes the comparison date) $date->isBeforeOrAtTheSameTime($other); // Check if a date between two other dates, inclusively @@ -113,9 +113,9 @@ You may format a {b`Tempest\DateTime\DateTime`} instance in a specific format us use Tempest\DateTime\FormatPattern; use Tempest\Intl\Locale; -$date->format(); // 19 Sept 2025, 02:00:00 -$date->format(pattern: FormatPattern::COOKIE); // Monday, 19-Sept-2025 02:00:00 BST -$date->format(locale: Locale::FRENCH); // 19 sept. 2025, 02:00:00 +$date->format(); // Jan 7, 2026, 10:30:05 PM +$date->format(pattern: FormatPattern::COOKIE); // Wednesday, 07-Jan-2026 22:30:46 UTC +$date->format(locale: Locale::FRENCH); // 7 janv. 2026, 22:32:12 ``` ## Clock interface diff --git a/docs/2-features/16-process.md b/docs/2-features/16-process.md index 550360e78f..721be15960 100644 --- a/docs/2-features/16-process.md +++ b/docs/2-features/16-process.md @@ -55,7 +55,7 @@ $this->executor ## Process pools -It is possible to execute multiple tasks simultaneously using a process pool. To do so, you may call the `pool()` method on the {`Tempest\Process\ProcessExecutor`}. This returns a {b`Tempest\Process\InvokedProcessPool`} instance, which provides convenient methods for managing the processes. +It is possible to execute multiple tasks simultaneously using a process pool. To do so, you may call the `pool()` method on the {`Tempest\Process\ProcessExecutor`}. This returns a {b`Tempest\Process\Pool`} instance, which provides convenient methods for managing the processes. ```php $pool = $this->executor->pool([ diff --git a/docs/2-features/17-oauth.md b/docs/2-features/17-oauth.md index 75ac6dc599..902869ce91 100644 --- a/docs/2-features/17-oauth.md +++ b/docs/2-features/17-oauth.md @@ -220,6 +220,7 @@ Tempest provides a different configuration object for each OAuth provider. Below - **Microsoft** authentication using {b`Tempest\Auth\OAuth\Config\MicrosoftOAuthConfig`}, - **Slack** authentication using {b`Tempest\Auth\OAuth\Config\SlackOAuthConfig`}, - **Apple** authentication using {b`Tempest\Auth\OAuth\Config\AppleOAuthConfig`}, +- **Twitch** authentication using {b`Tempest\Auth\OAuth\Config\TwitchOAuthConfig`}, - Any other OAuth platform using {b`Tempest\Auth\OAuth\Config\GenericOAuthConfig`}. ## Testing diff --git a/docs/3-packages/02-console.md b/docs/3-packages/02-console.md index d7d3a7a7a6..bb67fb1534 100644 --- a/docs/3-packages/02-console.md +++ b/docs/3-packages/02-console.md @@ -26,13 +26,17 @@ ConsoleApplication::boot()->run(); ## Registering commands -`tempest/console` relies on [discovery](../4-internals/02-discovery.md) to find and register console commands. That means you don't have to register any commands manually, and any method within your codebase using the `{php}#[ConsoleCommand]` attribute will automatically be discovered by your console application. +`tempest/console` relies on [discovery](../1-essentials/05-discovery.md) to find and register console commands. That means you don't have to register any commands manually, and any method within your codebase using the `{php}#[ConsoleCommand]` attribute will automatically be discovered by your console application. You may read more about building commands in the [dedicated documentation](../1-essentials/04-console-commands.md). ## Configuring discovery -Tempest will discover all console commands within namespaces configured as valid PSR-4 namespaces, as well as all third-party packages that require Tempest. +Tempest will automatically discover all console commands from multiple sources: + +1. **Core Tempest packages** — Built-in commands from Tempest itself +2. **Vendor packages** — Third-party packages that require `tempest/framework` or `tempest/core` +3. **App namespaces** — All namespaces configured as PSR-4 autoload paths in your `composer.json` ```json { @@ -44,21 +48,24 @@ Tempest will discover all console commands within namespaces configured as valid } ``` -In case you need more fine-grained control over which directories to discover, you may provide a custom {`Tempest\Core\AppConfig`} instance to the `{php}ConsoleApplication::boot()` method: +In case you need more fine-grained control over which directories to discover, you may provide additional discovery locations to the `{php}ConsoleApplication::boot()` method: ```php -use Tempest\AppConfig; -use Tempest\Core\DiscoveryLocation; use Tempest\Console\ConsoleApplication; +use Tempest\Discovery\DiscoveryLocation; -$appConfig = new AppConfig( +ConsoleApplication::boot( discoveryLocations: [ new DiscoveryLocation( - namespace: 'App\\', - path: __DIR__ . '/app/', + namespace: 'MyApp\\', + path: __DIR__ . '/src', ), ], -); - -ConsoleApplication::boot(appConfig: $appConfig)->run(); +)->run(); ``` + +The `{php}boot()` method accepts the following parameters: + +- `{php}$name` — The application name (default: `'Tempest'`) +- `{php}$root` — The root directory (default: current working directory) +- `{php}$discoveryLocations` — Additional discovery locations to append to the auto-discovered ones diff --git a/docs/4-internals/01-bootstrap.md b/docs/4-internals/01-bootstrap.md deleted file mode 100644 index 7d48810907..0000000000 --- a/docs/4-internals/01-bootstrap.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Framework bootstrap -description: "Learn the steps involved in bootstrapping the framework." ---- - -## Overview - -Here's a short summary of what booting Tempest looks like. - -- The entry point is either `public/index.php` or `./tempest`. -- Tempest boots using the {b`\Tempest\Core\FrameworkKernel`}. -- Bootstrap classes are located in the [`Tempest\Core\Kernel`](https://github.com/tempestphp/tempest-framework/tree/main/packages/core/src/Kernel) namespace. -- First, discovery is started through the {b`\Tempest\Core\LoadDiscoveryLocations`} and {b`\Tempest\Core\LoadDiscoveryClasses`} classes. -- Then, configuration files are registered through the {b`\Tempest\Core\LoadConfig`} class. -- When bootstrapping is completed, the `Tempest\Core\KernelEvent::BOOTED` event is fired. diff --git a/docs/4-internals/01-lifecycle.md b/docs/4-internals/01-lifecycle.md new file mode 100644 index 0000000000..7730c60dab --- /dev/null +++ b/docs/4-internals/01-lifecycle.md @@ -0,0 +1,25 @@ +--- +title: Framework lifecycle +description: "Learn the steps involved in booting, running and shutting down the framework." +--- + +## Booting + +Tempest's entry point is usually `public/index.php` or `./tempest`. The former uses {b`Tempest\Router\HttpApplication`}, the latter {b`Tempest\Console\ConsoleApplication`}. + +When created, the application boots by creating the {b`\Tempest\Core\FrameworkKernel`}: + +- it loads the environment, the exception handler, and configures the container, +- it then starts discovery through the {b`\Tempest\Core\Kernel\LoadDiscoveryLocations`} and {b`\Tempest\Core\Kernel\LoadDiscoveryClasses`} classes, +- and finally registers configuration files through the {b`\Tempest\Core\Kernel\LoadConfig`} class. + +When bootstrapping is completed, the `Tempest\Core\KernelEvent::BOOTED` event is fired. + +## Shutdown + +The shutdown process is managed by the kernel's `shutdown()` method, which is called at the end of both HTTP and console lifecycles, as well as in exception handlers. This method: + +- runs deferred tasks, +- dispatches the `KernelEvent::SHUTDOWN` event, +- performs any necessary cleanup, +- and terminates the application process. diff --git a/docs/4-internals/02-discovery.md b/docs/4-internals/02-discovery.md deleted file mode 100644 index d5225d2ef8..0000000000 --- a/docs/4-internals/02-discovery.md +++ /dev/null @@ -1,221 +0,0 @@ ---- -title: Discovery -description: "Learn how Tempest automatically locates controller actions, event handlers, console commands, and other components of your application." ---- - -:::info -Read the [getting started with discovery](/blog/discovery-explained) guide if you are new to Tempest. -::: - -## Overview - -Tempest introduces a unique approach to bootstrapping an application. Instead of requiring manual registration of project code and packages, Tempest automatically scans the codebase and detects the components that should be loaded. This process is called **discovery**. - -Discovery is powered by composer metadata. Every package that depends on Tempest, along with your application's own code, are included in the discovery process. Tempest applies various rules to determine the purpose of different pieces of code. It can analyze file names, attributes, interfaces, return types, and more. - -For instance, web routes are discovered based on route attributes: - -```php app/HomeController.php -final readonly class HomeController -{ - #[Get(uri: '/home')] - public function __invoke(): View - { - return view('home.view.php'); - } -} -``` - -Note that Tempest is able to cache discovery information to avoid any performance cost. Enabling this cache in production is highly recommended. - -## Built-in discovery classes - -Most of Tempest's features are built on top of discovery. The following describes which discovery class is associated to which feature. - -- {`\Tempest\Core\DiscoveryDiscovery`}
- Discovers other discovery classes. This class is run manually by the framework when booted. -- {`\Tempest\CommandBus\CommandBusDiscovery`}
- Discovers methods with the `#[CommandHandler]` attribute and registers them into the command bus. -- {`\Tempest\Console\Discovery\ConsoleCommandDiscovery`}
- Discovers methods with the `#[ConsoleCommand]` attribute and registers them as console commands. -- {`\Tempest\Console\Discovery\ScheduleDiscovery`}
- Discovers methods with the `#[Schedule]` attribute and registers them as scheduled tasks. -- {`\Tempest\Container\InitializerDiscovery`}
- Discovers classes that implement {b`\Tempest\Container\Initializer`} or {b`\Tempest\Container\DynamicInitializer`} and registers them in the container. -- {`\Tempest\Database\MigrationDiscovery`}
- Discovers classes that implement {`\Tempest\Database\Migration`} and registers them in the migration manager. -- {`\Tempest\EventBusDiscovery\EventBusDiscovery`}
- Discovers methods with the `#[EventHandler]` attribute and registers them in the event bus. -- {`\Tempest\Router\RouteDiscovery`}
- Discovers route attributes on methods and registers them as controller actions in the router. -- {`\Tempest\Mapper\MapperDiscovery`}
- Discovers classes that implement {`\Tempest\Mapper\Mapper`}, which are registered in `\Tempest\Mapper\ObjectFactory` -- {`\Tempest\View\ViewComponentDiscovery`}
- Discovers classes that implement {`\Tempest\View\ViewComponent`}, as well as view files that contain `{html}` or named `x-*.view.php` -- {`\Tempest\Vite\ViteDiscovery`}
- Discovers `*.entrypoint.{ts,js,css}` files and register them as entrypoints. - -## Implementing your own discovery - -### Discovering code in classes - -Tempest will discover classes that implement {`\Tempest\Discovery\Discovery`}. You may create one, and implement the `discover()` and `apply` methods. - -The `discover()` method accepts a {b`Tempest\Core\DiscoveryLocation`} and a {b`Tempest\Reflection\ClassReflector`} parameter. You may use the latter to loop through a class' attributes, methods, parameters or anything else. - -If you find what you are interested in, you may register it using `$this->discoveryItems->add()`. As an example, the following is a simplified version of the event bus discovery: - -```php EventBusDiscovery.php -use Tempest\Discovery\Discovery; -use Tempest\Discovery\IsDiscovery; - -final readonly class EventBusDiscovery implements Discovery -{ - // This provides the default implementation for `Discovery`'s internals - use IsDiscovery; - - public function __construct( - // Discovery classes are autowired, - // so you can inject all dependencies you need - private EventBusConfig $eventBusConfig, - ) { - } - - public function discover(DiscoveryLocation $location, ClassReflector $class): void - { - foreach ($class->getPublicMethods() as $method) { - $eventHandler = $method->getAttribute(EventHandler::class); - - // Extra checks to determine whether - // we can actually use the current method as an event handler - - // … - - // Finally, we add all discovery-related data into `$this->discoveryItems`: - $this->discoveryItems->add($location, [$eventName, $eventHandler, $method]); - } - } - - // Next, the `apply` method is called whenever discovery is ready to be - // applied into the framework. In this case, we want to loop over all - // registered discovery items, and add them to the event bus config. - public function apply(): void - { - foreach ($this->discoveryItems as [$eventName, $eventHandler, $method]) { - $this->eventBusConfig->addClassMethodHandler( - event: $eventName, - handler: $eventHandler, - reflectionMethod: $method, - ); - } - } -} -``` - -### Discovering files - -In some situations, you may want to not just discover classes, but also files. For instance, view files, front-end entrypoints or SQL migrations are not PHP classes, but still need to be discovered. - -In this case, you may implement the additional {`\Tempest\Discovery\DiscoversPath`} interface. It will allow a discovery class to discover all paths that aren't classes as well. As an example, below is a simplified version of the Vite discovery: - -```php -use Tempest\Discovery\Discovery; -use Tempest\Discovery\DiscoversPath; -use Tempest\Discovery\IsDiscovery; - -final class ViteDiscovery implements Discovery, DiscoversPath -{ - use IsDiscovery; - - public function __construct( - private readonly ViteConfig $viteConfig, - ) {} - - // We are not discovering any class, so we return immediately. - public function discover(DiscoveryLocation $location, ClassReflector $class): void - { - return; - } - - // This method is called for every file in registered discovery locations. - // We can use the `$path` to determine whether we are interested in it. - public function discoverPath(DiscoveryLocation $location, string $path): void - { - // We are insterested in `.ts`, `.css` and `.js` files only. - if (! ends_with($path, ['.ts', '.css', '.js'])) { - return; - } - - // These files need to be specifically marked as `.entrypoint`. - if (! str($path)->beforeLast('.')->endsWith('.entrypoint')) { - return; - } - - $this->discoveryItems->add($location, [$path]); - } - - // When discovery is cached, `discover` and `discoverPath` are not called. - // Instead, `discoveryItems` is already fed with serialized data, which - // we can use. In this case, we add the paths to the Vite config. - public function apply(): void - { - foreach ($this->discoveryItems as [$path]) { - $this->viteConfig->addEntrypoint($path); - } - } -} -``` - -## Discovery in production - -While discovery is a really powerful feature, it also comes with some performance considerations. In production environments, you need to make sure that the discovery workflow is cached. This is done by using the `DISCOVERY_CACHE` environment variable: - -```env .env -{:hl-property:DISCOVERY_CACHE:}={:hl-keyword:true:} -``` - -The most important step is to generate that cache. This is done by running the `discovery:generate`, which should be part of your deployment pipeline. Make sure to run it before any other Tempest command. - -```console -./tempest discovery:generate - ℹ Clearing existing discovery cache… - ✓ Discovery cached has been cleared - ℹ Generating new discovery cache… (cache strategy used: all) - ✓ Cached 1119 items -``` - -## Discovery for local development - -By default, the discovery cache is disabled in a development environment. Depending on your local setup, it is likely that you will not run into noticeable slowdowns. However, for larger projects, you might benefit from enabling a partial discovery cache: - -```env .env -{:hl-property:DISCOVERY_CACHE:}={:hl-keyword:partial:} -``` - -This caching strategy will only cache discovery for vendor files. For this reason, it is recommended to run `discovery:generate` after every composer update: - -```json -{ - "scripts": { - "post-package-update": [ - "php tempest discovery:generate" - ] - } -} -``` - -:::info -Note that, if you've created your project using {`tempest/app`}, you'll have the `post-package-update` script already included. You may read the [internal documentation about discovery](../3-internals/02-discovery) to learn more. -::: - -## Excluding files and classes from discovery - -If needed, you can always exclude discovered files and classes by providing a discovery config file: - -```php app/discovery.config.php -use Tempest\Core\DiscoveryConfig; - -return new DiscoveryConfig() - ->skipClasses(GlobalHiddenDiscovery::class) - ->skipPaths(__DIR__ . '/../../Fixtures/GlobalHiddenPathDiscovery.php'); -``` diff --git a/docs/4-internals/03-view-spec.md b/docs/4-internals/02-view-spec.md similarity index 99% rename from docs/4-internals/03-view-spec.md rename to docs/4-internals/02-view-spec.md index dad8aa800c..8636f6e55a 100644 --- a/docs/4-internals/03-view-spec.md +++ b/docs/4-internals/02-view-spec.md @@ -63,7 +63,7 @@ The following loop: Will be compiled to: ```html - + $item) { ?>
A
@@ -340,7 +340,7 @@ When a single slot is not enough, names can be attached to them. When using a co ``` -The above example uses a slot named `styles` in its `` element. The `` element has a default, unnamed slot. A view component may use `` and optionally refer to the `styles` slot using the syntax mentionned above, or simply provide content that will be injected in the default slot: +The above example uses a slot named `styles` in its `` element. The `` element has a default, unnamed slot. A view component may use `` and optionally refer to the `styles` slot using the syntax mentioned above, or simply provide content that will be injected in the default slot: ```html index.view.php diff --git a/docs/5-extra-topics/01-package-development.md b/docs/5-extra-topics/01-package-development.md index 911c3a1e3d..97d4eaa7f9 100644 --- a/docs/5-extra-topics/01-package-development.md +++ b/docs/5-extra-topics/01-package-development.md @@ -5,9 +5,9 @@ description: "Tempest comes with a handful of tools to help third-party package ## Overview -Creating a package for Tempest is as simple as adding `tempest/core` as a dependency. When this happens, [discovery](../4-internals/02-discovery.md) will find the package thanks to composer metadata and register discoverable classes. +Creating a package for Tempest is as simple as adding `tempest/core` as a dependency. When this happens, [discovery](../1-essentials/05-discovery.md) will find the package thanks to composer metadata and register discoverable classes. -Unlike Symfony or Laravel, Tempest doesn't have a dedicated "service provider" concept. Instead, you're encouraged to rely on [discovery](../4-internals/02-discovery.md) and [initializers](../1-essentials/05-container#dependency-initializers). +Unlike Symfony or Laravel, Tempest doesn't have a dedicated "service provider" concept. Instead, you're encouraged to rely on [discovery](../1-essentials/05-discovery.md) and [initializers](../1-essentials/05-container#dependency-initializers). ## Preventing discovery @@ -49,11 +49,8 @@ final readonly class AuthInstaller implements Installer { use PublishesFiles; - public function getName(): string - { - return 'auth'; - } - + private(set) string $name = 'auth'; + public function install(): void { $publishFiles = [ diff --git a/docs/5-extra-topics/02-standalone-components.md b/docs/5-extra-topics/02-standalone-components.md index f108d6df9c..f08a761af9 100644 --- a/docs/5-extra-topics/02-standalone-components.md +++ b/docs/5-extra-topics/02-standalone-components.md @@ -134,7 +134,7 @@ $commandBus = $container->get(\Tempest\CommandBus\CommandBus::class); $commandBus->dispatch(new MyCommand()); // Or use the `command` function, which is shipped with the package -\Tempest\command(new \Brendt\MyEvent()); +\Tempest\command(new MyCommand()); ``` ## `tempest/mapper` diff --git a/docs/5-extra-topics/04-contributing.md b/docs/5-extra-topics/04-contributing.md index 9bd6d4404a..df8290c23c 100644 --- a/docs/5-extra-topics/04-contributing.md +++ b/docs/5-extra-topics/04-contributing.md @@ -282,3 +282,34 @@ Pull request titles and descriptions should be as explicit as possible to ease t Contributors are not required to respect conventional commits within pull requests, but doing so will ease the review process by removing some overhead for core contributors. All pull requests will be renamed to the conventional commit convention if necessary before being squash-merged to keep the commit history and changelog clean. + +## Release cycles + +Tempest currently does not follow a fixed release cycle. In general, bug fixes and minor features can be released as soon as possible. For breaking changes, though, we aim to bundle as many as possible in a single major release. + +### Milestones + +Even though bug fixes and minor features can be released whenever available, we do some level of long-term planning to ensure Tempest stays on track. There should always be two active milestones, and one for future versions. + +- The **current minor milestone** includes all issues that should be addressed as patch or minor versions within the current major version. Anything in this milestone should be considered "ready to work on" and can be done at any point in time before the next major release. +- The **next major milestone** includes all issues that are planned for the next major release, many will be breaking changes. Oftentimes, we'll work on both current minor and next major milestones at the same time. +- The **next minor milestone** includes all issues that should be addressed as patch or minor versions after the next major release has been tagged. +- All other issues that don't get assigned a milestone are considered to be "unplanned". They might at one point be added to a milestone, but there's no guarantee on timing. + +As an example: + +- The current Tempest version is `2.14`, that means that the current minor milestone is `2.x` +- The next major release is planned for `3.0`, so the next major milestone is `3.0` +- The next minor milestone includes features that are planned after 3.0 is released, and thus go in the `3.x` milestone + +For clarity, each milestone will get its corresponding name, with the target branch or tag at the end. For the previous example, the milestones are called: + +- `current minor (2.x)` +- `next major (3.0)` +- `next minor (3.x)` + +Finally, as we close in on tagging `next major`, features that would usually go in `current minor` can be targeted to `next major` instead, in order to avoid too many merge conflicts between the two milestones. + +### Milestone deadlines + +Even though we release on a non-fixed schedule, we do assign deadlines to the `next major` version. This gives all contributors a clear goal to work towards, and helps us stay on track. The dealine for `next major` also determines the end date of `current minor` \ No newline at end of file diff --git a/mago.toml b/mago.toml index 8b029a6e17..72fd2b4194 100644 --- a/mago.toml +++ b/mago.toml @@ -1,4 +1,4 @@ -php-version = "8.4.0" +php-version = "8.5.0" [source] paths = ["src", "packages", "tests"] diff --git a/packages/auth/composer.json b/packages/auth/composer.json index f530ffa195..4a83377c24 100644 --- a/packages/auth/composer.json +++ b/packages/auth/composer.json @@ -2,7 +2,7 @@ "name": "tempest/auth", "description": "A flexible authentication package for Tempest, providing authentication and authorization.", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/core": "dev-main", "tempest/router": "dev-main", "tempest/database": "dev-main", @@ -22,7 +22,7 @@ "adam-paterson/oauth2-slack": "^1.1", "wohali/oauth2-discord-new": "^1.2", "smolblog/oauth2-twitter": "^1.0", - "depotwarehouse/oauth2-twitch": "^1.3" + "vertisan/oauth2-twitch-helix": "^2.0" }, "autoload": { "psr-4": { diff --git a/packages/auth/src/Authentication/AuthenticatorInitializer.php b/packages/auth/src/Authentication/AuthenticatorInitializer.php index 7b13dfb9e9..e87555096b 100644 --- a/packages/auth/src/Authentication/AuthenticatorInitializer.php +++ b/packages/auth/src/Authentication/AuthenticatorInitializer.php @@ -4,11 +4,11 @@ namespace Tempest\Auth\Authentication; -use Tempest\Auth\AuthConfig; use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; use Tempest\Http\Session\Session; +use Tempest\Http\Session\SessionManager; final readonly class AuthenticatorInitializer implements Initializer { @@ -16,7 +16,7 @@ public function initialize(Container $container): Authenticator { return new SessionAuthenticator( - authConfig: $container->get(AuthConfig::class), + sessionManager: $container->get(SessionManager::class), session: $container->get(Session::class), authenticatableResolver: $container->get(AuthenticatableResolver::class), ); diff --git a/packages/auth/src/Authentication/SessionAuthenticator.php b/packages/auth/src/Authentication/SessionAuthenticator.php index 4b6bbfb915..a7357f9794 100644 --- a/packages/auth/src/Authentication/SessionAuthenticator.php +++ b/packages/auth/src/Authentication/SessionAuthenticator.php @@ -4,8 +4,8 @@ namespace Tempest\Auth\Authentication; -use Tempest\Auth\AuthConfig; use Tempest\Http\Session\Session; +use Tempest\Http\Session\SessionManager; final readonly class SessionAuthenticator implements Authenticator { @@ -13,7 +13,7 @@ public const string AUTHENTICATABLE_CLASS = '#authenticatable:class'; public function __construct( - private AuthConfig $authConfig, + private SessionManager $sessionManager, private Session $session, private AuthenticatableResolver $authenticatableResolver, ) {} @@ -34,7 +34,8 @@ public function authenticate(Authenticatable $authenticatable): void public function deauthenticate(): void { $this->session->remove(self::AUTHENTICATABLE_KEY); - $this->session->destroy(); + $this->session->remove(self::AUTHENTICATABLE_CLASS); + $this->sessionManager->save($this->session); } public function current(): ?Authenticatable diff --git a/packages/auth/src/Installer/AuthenticationInstaller.php b/packages/auth/src/Installer/AuthenticationInstaller.php index d80395421b..56d5291101 100644 --- a/packages/auth/src/Installer/AuthenticationInstaller.php +++ b/packages/auth/src/Installer/AuthenticationInstaller.php @@ -5,15 +5,14 @@ namespace Tempest\Auth\Installer; use Tempest\Console\Console; +use Tempest\Console\ConsoleCommand; use Tempest\Console\Input\ConsoleArgumentBag; use Tempest\Container\Container; use Tempest\Core\Installer; use Tempest\Core\PublishesFiles; use Tempest\Database\Migrations\MigrationManager; -use function Tempest\root_path; use function Tempest\src_path; -use function Tempest\Support\Namespace\to_fqcn; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { final class AuthenticationInstaller implements Installer @@ -36,9 +35,7 @@ public function install(): void $this->publishImports(); if ($migration && $this->shouldMigrate()) { - $this->migrationManager->executeUp( - migration: $this->container->get(to_fqcn($migration, root: root_path())), - ); + $this->migrationManager->up(); } if ($this->shouldInstallOAuth()) { diff --git a/packages/auth/src/Installer/OAuthInstaller.php b/packages/auth/src/Installer/OAuthInstaller.php index 0ee15ec402..8915b44d3d 100644 --- a/packages/auth/src/Installer/OAuthInstaller.php +++ b/packages/auth/src/Installer/OAuthInstaller.php @@ -93,8 +93,7 @@ private function publishConfig(SupportedOAuthProvider $provider): void private function publishController(SupportedOAuthProvider $provider): void { - $fileName = str($provider->value) - ->classBasename() + $fileName = str($provider->getName()) ->replace('Provider', '') ->append('Controller.php') ->toString(); @@ -104,7 +103,7 @@ private function publishController(SupportedOAuthProvider $provider): void destination: src_path("Authentication/OAuth/{$fileName}"), callback: function (string $source, string $destination) use ($provider) { $providerFqcn = $provider::class; - $name = strtolower($provider->name); + $name = strtolower($provider->getName()); $userModelFqcn = to_fqcn(src_path('Authentication/User.php'), root: root_path()); $this->update( diff --git a/packages/auth/src/Installer/oauth/twitch.config.stub.php b/packages/auth/src/Installer/oauth/twitch.config.stub.php new file mode 100644 index 0000000000..11968506ff --- /dev/null +++ b/packages/auth/src/Installer/oauth/twitch.config.stub.php @@ -0,0 +1,15 @@ + $this->clientId, + 'clientSecret' => $this->clientSecret, + 'redirectUri' => $this->redirectTo, + ]); + } + + /** + * @param TwitchHelixResourceOwner $resourceOwner + */ + public function mapUser(ObjectFactory $factory, ResourceOwnerInterface $resourceOwner): OAuthUser + { + return $factory->withData([ + 'id' => (string) $resourceOwner->getId(), + 'email' => $resourceOwner->getEmail(), + 'name' => $resourceOwner->getDisplayName(), + 'nickname' => $resourceOwner->getDisplayName(), + 'avatar' => $resourceOwner->getProfileImageUrl(), + 'provider' => $this->provider, + 'raw' => $resourceOwner->toArray(), + ])->to(OAuthUser::class); + } +} diff --git a/packages/auth/src/OAuth/SupportedOAuthProvider.php b/packages/auth/src/OAuth/SupportedOAuthProvider.php index 1346a942d7..87c7677bbb 100644 --- a/packages/auth/src/OAuth/SupportedOAuthProvider.php +++ b/packages/auth/src/OAuth/SupportedOAuthProvider.php @@ -11,6 +11,7 @@ use League\OAuth2\Client\Provider\Instagram; use League\OAuth2\Client\Provider\LinkedIn; use Stevenmaguire\OAuth2\Client\Provider\Microsoft; +use Vertisan\OAuth2\Client\Provider\TwitchHelix; use Wohali\OAuth2\Client\Provider\Discord; enum SupportedOAuthProvider: string @@ -25,7 +26,33 @@ enum SupportedOAuthProvider: string case LINKEDIN = LinkedIn::class; case MICROSOFT = Microsoft::class; case SLACK = Slack::class; + case TWITCH = TwitchHelix::class; + /** + * Returns the canonical name for the given OAuth provider. Required because some of the providers have mixed-case names. + */ + public function getName(): string + { + return match ($this) { + self::APPLE => 'Apple', + self::DISCORD => 'Discord', + self::FACEBOOK => 'Facebook', + self::GENERIC => 'Generic', + self::GITHUB => 'Github', + self::GOOGLE => 'Google', + self::INSTAGRAM => 'Instagram', + self::LINKEDIN => 'LinkedIn', + self::MICROSOFT => 'Microsoft', + self::SLACK => 'Slack', + self::TWITCH => 'Twitch', + }; + } + + /** + * Returns the Composer package name for the given OAuth provider. + * + * @return string|null The Composer package name, or null if the provider is generic. + */ public function composerPackage(): ?string { return match ($this) { @@ -39,6 +66,7 @@ public function composerPackage(): ?string self::LINKEDIN => 'league/oauth2-linkedin', self::MICROSOFT => 'stevenmaguire/oauth2-microsoft', self::SLACK => 'adam-paterson/oauth2-slack', + self::TWITCH => 'vertisan/oauth2-twitch-helix', }; } } diff --git a/packages/auth/tests/OAuthTest.php b/packages/auth/tests/OAuthTest.php index 398fbc38de..8f9271d556 100644 --- a/packages/auth/tests/OAuthTest.php +++ b/packages/auth/tests/OAuthTest.php @@ -11,6 +11,7 @@ use League\OAuth2\Client\Provider\Instagram; use League\OAuth2\Client\Provider\LinkedIn; use League\OAuth2\Client\Provider\ResourceOwnerInterface; +use PHPUnit\Framework\Attributes\Before; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Tempest\Auth\OAuth\Config\AppleOAuthConfig; @@ -23,6 +24,7 @@ use Tempest\Auth\OAuth\Config\LinkedInOAuthConfig; use Tempest\Auth\OAuth\OAuthClientInitializer; use Tempest\Auth\OAuth\OAuthUser; +use Tempest\Container\Container; use Tempest\Container\GenericContainer; use Tempest\Mapper\MapperConfig; use Tempest\Mapper\Mappers\ArrayToObjectMapper; @@ -31,13 +33,20 @@ final class OAuthTest extends TestCase { private GenericContainer $container { - get => $this->container ??= new GenericContainer()->addInitializer(OAuthClientInitializer::class); + get => $this->container ??= new GenericContainer(); } private ObjectFactory $factory { get => $this->factory ??= new ObjectFactory(new MapperConfig([ArrayToObjectMapper::class]), $this->container); } + #[Before] + public function before(): void + { + $this->container->singleton(Container::class, $this->container); + $this->container->addInitializer(OAuthClientInitializer::class); + } + #[Test] public function github_oauth_config(): void { diff --git a/packages/cache/composer.json b/packages/cache/composer.json index 60ae7d9059..3cbc5b997e 100644 --- a/packages/cache/composer.json +++ b/packages/cache/composer.json @@ -2,7 +2,7 @@ "name": "tempest/cache", "description": "The PHP framework that gets out of your way.", "require": { - "php": "^8.4", + "php": "^8.5", "psr/cache": "^3.0", "symfony/cache": "^7.3", "tempest/core": "dev-main", diff --git a/packages/cache/src/Cache.php b/packages/cache/src/Cache.php index 6508f334b4..ee9ba65cb4 100644 --- a/packages/cache/src/Cache.php +++ b/packages/cache/src/Cache.php @@ -24,10 +24,10 @@ interface Cache * Returns a lock for the specified key. The lock is not acquired until `acquire()` is called. * * @param Stringable|string $key The identifier of the lock. - * @param null|Duration|DateTimeInterface $expiration The expiration time for the lock. If not specified, the lock will not expire. + * @param null|Duration|DateTimeInterface $duration The duration for the lock, or an expiration date from which the duration will be calculated. If not specified, the lock will not expire. * @param null|Stringable|string $owner The owner of the lock, which will be used to identify the process releasing it. If not specified, a random string will be used. */ - public function lock(Stringable|string $key, null|Duration|DateTimeInterface $expiration = null, null|Stringable|string $owner = null): Lock; + public function lock(Stringable|string $key, null|Duration|DateTimeInterface $duration = null, null|Stringable|string $owner = null): Lock; /** * Sets the specified key to the specified value in the cache. Optionally, specify an expiration. diff --git a/packages/cache/src/Commands/CacheStatusCommand.php b/packages/cache/src/Commands/CacheStatusCommand.php index 42158ecb69..a859a9dc54 100644 --- a/packages/cache/src/Commands/CacheStatusCommand.php +++ b/packages/cache/src/Commands/CacheStatusCommand.php @@ -12,9 +12,9 @@ use Tempest\Console\HasConsole; use Tempest\Container\Container; use Tempest\Container\GenericContainer; -use Tempest\Core\AppConfig; use Tempest\Core\ConfigCache; use Tempest\Core\DiscoveryCache; +use Tempest\Core\Environment; use Tempest\Icon\IconCache; use Tempest\Support\Str; use Tempest\View\ViewCache; @@ -30,7 +30,7 @@ public function __construct( private Console $console, private Container $container, - private AppConfig $appConfig, + private Environment $environment, private DiscoveryCache $discoveryCache, ) {} @@ -73,7 +73,7 @@ public function __invoke(bool $internal = true): void }, ); - if ($this->appConfig->environment->isProduction() && ! $this->discoveryCache->enabled) { + if ($this->environment->requiresCaution() && ! $this->discoveryCache->enabled) { $this->console->writeln(); $this->console->error('Discovery cache is disabled in production. This is not recommended.'); } diff --git a/packages/cache/src/GenericCache.php b/packages/cache/src/GenericCache.php index 1d59a34f00..2023dd58be 100644 --- a/packages/cache/src/GenericCache.php +++ b/packages/cache/src/GenericCache.php @@ -23,17 +23,17 @@ public function __construct( public null|UnitEnum|string $tag = null, ) {} - public function lock(Stringable|string $key, null|Duration|DateTimeInterface $expiration = null, null|Stringable|string $owner = null): Lock + public function lock(Stringable|string $key, null|Duration|DateTimeInterface $duration = null, null|Stringable|string $owner = null): Lock { - if ($expiration instanceof Duration) { - $expiration = DateTime::now()->plus($expiration); + if ($duration instanceof DateTimeInterface) { + $duration = $duration->since(DateTime::now()); } return new GenericLock( key: (string) $key, owner: $owner ? (string) $owner : Random\secure_string(length: 10), cache: $this, - expiration: $expiration, + duration: $duration, ); } diff --git a/packages/cache/src/GenericLock.php b/packages/cache/src/GenericLock.php index bf4b9129ab..98b6963aec 100644 --- a/packages/cache/src/GenericLock.php +++ b/packages/cache/src/GenericLock.php @@ -14,7 +14,7 @@ public function __construct( private(set) string $key, private(set) string $owner, private readonly Cache $cache, - private(set) ?DateTimeInterface $expiration = null, + private(set) ?Duration $duration = null, ) {} public function locked(null|Stringable|string $by = null): bool @@ -32,13 +32,17 @@ public function acquire(): bool return false; } + $expiration = $this->duration !== null + ? DateTime::now()->plus($this->duration) + : null; + $this->cache->put( key: $this->key, value: $this->owner, - expiration: $this->expiration, + expiration: $expiration, ); - return true; + return $this->cache->get($this->key) === $this->owner; } public function execute(Closure $callback, null|DateTimeInterface|Duration $wait = null): mixed diff --git a/packages/cache/src/Lock.php b/packages/cache/src/Lock.php index 0c1e684b3f..908df168b4 100644 --- a/packages/cache/src/Lock.php +++ b/packages/cache/src/Lock.php @@ -17,9 +17,9 @@ interface Lock } /** - * The expiration date of the lock. If null, the lock will not expire. + * The duration of the lock. If null, the lock will not expire. */ - public ?DateTimeInterface $expiration { + public ?Duration $duration { get; } diff --git a/packages/cache/src/Testing/RestrictedCache.php b/packages/cache/src/Testing/RestrictedCache.php index eb7e5d4655..a5b5526975 100644 --- a/packages/cache/src/Testing/RestrictedCache.php +++ b/packages/cache/src/Testing/RestrictedCache.php @@ -25,7 +25,7 @@ private function resolveTag(): ?string return $this->tag instanceof UnitEnum ? $this->tag->name : $this->tag; } - public function lock(Stringable|string $key, null|Duration|DateTimeInterface $expiration = null, null|Stringable|string $owner = null): Lock + public function lock(Stringable|string $key, null|Duration|DateTimeInterface $duration = null, null|Stringable|string $owner = null): Lock { throw new CacheUsageWasForbidden($this->resolveTag()); } diff --git a/packages/cache/src/Testing/TestingCache.php b/packages/cache/src/Testing/TestingCache.php index 01615f4775..516e2abe89 100644 --- a/packages/cache/src/Testing/TestingCache.php +++ b/packages/cache/src/Testing/TestingCache.php @@ -11,6 +11,7 @@ use Tempest\Cache\Cache; use Tempest\Cache\GenericCache; use Tempest\Cache\GenericLock; +use Tempest\DateTime\DateTime; use Tempest\DateTime\DateTimeInterface; use Tempest\DateTime\Duration; use Tempest\Support\Random; @@ -33,13 +34,17 @@ public function __construct( $this->cache = new GenericCache($this->adapter); } - public function lock(Stringable|string $key, null|Duration|DateTimeInterface $expiration = null, null|Stringable|string $owner = null): TestingLock + public function lock(Stringable|string $key, null|Duration|DateTimeInterface $duration = null, null|Stringable|string $owner = null): TestingLock { + if ($duration instanceof DateTimeInterface) { + $duration = $duration->since(DateTime::now()); + } + return new TestingLock(new GenericLock( key: (string) $key, owner: $owner ? (string) $owner : Random\secure_string(length: 10), cache: $this->cache, - expiration: $expiration, + duration: $duration, )); } @@ -186,9 +191,9 @@ public function assertNotEmpty(): self /** * Asserts that the specified lock is being held. */ - public function assertLocked(string|Stringable $key, null|Stringable|string $by = null, null|DateTimeInterface|Duration $until = null): self + public function assertLocked(string|Stringable $key, null|Stringable|string $by = null, null|DateTimeInterface|Duration $for = null): self { - $this->lock($key)->assertLocked($by, $until); + $this->lock($key)->assertLocked($by, $for); return $this; } diff --git a/packages/cache/src/Testing/TestingLock.php b/packages/cache/src/Testing/TestingLock.php index e1c0a873e1..1cccdb33a9 100644 --- a/packages/cache/src/Testing/TestingLock.php +++ b/packages/cache/src/Testing/TestingLock.php @@ -17,8 +17,8 @@ final class TestingLock implements Lock get => $this->lock->key; } - public ?DateTimeInterface $expiration { - get => $this->lock->expiration; + public ?Duration $duration { + get => $this->lock->duration; } public string $owner { @@ -52,7 +52,7 @@ public function release(bool $force = false): bool /** * Asserts that the specified lock is being held. */ - public function assertLocked(null|Stringable|string $by = null, null|DateTimeInterface|Duration $until = null): self + public function assertLocked(null|Stringable|string $by = null, null|DateTimeInterface|Duration $for = null): self { Assert::assertTrue( condition: $this->locked($by), @@ -61,19 +61,19 @@ public function assertLocked(null|Stringable|string $by = null, null|DateTimeInt : "Lock `{$this->key}` is not being held.", ); - if ($until) { - if ($until instanceof Duration) { - $until = DateTime::now()->plus($until); + if ($for) { + if ($for instanceof DateTimeInterface) { + $for = $for->since(DateTime::now()); } Assert::assertNotNull( - actual: $this->expiration, - message: "Expected lock `{$this->key}` to have an expiration, but it has none.", + actual: $this->duration, + message: "Expected lock `{$this->key}` to have a duration, but it has none.", ); Assert::assertTrue( - condition: $this->expiration->afterOrAtTheSameTime($until), - message: "Expected lock `{$this->key}` to expire at or after `{$until}`, but it expires at `{$this->expiration}`.", + condition: $this->duration->getTotalSeconds() >= $for->getTotalSeconds(), + message: "Expected lock `{$this->key}` to have a duration of at least `{$for->getTotalSeconds()}` seconds, but it has `{$this->duration->getTotalSeconds()}` seconds.", ); } diff --git a/packages/clock/composer.json b/packages/clock/composer.json index f2272e0734..d3411aa199 100644 --- a/packages/clock/composer.json +++ b/packages/clock/composer.json @@ -2,7 +2,7 @@ "name": "tempest/clock", "description": "A clock component that handle few simple clock operations.", "require": { - "php": "^8.4", + "php": "^8.5", "psr/clock": "^1.0.0", "tempest/datetime": "dev-main" }, diff --git a/packages/clock/src/functions.php b/packages/clock/src/functions.php index ede8871ba6..2f2a6fad72 100644 --- a/packages/clock/src/functions.php +++ b/packages/clock/src/functions.php @@ -4,7 +4,7 @@ use Tempest\DateTime\DateTimeInterface; -use function Tempest\get; +use function Tempest\Container\get; /** * Get the current date and time as a {@see \Tempest\DateTime\DateTimeInterface} object. diff --git a/packages/command-bus/composer.json b/packages/command-bus/composer.json index 5834601a09..f34ec675d2 100644 --- a/packages/command-bus/composer.json +++ b/packages/command-bus/composer.json @@ -2,7 +2,7 @@ "name": "tempest/command-bus", "description": "A command bus component designed to dispatch commands to their respective handlers.", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/core": "dev-main", "tempest/console": "dev-main", "tempest/container": "dev-main" diff --git a/packages/command-bus/src/AsyncCommandRepositories/FileCommandRepository.php b/packages/command-bus/src/AsyncCommandRepositories/FileCommandRepository.php index 84ebdfb1e1..b7759e9822 100644 --- a/packages/command-bus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/packages/command-bus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -53,6 +53,10 @@ public function getPendingCommands(): array { return arr(glob(__DIR__ . '/../stored-commands/*.pending.txt')) ->mapWithKeys(function (string $path) { + if (! Filesystem\is_file($path)) { + return; + } + $uuid = str_replace('.pending.txt', '', pathinfo($path, PATHINFO_BASENAME)); $payload = Filesystem\read_file($path); diff --git a/packages/command-bus/src/functions.php b/packages/command-bus/src/functions.php index 72aeb7aa0c..e9dfbceda2 100644 --- a/packages/command-bus/src/functions.php +++ b/packages/command-bus/src/functions.php @@ -2,16 +2,17 @@ declare(strict_types=1); -namespace Tempest { - use Tempest\CommandBus\CommandBus; +namespace Tempest\CommandBus; - /** - * Dispatches the given `$command` to the {@see CommandBus}, triggering all associated command handlers. - */ - function command(object $command): void - { - $commandBus = get(CommandBus::class); +use Tempest\CommandBus\CommandBus; +use Tempest\Container; - $commandBus->dispatch($command); - } +/** + * Dispatches the given `$command` to the {@see CommandBus}, triggering all associated command handlers. + */ +function command(object $command): void +{ + $commandBus = Container\get(CommandBus::class); + + $commandBus->dispatch($command); } diff --git a/packages/console/composer.json b/packages/console/composer.json index ce53de888c..d76d687d71 100644 --- a/packages/console/composer.json +++ b/packages/console/composer.json @@ -4,11 +4,9 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", - "tempest/cache": "dev-main", + "php": "^8.5", "tempest/core": "dev-main", "tempest/container": "dev-main", - "tempest/debug": "dev-main", "tempest/generation": "dev-main", "tempest/highlight": "^2.11.4", "tempest/log": "dev-main", diff --git a/packages/console/src/Actions/ResolveShell.php b/packages/console/src/Actions/ResolveShell.php new file mode 100644 index 0000000000..89d45e4f3d --- /dev/null +++ b/packages/console/src/Actions/ResolveShell.php @@ -0,0 +1,31 @@ +console->supportsPrompting()) { + /** @var Shell */ + return $this->console->ask( + question: $question, + options: Shell::class, + default: $detected, + ); + } + + return $detected; + } +} diff --git a/packages/console/src/Commands/CompletionInstallCommand.php b/packages/console/src/Commands/CompletionInstallCommand.php new file mode 100644 index 0000000000..73e607c0a5 --- /dev/null +++ b/packages/console/src/Commands/CompletionInstallCommand.php @@ -0,0 +1,98 @@ +resolveShell)('Which shell do you want to install completions for?'); + + if ($shell === null) { + $this->console->error('Could not detect shell. Please specify one using the --shell option. Possible values are: zsh, bash.'); + + return ExitCode::ERROR; + } + + $sourcePath = $this->getSourcePath($shell); + $targetDir = $shell->getCompletionsDirectory(); + $targetPath = $shell->getInstalledCompletionPath(); + + if (! Filesystem\is_file($sourcePath)) { + $this->console->error("Completion script not found: {$sourcePath}"); + + return ExitCode::ERROR; + } + + if (! $force) { + $this->console->info("Installing {$shell->value} completions"); + $this->console->keyValue('Source', $sourcePath); + $this->console->keyValue('Target', $targetPath); + $this->console->writeln(); + + if (! $this->console->confirm('Proceed with installation?', default: true)) { + $this->console->warning('Installation cancelled.'); + + return ExitCode::CANCELLED; + } + } + + Filesystem\ensure_directory_exists($targetDir); + + if (Filesystem\is_file($targetPath)) { + if (! $force && ! $this->console->confirm('Completion file already exists. Overwrite?', default: false)) { + $this->console->warning('Installation cancelled.'); + + return ExitCode::CANCELLED; + } + } + + Filesystem\copy_file($sourcePath, $targetPath, overwrite: true); + $this->console->success("Installed completion script to: {$targetPath}"); + + $this->console->writeln(); + $this->console->info('Next steps:'); + $this->console->instructions($shell->getPostInstallInstructions()); + + return ExitCode::SUCCESS; + } + + private function getSourcePath(Shell $shell): string + { + return Path::canonicalize( + path(__DIR__, '..', $shell->getSourceFilename())->toString(), + ); + } +} diff --git a/packages/console/src/Commands/CompletionShowCommand.php b/packages/console/src/Commands/CompletionShowCommand.php new file mode 100644 index 0000000000..1afeb32213 --- /dev/null +++ b/packages/console/src/Commands/CompletionShowCommand.php @@ -0,0 +1,63 @@ +resolveShell)('Which shell completion script do you want to see?'); + + if ($shell === null) { + $this->console->error('Could not detect shell. Please specify one using the --shell option. Possible values are: zsh, bash.'); + + return ExitCode::ERROR; + } + + $sourcePath = $this->getSourcePath($shell); + + if (! Filesystem\is_file($sourcePath)) { + $this->console->error("Completion script not found: {$sourcePath}"); + + return ExitCode::ERROR; + } + + $this->console->writeRaw(Filesystem\read_file($sourcePath)); + + return ExitCode::SUCCESS; + } + + private function getSourcePath(Shell $shell): string + { + return Path::canonicalize( + path(__DIR__, '..', $shell->getSourceFilename())->toString(), + ); + } +} diff --git a/packages/console/src/Commands/CompletionUninstallCommand.php b/packages/console/src/Commands/CompletionUninstallCommand.php new file mode 100644 index 0000000000..7e0a0e61cd --- /dev/null +++ b/packages/console/src/Commands/CompletionUninstallCommand.php @@ -0,0 +1,106 @@ +resolveShell)('Which shell completions do you want to uninstall?'); + + if ($shell === null) { + $this->console->error('Could not detect shell. Please specify one using the --shell option. Possible values are: zsh, bash.'); + + return ExitCode::ERROR; + } + + $targetPath = $shell->getInstalledCompletionPath(); + + if (! Filesystem\is_file($targetPath)) { + $this->console->warning("Completion file not found: {$targetPath}"); + $this->console->info('Nothing to uninstall.'); + + return ExitCode::SUCCESS; + } + + if (! $force) { + $this->console->info("Uninstalling {$shell->value} completions"); + $this->console->keyValue('File', $targetPath); + $this->console->writeln(); + + if (! $this->console->confirm('Proceed with uninstallation?', default: true)) { + $this->console->warning('Uninstallation cancelled.'); + + return ExitCode::CANCELLED; + } + } + + Filesystem\delete_file($targetPath); + $this->console->success("Removed completion script: {$targetPath}"); + + if ($shell === Shell::ZSH) { + $this->cleanupZshCache(); + } + + $this->console->writeln(); + $this->console->info('Remember to remove any related lines from your shell configuration:'); + $this->console->keyValue('Config file', $shell->getRcFile()); + + return ExitCode::SUCCESS; + } + + private function cleanupZshCache(): void + { + $home = $_SERVER['HOME'] ?? $_ENV['HOME'] ?? null; + + if ($home === null) { + return; + } + + $cacheFiles = glob("{$home}/.zcompdump*") ?: []; + + foreach ($cacheFiles as $file) { + Filesystem\delete_file($file); + } + + if ($cacheFiles !== []) { + $this->console->info('Cleared zsh completion cache (~/.zcompdump*)'); + } + + $this->console->writeln(); + $this->console->info('Run this to clear completions in your current shell:'); + $this->console->writeln(); + $this->console->writeln(" unset '_patcomps[php]' '_patcomps[tempest]' '_patcomps[*/tempest]' 2>/dev/null"); + $this->console->writeln(); + $this->console->info('Or restart your shell: exec zsh'); + } +} diff --git a/packages/console/src/Commands/MakeCommandCommand.php b/packages/console/src/Commands/MakeCommandCommand.php index 24e0e82ed9..ff03458bfa 100644 --- a/packages/console/src/Commands/MakeCommandCommand.php +++ b/packages/console/src/Commands/MakeCommandCommand.php @@ -9,8 +9,8 @@ use Tempest\Console\Stubs\CommandStub; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; use function Tempest\Support\str; diff --git a/packages/console/src/Commands/MakeConfigCommand.php b/packages/console/src/Commands/MakeConfigCommand.php index be133f8a4a..eee7a984c0 100644 --- a/packages/console/src/Commands/MakeConfigCommand.php +++ b/packages/console/src/Commands/MakeConfigCommand.php @@ -9,9 +9,9 @@ use Tempest\Console\ConsoleCommand; use Tempest\Console\Enums\ConfigType; use Tempest\Core\PublishesFiles; -use Tempest\Generation\DataObjects\StubFile; -use Tempest\Generation\Exceptions\FileGenerationFailedException; -use Tempest\Generation\Exceptions\FileGenerationWasAborted; +use Tempest\Generation\Php\DataObjects\StubFile; +use Tempest\Generation\Php\Exceptions\FileGenerationFailedException; +use Tempest\Generation\Php\Exceptions\FileGenerationWasAborted; use function Tempest\Support\str; diff --git a/packages/console/src/Commands/MakeGeneratorCommandCommand.php b/packages/console/src/Commands/MakeGeneratorCommandCommand.php index 69210edc05..b3ef45c23b 100644 --- a/packages/console/src/Commands/MakeGeneratorCommandCommand.php +++ b/packages/console/src/Commands/MakeGeneratorCommandCommand.php @@ -9,8 +9,8 @@ use Tempest\Console\Stubs\GeneratorCommandStub; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; use function Tempest\Support\str; diff --git a/packages/console/src/Commands/MakeMiddlewareCommand.php b/packages/console/src/Commands/MakeMiddlewareCommand.php index 3bed265ec8..9a041f721a 100644 --- a/packages/console/src/Commands/MakeMiddlewareCommand.php +++ b/packages/console/src/Commands/MakeMiddlewareCommand.php @@ -14,8 +14,8 @@ use Tempest\Console\Stubs\HttpMiddlewareStub; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; final class MakeMiddlewareCommand { diff --git a/packages/console/src/Commands/TailCommand.php b/packages/console/src/Commands/TailCommand.php deleted file mode 100644 index be8edd600c..0000000000 --- a/packages/console/src/Commands/TailCommand.php +++ /dev/null @@ -1,61 +0,0 @@ - $loggers */ - $loggers = array_filter([ - $shouldFilter === false || $project ? $this->tailProjectLogCommand : null, - $shouldFilter === false || $server ? $this->tailServerLogCommand : null, - $shouldFilter === false || $debug ? $this->tailDebugLogCommand : null, - ]); - - /** @var Fiber[] $fibers */ - $fibers = []; - - foreach ($loggers as $key => $logger) { - $fiber = new Fiber(fn () => $logger()); - $fibers[$key] = $fiber; - $fiber->start(); - } - - while ($fibers !== []) { - foreach ($fibers as $key => $fiber) { - if ($fiber->isSuspended()) { - $fiber->resume(); - } - - if ($fiber->isTerminated()) { - unset($fibers[$key]); - } - } - } - } -} diff --git a/packages/console/src/Commands/TailDebugLogCommand.php b/packages/console/src/Commands/TailDebugLogCommand.php deleted file mode 100644 index 4dd60e9e50..0000000000 --- a/packages/console/src/Commands/TailDebugLogCommand.php +++ /dev/null @@ -1,55 +0,0 @@ -logConfig->debugLogPath; - - if (! $debugLogPath) { - $this->console->error('No debug log configured in LogConfig.'); - - return; - } - - $dir = pathinfo($debugLogPath, PATHINFO_DIRNAME); - - if (! is_dir($dir)) { - mkdir($dir); - } - - if (! file_exists($debugLogPath)) { - touch($debugLogPath); - } - - $this->console->header('Tailing debug logs', "Reading …"); - - new TailReader()->tail( - path: $debugLogPath, - format: fn (string $text) => $this->highlighter->parse( - $text, - new VarExportLanguage(), - ), - ); - } -} diff --git a/packages/console/src/Commands/TailServerLogCommand.php b/packages/console/src/Commands/TailServerLogCommand.php deleted file mode 100644 index 42d5cebe0c..0000000000 --- a/packages/console/src/Commands/TailServerLogCommand.php +++ /dev/null @@ -1,51 +0,0 @@ -logConfig->serverLogPath; - - if (! $serverLogPath) { - $this->console->error('No server log configured in LogConfig.'); - - return; - } - - if (! file_exists($serverLogPath)) { - $this->console->error("No valid server log at "); - - return; - } - - $this->console->header('Tailing server logs', "Reading …"); - - new TailReader()->tail( - path: $serverLogPath, - format: fn (string $text) => $this->highlighter->parse( - $text, - new LogLanguage(), - ), - ); - } -} diff --git a/packages/console/src/ConsoleApplication.php b/packages/console/src/ConsoleApplication.php index 9feada2ccb..7036f9c6b4 100644 --- a/packages/console/src/ConsoleApplication.php +++ b/packages/console/src/ConsoleApplication.php @@ -10,6 +10,7 @@ use Tempest\Core\Application; use Tempest\Core\Kernel; use Tempest\Core\Tempest; +use Tempest\Support\Str; final readonly class ConsoleApplication implements Application { @@ -18,24 +19,36 @@ public function __construct( private ConsoleArgumentBag $argumentBag, ) {} - /** @param \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */ + /** + * Boots the console application. + * + * @param string|null $root The root directory of the application. By default, the current working directory. + * @param \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations The locations to use for class discovery. + * @param string|null $internalStorage The *absolute* internal storage directory for Tempest. + * @param string $name The name of the console application. + * @param bool $loadBuiltInCommands Whether to load built-in Tempest console commands. + */ public static function boot( - string $name = 'Tempest', ?string $root = null, array $discoveryLocations = [], + ?string $internalStorage = null, + ?string $name = null, + ?bool $loadBuiltInCommands = true, ): self { - $container = Tempest::boot($root, $discoveryLocations); + if (! $internalStorage && $name) { + $internalStorage = sprintf('.%s', Str\to_kebab_case($name)); + } - $application = $container->get(ConsoleApplication::class); + $container = Tempest::boot($root, $discoveryLocations, $internalStorage); - // Application-specific config $consoleConfig = $container->get(ConsoleConfig::class); - $consoleConfig->name = $name; + $consoleConfig->name ??= $name; + $consoleConfig->loadBuiltInCommands = $loadBuiltInCommands ?? $consoleConfig->loadBuiltInCommands; - return $application; + return $container->get(ConsoleApplication::class); } - public function run(): void + public function run(): never { $exitCode = $this->container->get(ExecuteConsoleCommand::class)($this->argumentBag->getCommandName()); diff --git a/packages/console/src/ConsoleConfig.php b/packages/console/src/ConsoleConfig.php index ad94cfd3e9..82e58a2755 100644 --- a/packages/console/src/ConsoleConfig.php +++ b/packages/console/src/ConsoleConfig.php @@ -9,15 +9,36 @@ final class ConsoleConfig { - public function __construct( - public string $name = 'Tempest', - - /** @var ConsoleCommand[] $commands */ - public array $commands = [], - public ?string $logPath = null, + /** + * List of registered console commands. + * + * @var ConsoleCommand[] $commands + */ + public array $commands = []; + + /** + * The path to the log file where console output will be recorded. + */ + public ?string $logPath = null; + + /** + * Middleware stack for console commands. + * + * @see https://tempestphp.com/current/essentials/console-commands#middleware + * + * @var Middleware<\Tempest\Console\ConsoleMiddleware> + */ + public Middleware $middleware { + get => $this->middleware ??= new Middleware(); + } - /** @var Middleware<\Tempest\Console\ConsoleMiddleware> */ - public Middleware $middleware = new Middleware(), + /** + * @param ?string $name The name of the application. Will appear in console command menus. + * @param bool $loadBuiltInCommands Whether to load built-in Tempest commands. + */ + public function __construct( + public ?string $name = null, + public bool $loadBuiltInCommands = true, ) {} public function addCommand(MethodReflector $handler, ConsoleCommand $consoleCommand): self diff --git a/packages/console/src/Discovery/ConsoleCommandDiscovery.php b/packages/console/src/Discovery/ConsoleCommandDiscovery.php index 924bcd2dd9..43f6bcb9d6 100644 --- a/packages/console/src/Discovery/ConsoleCommandDiscovery.php +++ b/packages/console/src/Discovery/ConsoleCommandDiscovery.php @@ -28,6 +28,10 @@ public function discover(DiscoveryLocation $location, ClassReflector $class): vo continue; } + if (! $this->consoleConfig->loadBuiltInCommands && $location->isTempest()) { + continue; + } + $this->discoveryItems->add($location, [$method, $consoleCommand]); } } diff --git a/packages/console/src/Enums/Shell.php b/packages/console/src/Enums/Shell.php new file mode 100644 index 0000000000..ddf4ae28df --- /dev/null +++ b/packages/console/src/Enums/Shell.php @@ -0,0 +1,94 @@ + self::ZSH, + str_contains($shell, 'bash') => self::BASH, + default => null, + }; + } + + public function getCompletionsDirectory(): string + { + $home = $_SERVER['HOME'] ?? getenv('HOME') ?: ''; + + return match ($this) { + self::ZSH => $home . '/.zsh/completions', + self::BASH => $home . '/.bash_completion.d', + }; + } + + public function getCompletionFilename(): string + { + return match ($this) { + self::ZSH => '_tempest', + self::BASH => 'tempest.bash', + }; + } + + public function getInstalledCompletionPath(): string + { + return $this->getCompletionsDirectory() . '/' . $this->getCompletionFilename(); + } + + public function getSourceFilename(): string + { + return match ($this) { + self::ZSH => 'completion.zsh', + self::BASH => 'completion.bash', + }; + } + + public function getRcFile(): string + { + $home = $_SERVER['HOME'] ?? getenv('HOME') ?: ''; + + return match ($this) { + self::ZSH => $home . '/.zshrc', + self::BASH => $home . '/.bashrc', + }; + } + + /** + * @return string[] + */ + public function getPostInstallInstructions(): array + { + return match ($this) { + self::ZSH => [ + 'Add the completions directory to your fpath in ~/.zshrc:', + '', + ' fpath=(~/.zsh/completions $fpath)', + '', + 'Then reload completions:', + '', + ' autoload -Uz compinit && compinit', + '', + 'Or restart your terminal.', + ], + self::BASH => [ + 'Source the completion file in your ~/.bashrc:', + '', + ' source ~/.bash_completion.d/tempest.bash', + '', + 'Or restart your terminal.', + ], + }; + } +} diff --git a/packages/console/src/Exceptions/ConsoleExceptionHandler.php b/packages/console/src/Exceptions/ConsoleExceptionHandler.php index 052b311ef6..3c9d20f9fe 100644 --- a/packages/console/src/Exceptions/ConsoleExceptionHandler.php +++ b/packages/console/src/Exceptions/ConsoleExceptionHandler.php @@ -9,11 +9,9 @@ use Tempest\Console\GlobalFlags; use Tempest\Console\HasExitCode; use Tempest\Console\Input\ConsoleArgumentBag; -use Tempest\Container\Container; use Tempest\Container\Tag; -use Tempest\Core\AppConfig; use Tempest\Core\ExceptionHandler; -use Tempest\Core\ExceptionReporter; +use Tempest\Core\Exceptions\ExceptionProcessor; use Tempest\Core\Kernel; use Tempest\Highlight\Escape; use Tempest\Highlight\Highlighter; @@ -25,20 +23,18 @@ final readonly class ConsoleExceptionHandler implements ExceptionHandler { public function __construct( - private AppConfig $appConfig, - private Container $container, private Kernel $kernel, #[Tag('console')] private Highlighter $highlighter, private Console $console, private ConsoleArgumentBag $argumentBag, - private ExceptionReporter $exceptionReporter, + private ExceptionProcessor $exceptionProcessor, ) {} public function handle(Throwable $throwable): void { try { - $this->exceptionReporter->report($throwable); + $this->exceptionProcessor->process($throwable); $this->console ->writeln() @@ -59,12 +55,15 @@ public function handle(Throwable $throwable): void $this->console->writeln(); } else { - $this->console - ->writeln('#0 ' . $this->formatTrace($throwable->getTrace()[0])) - ->writeln('#1 ' . $this->formatTrace($throwable->getTrace()[1])) - ->writeln() - ->writeln(' Run with -v to show more.') - ->writeln(); + $this->console->writeln('#0 ' . $this->formatTrace($throwable->getTrace()[0])); + + if (count($throwable->getTrace()) > 1) { + $this->console->writeln('#1 ' . $this->formatTrace($throwable->getTrace()[1])); + } + + $this->console->writeln(); + $this->console->writeln(' Run with -v to show more.'); + $this->console->writeln(); } } finally { $exitCode = $throwable instanceof HasExitCode diff --git a/packages/console/src/Initializers/LogOutputBufferInitializer.php b/packages/console/src/Initializers/LogOutputBufferInitializer.php index 840ed0408c..126e365cb6 100644 --- a/packages/console/src/Initializers/LogOutputBufferInitializer.php +++ b/packages/console/src/Initializers/LogOutputBufferInitializer.php @@ -10,8 +10,7 @@ use Tempest\Container\Initializer; use Tempest\Container\Singleton; use Tempest\Core\Kernel; - -use function Tempest\Support\path; +use Tempest\Support\Path; final readonly class LogOutputBufferInitializer implements Initializer { @@ -21,7 +20,7 @@ public function initialize(Container $container): LogOutputBuffer $consoleConfig = $container->get(ConsoleConfig::class); $kernel = $container->get(Kernel::class); - $path = $consoleConfig->logPath ?? path($kernel->root, 'console.log')->toString(); + $path = $consoleConfig->logPath ?? Path\normalize($kernel->root, 'console.log'); return new LogOutputBuffer($path); } diff --git a/packages/console/src/Input/MemoryInputBuffer.php b/packages/console/src/Input/MemoryInputBuffer.php index 46a4e2497e..63676ce4e0 100644 --- a/packages/console/src/Input/MemoryInputBuffer.php +++ b/packages/console/src/Input/MemoryInputBuffer.php @@ -6,6 +6,7 @@ use Exception; use Fiber; +use FiberError; use Tempest\Console\InputBuffer; use Tempest\Console\Key; @@ -25,7 +26,22 @@ public function add(int|string|Key ...$input): void : (string) $line; } - $this->fiber?->resume(); + try { + $this->fiber?->resume(); + } catch (FiberError) { + throw new \RuntimeException(sprintf( + 'Tried to send [%s] to the console, but no input was expected.', + implode( + separator: ', ', + array: array_map( + callback: fn (int|string|Key $i) => is_string($i) + ? rtrim($i) + : $i->value, + array: $input, + ), + ), + )); + } } public function read(int $bytes): string diff --git a/packages/console/src/Middleware/CautionMiddleware.php b/packages/console/src/Middleware/CautionMiddleware.php index e6b6d5d833..3842ea9b5a 100644 --- a/packages/console/src/Middleware/CautionMiddleware.php +++ b/packages/console/src/Middleware/CautionMiddleware.php @@ -9,7 +9,7 @@ use Tempest\Console\ConsoleMiddlewareCallable; use Tempest\Console\ExitCode; use Tempest\Console\Initializers\Invocation; -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; use Tempest\Discovery\SkipDiscovery; #[SkipDiscovery] @@ -17,14 +17,12 @@ { public function __construct( private Console $console, - private AppConfig $appConfig, + private Environment $environment, ) {} public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next): ExitCode|int { - $environment = $this->appConfig->environment; - - if ($environment->isProduction() || $environment->isStaging()) { + if ($this->environment->requiresCaution()) { if ($this->console->isForced) { return $next($invocation); } diff --git a/packages/console/src/Middleware/OverviewMiddleware.php b/packages/console/src/Middleware/OverviewMiddleware.php index 0611b7800f..f68ce82273 100644 --- a/packages/console/src/Middleware/OverviewMiddleware.php +++ b/packages/console/src/Middleware/OverviewMiddleware.php @@ -12,6 +12,7 @@ use Tempest\Console\ConsoleMiddlewareCallable; use Tempest\Console\ExitCode; use Tempest\Console\Initializers\Invocation; +use Tempest\Core\AppConfig; use Tempest\Core\DiscoveryCache; use Tempest\Core\Priority; @@ -23,6 +24,7 @@ { public function __construct( private Console $console, + private AppConfig $appConfig, private ConsoleConfig $consoleConfig, private DiscoveryCache $discoveryCache, ) {} @@ -41,7 +43,7 @@ public function __invoke(Invocation $invocation, ConsoleMiddlewareCallable $next private function renderOverview(bool $showHidden = false): void { $this->console->header( - header: $this->consoleConfig->name, + header: $this->consoleConfig->name ?? $this->appConfig->name ?? 'Tempest', subheader: 'This is an overview of available commands.' . PHP_EOL . 'Type --help to get more help about a specific command.', ); diff --git a/packages/console/src/Scheduler/GenericScheduler.php b/packages/console/src/Scheduler/GenericScheduler.php index 1cfc370caf..b98c1bec84 100644 --- a/packages/console/src/Scheduler/GenericScheduler.php +++ b/packages/console/src/Scheduler/GenericScheduler.php @@ -10,7 +10,7 @@ use Tempest\Process\ProcessExecutor; use Tempest\Support\Filesystem; -use function Tempest\event; +use function Tempest\EventBus\event; use function Tempest\internal_storage_path; final readonly class GenericScheduler implements Scheduler diff --git a/packages/console/src/Stubs/GeneratorCommandStub.php b/packages/console/src/Stubs/GeneratorCommandStub.php index 4e28a52f78..246fff407c 100644 --- a/packages/console/src/Stubs/GeneratorCommandStub.php +++ b/packages/console/src/Stubs/GeneratorCommandStub.php @@ -8,8 +8,8 @@ use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; #[SkipDiscovery] final class GeneratorCommandStub diff --git a/packages/console/src/Testing/ConsoleTester.php b/packages/console/src/Testing/ConsoleTester.php index eb50db0408..073436ed24 100644 --- a/packages/console/src/Testing/ConsoleTester.php +++ b/packages/console/src/Testing/ConsoleTester.php @@ -20,6 +20,7 @@ use Tempest\Console\OutputBuffer; use Tempest\Container\Container; use Tempest\Highlight\Highlighter; +use Tempest\Validation\Validator; final class ConsoleTester { @@ -174,7 +175,7 @@ public function getBuffer(?callable $callback = null): array public function useInteractiveTerminal(): self { - $this->componentRenderer = new InteractiveComponentRenderer(); + $this->componentRenderer = new InteractiveComponentRenderer($this->container->get(Validator::class)); return $this; } diff --git a/packages/console/src/complete.zsh b/packages/console/src/complete.zsh deleted file mode 100644 index 92ff620f5e..0000000000 --- a/packages/console/src/complete.zsh +++ /dev/null @@ -1,84 +0,0 @@ -# Forked from https://github.com/symfony/console/tree/7.0/Resources - -#compdef tempest - -# This file is part of the Symfony package. -# -# (c) Fabien Potencier -# -# For the full copyright and license information, please view -# https://symfony.com/doc/current/contributing/code/license.html - -# -# zsh completions for tempest -# -# References: -# - https://github.com/spf13/cobra/blob/master/zsh_completions.go -# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash -# -_sf_tempest() { - local lastParam flagPrefix requestComp out comp - local -a completions - - # The user could have moved the cursor backwards on the command-line. - # We need to trigger completion from the $CURRENT location, so we need - # to truncate the command-line ($words) up to the $CURRENT location. - # (We cannot use $CURSOR as its value does not work when a command is an alias.) - words=("${=words[1,CURRENT]}") lastParam=${words[-1]} - - # For zsh, when completing a flag with an = (e.g., tempest -n=) - # completions must be prefixed with the flag - setopt local_options BASH_REMATCH - if [[ "${lastParam}" =~ '-.*=' ]]; then - # We are dealing with a flag with an = - flagPrefix="-P ${BASH_REMATCH}" - fi - - # Prepare the command to obtain completions - requestComp="${words[0]} ${words[1]} _complete --no-interaction --shell=zsh --current=$((CURRENT-1))" i="" - for w in ${words[@]}; do - w=$(printf -- '%b' "$w") - # remove quotes from typed values - quote="${w:0:1}" - if [ "$quote" = \' ]; then - w="${w%\'}" - w="${w#\'}" - elif [ "$quote" = \" ]; then - w="${w%\"}" - w="${w#\"}" - fi - # empty values are ignored - if [ ! -z "$w" ]; then - i="${i}--input=\"${w}\" " - fi - done - - # Ensure at least 1 input - if [ "${i}" = "" ]; then - requestComp="${requestComp} --input=\" \"" - else - requestComp="${requestComp} ${i}" - fi - - # Use eval to handle any environment variables and such - out=$(eval ${requestComp} 2>/dev/null) - - while IFS='\n' read -r comp; do - if [ -n "$comp" ]; then - # If requested, completions are returned with a description. - # The description is preceded by a TAB character. - # For zsh's _describe, we need to use a : instead of a TAB. - # We first need to escape any : as part of the completion itself. - comp=${comp//:/\\:} - local tab=$(printf '\t') - comp=${comp//$tab/:} - completions+=${comp} - fi - done < <(printf "%s\n" "${out[@]}") - - # Let inbuilt _describe handle completions - eval _describe "completions" completions $flagPrefix - return $? -} - -compdef _sf_tempest tempest diff --git a/packages/console/src/completion.bash b/packages/console/src/completion.bash new file mode 100644 index 0000000000..b700571e41 --- /dev/null +++ b/packages/console/src/completion.bash @@ -0,0 +1,45 @@ +# Tempest Framework Bash Completion +# Supports: ./tempest, tempest, php tempest, php vendor/bin/tempest, etc. + +_tempest() { + local cur tempest_cmd shift_count input_args output IFS + + # Initialize current word (use bash-completion if available) + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion || return + else + cur="${COMP_WORDS[COMP_CWORD]}" + fi + + # Detect invocation pattern and build command + if [[ "${COMP_WORDS[0]}" == "php" ]]; then + tempest_cmd="${COMP_WORDS[0]} ${COMP_WORDS[1]}" + shift_count=2 + else + tempest_cmd="${COMP_WORDS[0]}" + shift_count=1 + fi + + # Build _complete arguments, skipping "php" prefix if present + input_args="--current=$((COMP_CWORD - shift_count + 1))" + for ((i = shift_count - 1; i < ${#COMP_WORDS[@]}; i++)); do + input_args+=" --input=\"${COMP_WORDS[i]}\"" + done + + # Execute completion command + output=$(eval "$tempest_cmd _complete $input_args" 2>/dev/null) || return 0 + [[ -z "$output" ]] && return 0 + + # Parse and filter completions + IFS=$'\n' + COMPREPLY=($(compgen -W "$output" -- "$cur")) + + # Suppress trailing space for flags expecting values (bash 4.0+) + if [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == *= ]] && type compopt &>/dev/null; then + compopt -o nospace + fi +} + +complete -F _tempest ./tempest +complete -F _tempest tempest +complete -F _tempest php diff --git a/packages/console/src/completion.zsh b/packages/console/src/completion.zsh new file mode 100644 index 0000000000..1017276ef0 --- /dev/null +++ b/packages/console/src/completion.zsh @@ -0,0 +1,54 @@ +#compdef -p '*/tempest' -p 'tempest' php + +# Tempest Framework Zsh Completion + +_tempest() { + local current="${words[CURRENT]}" tempest_cmd shift_count output + local -a args with_suffix without_suffix + + # Detect invocation: "php tempest ..." vs "./tempest ..." + if [[ "${words[1]}" == "php" ]]; then + tempest_cmd="${words[1]} ${words[2]}" + shift_count=2 + else + tempest_cmd="${words[1]}" + shift_count=1 + fi + + # Build completion request arguments + # Skip "php" from inputs but keep the tempest binary and args + local skip=$((shift_count - 1)) + args=("--current=$((CURRENT - shift_count))") + for word in "${words[@]:$skip}"; do + args+=("--input=$word") + done + + # Fetch completions from tempest + output=$(eval "$tempest_cmd _complete ${args[*]}" 2>/dev/null) || return 0 + [[ -z "$output" ]] && return 0 + + # Parse completions, separating by suffix behavior + for line in "${(@f)output}"; do + [[ -z "$line" ]] && continue + if [[ "$line" == *= ]]; then + without_suffix+=("$line") + else + with_suffix+=("${line//:/\\:}") + fi + done + + # Add completions: no trailing space for "=" options, use _describe for commands + (( ${#without_suffix} )) && compadd -Q -S '' -- "${without_suffix[@]}" + + if (( ${#with_suffix} )); then + if [[ "$current" == -* || "${with_suffix[1]}" == -* ]]; then + compadd -Q -- "${with_suffix[@]}" + else + _describe -t commands 'tempest commands' with_suffix + fi + fi +} + +compdef _tempest -p '*/tempest' +compdef _tempest -p 'tempest' +compdef _tempest php diff --git a/packages/console/tests/Enums/ShellTest.php b/packages/console/tests/Enums/ShellTest.php new file mode 100644 index 0000000000..dcf569e969 --- /dev/null +++ b/packages/console/tests/Enums/ShellTest.php @@ -0,0 +1,108 @@ +assertSame($expected, $result); + } finally { + if ($originalShell === false) { + putenv('SHELL'); + } else { + putenv("SHELL={$originalShell}"); + } + } + } + + public static function detectDataProvider(): array + { + return [ + 'zsh' => ['/bin/zsh', Shell::ZSH], + 'bash' => ['/bin/bash', Shell::BASH], + 'usr local zsh' => ['/usr/local/bin/zsh', Shell::ZSH], + 'usr local bash' => ['/usr/local/bin/bash', Shell::BASH], + 'fish' => ['/bin/fish', null], + 'empty' => ['', null], + 'not set' => [false, null], + ]; + } + + #[Test] + public function getCompletionsDirectory(): void + { + $home = $_SERVER['HOME'] ?? getenv('HOME') ?: ''; + + $this->assertSame($home . '/.zsh/completions', Shell::ZSH->getCompletionsDirectory()); + $this->assertSame($home . '/.bash_completion.d', Shell::BASH->getCompletionsDirectory()); + } + + #[Test] + public function getCompletionFilename(): void + { + $this->assertSame('_tempest', Shell::ZSH->getCompletionFilename()); + $this->assertSame('tempest.bash', Shell::BASH->getCompletionFilename()); + } + + #[Test] + public function getInstalledCompletionPath(): void + { + $home = $_SERVER['HOME'] ?? getenv('HOME') ?: ''; + + $this->assertSame($home . '/.zsh/completions/_tempest', Shell::ZSH->getInstalledCompletionPath()); + $this->assertSame($home . '/.bash_completion.d/tempest.bash', Shell::BASH->getInstalledCompletionPath()); + } + + #[Test] + public function getSourceFilename(): void + { + $this->assertSame('completion.zsh', Shell::ZSH->getSourceFilename()); + $this->assertSame('completion.bash', Shell::BASH->getSourceFilename()); + } + + #[Test] + public function getRcFile(): void + { + $home = $_SERVER['HOME'] ?? getenv('HOME') ?: ''; + + $this->assertSame($home . '/.zshrc', Shell::ZSH->getRcFile()); + $this->assertSame($home . '/.bashrc', Shell::BASH->getRcFile()); + } + + #[Test] + public function getPostInstallInstructions(): void + { + $zshInstructions = Shell::ZSH->getPostInstallInstructions(); + $this->assertIsArray($zshInstructions); + $this->assertNotEmpty($zshInstructions); + $this->assertStringContainsString('fpath', $zshInstructions[0]); + + $bashInstructions = Shell::BASH->getPostInstallInstructions(); + $this->assertIsArray($bashInstructions); + $this->assertNotEmpty($bashInstructions); + $this->assertStringContainsStringIgnoringCase('source', $bashInstructions[0]); + } +} diff --git a/packages/container/composer.json b/packages/container/composer.json index 3897d3cce5..b60a285179 100644 --- a/packages/container/composer.json +++ b/packages/container/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/reflection": "dev-main" }, "autoload": { diff --git a/packages/container/src/Commands/MakeInitializerCommand.php b/packages/container/src/Commands/MakeInitializerCommand.php index 3cdb0eb996..dff9019d0f 100644 --- a/packages/container/src/Commands/MakeInitializerCommand.php +++ b/packages/container/src/Commands/MakeInitializerCommand.php @@ -10,8 +10,8 @@ use Tempest\Container\Stubs\InitializerStub; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { final class MakeInitializerCommand diff --git a/packages/container/src/GenericContainer.php b/packages/container/src/GenericContainer.php index bc1342c741..2483f0640b 100644 --- a/packages/container/src/GenericContainer.php +++ b/packages/container/src/GenericContainer.php @@ -177,11 +177,7 @@ public function get(string $className, null|string|UnitEnum $tag = null, mixed . { $this->resolveChain(); - $dependency = $this->resolve( - className: $className, - tag: $tag, - params: $params, - ); + $dependency = $this->resolve($className, $tag, ...$params); $this->stopChain(); diff --git a/packages/container/src/functions.php b/packages/container/src/functions.php index 4ceadcb0a5..32eee772ba 100644 --- a/packages/container/src/functions.php +++ b/packages/container/src/functions.php @@ -2,42 +2,42 @@ declare(strict_types=1); -namespace Tempest { - use Tempest\Container\GenericContainer; - use Tempest\Reflection\FunctionReflector; - use Tempest\Reflection\MethodReflector; +namespace Tempest\Container; - /** - * Retrieves an instance of the specified `$className` from the container. - * - * @template TClassName of object - * @param class-string $className - * @return TClassName - */ - function get(string $className, ?string $tag = null, mixed ...$params): object - { - $container = GenericContainer::instance(); +use Tempest\Container\GenericContainer; +use Tempest\Reflection\FunctionReflector; +use Tempest\Reflection\MethodReflector; - return $container->get($className, $tag, ...$params); - } +/** + * Retrieves an instance of the specified `$className` from the container. + * + * @template TClassName of object + * @param class-string $className + * @return TClassName + */ +function get(string $className, ?string $tag = null, mixed ...$params): object +{ + $container = GenericContainer::instance(); - /** - * Invokes the given method, function, callable or invokable class from the container. If no named parameters are specified, they will be resolved from the container. - * - * #### Examples - * ```php - * \Tempest\invoke(function (MyService $service) { - * $service->execute(); - * }); - * ``` - * ```php - * \Tempest\invoke(MyService::class, key: $apiKey); - * ``` - */ - function invoke(MethodReflector|FunctionReflector|callable|string $callable, mixed ...$params): mixed - { - $container = GenericContainer::instance(); + return $container->get($className, $tag, ...$params); +} + +/** + * Invokes the given method, function, callable or invokable class from the container. If no named parameters are specified, they will be resolved from the container. + * + * #### Examples + * ```php + * \Tempest\invoke(function (MyService $service) { + * $service->execute(); + * }); + * ``` + * ```php + * \Tempest\invoke(MyService::class, key: $apiKey); + * ``` + */ +function invoke(MethodReflector|FunctionReflector|callable|string $callable, mixed ...$params): mixed +{ + $container = GenericContainer::instance(); - return $container->invoke($callable, ...$params); - } + return $container->invoke($callable, ...$params); } diff --git a/packages/container/tests/ContainerTest.php b/packages/container/tests/ContainerTest.php index 9f8552a8d3..effa97435e 100644 --- a/packages/container/tests/ContainerTest.php +++ b/packages/container/tests/ContainerTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use ReflectionClass; +use Tempest\Container; use Tempest\Container\Exceptions\CircularDependencyEncountered; use Tempest\Container\Exceptions\DecoratorDidNotImplementInterface; use Tempest\Container\Exceptions\DependencyCouldNotBeAutowired; @@ -65,7 +66,7 @@ use Tempest\Container\Tests\Fixtures\UnionTypesClass; use Tempest\Reflection\ClassReflector; -use function Tempest\reflect; +use function Tempest\Reflection\reflect; /** * @internal @@ -411,7 +412,7 @@ public function test_invoke_closure_with_function(): void GenericContainer::setInstance($container = new GenericContainer()); $container->singleton(SingletonClass::class, fn () => new SingletonClass()); - $result = \Tempest\invoke(fn (SingletonClass $class) => $class::class); + $result = Container\invoke(fn (SingletonClass $class) => $class::class); $this->assertEquals(SingletonClass::class, $result); } diff --git a/packages/core/composer.json b/packages/core/composer.json index 226b393e26..cf906e8d38 100644 --- a/packages/core/composer.json +++ b/packages/core/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/container": "dev-main", "tempest/discovery": "dev-main", "tempest/reflection": "dev-main", @@ -13,6 +13,10 @@ "symfony/cache": "^7.3", "filp/whoops": "^2.15" }, + "require-dev": { + "tempest/validation": "dev-main", + "tempest/intl": "dev-main" + }, "autoload": { "psr-4": { "Tempest\\Core\\": "src" diff --git a/packages/core/src/AppConfig.php b/packages/core/src/AppConfig.php index ab1c1dc833..e2109696d3 100644 --- a/packages/core/src/AppConfig.php +++ b/packages/core/src/AppConfig.php @@ -8,26 +8,15 @@ final class AppConfig { - public Environment $environment; - public string $baseUri; + /** @var array> */ + public array $insightsProviders = []; + public function __construct( public ?string $name = null, - - ?Environment $environment = null, - ?string $baseUri = null, - - /** @var class-string<\Tempest\Core\ExceptionProcessor>[] */ - public array $exceptionProcessors = [], - - /** - * @var array> - */ - public array $insightsProviders = [], ) { - $this->environment = $environment ?? Environment::fromEnv(); - $this->baseUri = $baseUri ?? env('BASE_URI') ?? ''; + $this->baseUri = $baseUri ?: env('BASE_URI', default: ''); } } diff --git a/packages/core/src/Commands/DiscoveryClearCommand.php b/packages/core/src/Commands/DiscoveryClearCommand.php index 3b2b4ce519..b31776e113 100644 --- a/packages/core/src/Commands/DiscoveryClearCommand.php +++ b/packages/core/src/Commands/DiscoveryClearCommand.php @@ -16,7 +16,11 @@ public function __construct( private Console $console, ) {} - #[ConsoleCommand(name: 'discovery:clear', description: 'Clears all cached discovery files')] + #[ConsoleCommand( + name: 'discovery:clear', + description: 'Clears all cached discovery files', + aliases: ['d:c', 'dc'], + )] public function __invoke(): void { $this->console->task( diff --git a/packages/core/src/Commands/DiscoveryGenerateCommand.php b/packages/core/src/Commands/DiscoveryGenerateCommand.php index f9f389a694..eacf18292e 100644 --- a/packages/core/src/Commands/DiscoveryGenerateCommand.php +++ b/packages/core/src/Commands/DiscoveryGenerateCommand.php @@ -9,10 +9,10 @@ use Tempest\Console\HasConsole; use Tempest\Container\Container; use Tempest\Container\GenericContainer; -use Tempest\Core\AppConfig; use Tempest\Core\DiscoveryCache; use Tempest\Core\DiscoveryCacheStrategy; use Tempest\Core\DiscoveryConfig; +use Tempest\Core\Environment; use Tempest\Core\FrameworkKernel; use Tempest\Core\Kernel; use Tempest\Core\Kernel\LoadDiscoveryClasses; @@ -27,17 +27,17 @@ public function __construct( private Kernel $kernel, private DiscoveryCache $discoveryCache, - private AppConfig $appConfig, + private Environment $environment, ) {} #[ConsoleCommand( name: 'discovery:generate', description: 'Compile and cache all discovery according to the configured discovery caching strategy', - aliases: ['d:g'], + aliases: ['d:g', 'dg'], )] public function __invoke(): void { - $strategy = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: $this->appConfig->environment->isProduction())); + $strategy = DiscoveryCacheStrategy::resolveFromEnvironment(); if ($strategy === DiscoveryCacheStrategy::NONE) { $this->info('Discovery cache disabled, nothing to generate.'); diff --git a/packages/core/src/ConfigCacheInitializer.php b/packages/core/src/ConfigCacheInitializer.php index 351df4cdf4..584f123fd1 100644 --- a/packages/core/src/ConfigCacheInitializer.php +++ b/packages/core/src/ConfigCacheInitializer.php @@ -14,18 +14,16 @@ final class ConfigCacheInitializer implements Initializer public function initialize(Container $container): ConfigCache { return new ConfigCache( - enabled: $this->shouldCacheBeEnabled( - $container->get(AppConfig::class)->environment->isProduction(), - ), + enabled: $this->shouldCacheBeEnabled(), ); } - private function shouldCacheBeEnabled(bool $isProduction): bool + private function shouldCacheBeEnabled(): bool { if (env('INTERNAL_CACHES') === false) { return false; } - return (bool) env('CONFIG_CACHE', default: $isProduction); + return (bool) env('CONFIG_CACHE', default: Environment::guessFromEnvironment()->requiresCaution()); } } diff --git a/packages/core/src/DevelopmentExceptionHandler.php b/packages/core/src/DevelopmentExceptionHandler.php deleted file mode 100644 index cf4d28db27..0000000000 --- a/packages/core/src/DevelopmentExceptionHandler.php +++ /dev/null @@ -1,64 +0,0 @@ -whoops = new Run(); - $this->whoops->pushHandler($this->createHandler()); - } - - public function handle(Throwable $throwable): void - { - $this->exceptionReporter->report($throwable); - $this->whoops->handleException($throwable); - } - - private function createHandler(): HandlerInterface - { - $handler = new PrettyPageHandler(); - - $handler->addDataTableCallback('Route', function () { - $route = $this->container->get(MatchedRoute::class); - - if (! $route) { - return []; - } - - return [ - 'Handler' => $route->route->handler->getDeclaringClass()->getFileName() . ':' . $route->route->handler->getName(), - 'URI' => $route->route->uri, - 'Allowed parameters' => $route->route->parameters, - 'Received parameters' => $route->params, - ]; - }); - - $handler->addDataTableCallback('Request', function () { - $request = $this->container->get(Request::class); - - return [ - 'URI' => $request->uri, - 'Method' => $request->method->value, - 'Headers' => $request->headers->toArray(), - 'Parsed body' => array_filter(array_values($request->body)) ? $request->body : [], - 'Raw body' => $request->raw, - ]; - }); - - return $handler; - } -} diff --git a/packages/core/src/DiscoveryCache.php b/packages/core/src/DiscoveryCache.php index d42d17ea6d..be6c20a6e3 100644 --- a/packages/core/src/DiscoveryCache.php +++ b/packages/core/src/DiscoveryCache.php @@ -10,6 +10,7 @@ use Tempest\Discovery\Discovery; use Tempest\Discovery\DiscoveryItems; use Tempest\Discovery\DiscoveryLocation; +use Tempest\Support\Filesystem; use Throwable; use function Tempest\internal_storage_path; @@ -86,13 +87,10 @@ public function clear(): void public function storeStrategy(DiscoveryCacheStrategy $strategy): void { - $dir = dirname(self::getCurrentDiscoverStrategyCachePath()); + $path = self::getCurrentDiscoverStrategyCachePath(); - if (! is_dir($dir)) { - mkdir($dir, recursive: true); - } - - file_put_contents(self::getCurrentDiscoverStrategyCachePath(), $strategy->value); + Filesystem\create_directory_for_file($path); + Filesystem\write_file($path, $strategy->value); } public static function getCurrentDiscoverStrategyCachePath(): string diff --git a/packages/core/src/DiscoveryCacheInitializer.php b/packages/core/src/DiscoveryCacheInitializer.php index 1d92321a41..0370519a66 100644 --- a/packages/core/src/DiscoveryCacheInitializer.php +++ b/packages/core/src/DiscoveryCacheInitializer.php @@ -5,8 +5,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; - -use function Tempest\env; +use Tempest\Support\Filesystem; final class DiscoveryCacheInitializer implements Initializer { @@ -14,25 +13,28 @@ final class DiscoveryCacheInitializer implements Initializer public function initialize(Container $container): DiscoveryCache { return new DiscoveryCache( - strategy: $this->resolveDiscoveryCacheStrategy( - $container->get(AppConfig::class)->environment->isProduction(), - ), + strategy: $this->resolveDiscoveryCacheStrategy(), ); } - private function resolveDiscoveryCacheStrategy(bool $isProduction): DiscoveryCacheStrategy + private function resolveDiscoveryCacheStrategy(): DiscoveryCacheStrategy { if ($this->isDiscoveryGenerateCommand() || $this->isDiscoveryClearCommand()) { return DiscoveryCacheStrategy::NONE; } - $current = DiscoveryCacheStrategy::make(env('DISCOVERY_CACHE', default: $isProduction)); + $current = DiscoveryCacheStrategy::resolveFromEnvironment(); if ($current === DiscoveryCacheStrategy::NONE) { return $current; } - $original = DiscoveryCacheStrategy::make(@file_get_contents(DiscoveryCache::getCurrentDiscoverStrategyCachePath())); + $path = DiscoveryCache::getCurrentDiscoverStrategyCachePath(); + $stored = Filesystem\exists($path) + ? Filesystem\read_file($path) + : null; + + $original = DiscoveryCacheStrategy::resolveFromInput($stored); if ($current !== $original) { return DiscoveryCacheStrategy::INVALID; diff --git a/packages/core/src/DiscoveryCacheStrategy.php b/packages/core/src/DiscoveryCacheStrategy.php index b76c8fee50..a8ff3a1ecf 100644 --- a/packages/core/src/DiscoveryCacheStrategy.php +++ b/packages/core/src/DiscoveryCacheStrategy.php @@ -4,14 +4,42 @@ namespace Tempest\Core; +use function Tempest\env; + enum DiscoveryCacheStrategy: string { + /** + * Discovery is completely cached and will not be re-run. + */ case FULL = 'full'; + + /** + * Vendors are cached, application discovery is re-run. + */ case PARTIAL = 'partial'; + + /** + * Discovery is not cached. + */ case NONE = 'none'; + + /** + * There is a mismatch between the stored strategy and the resolved strategy, discovery is considered as not cached. + */ case INVALID = 'invalid'; - public static function make(mixed $input): self + public static function resolveFromEnvironment(): self + { + $environment = Environment::guessFromEnvironment(); + + return static::resolveFromInput(env('DISCOVERY_CACHE', default: match (true) { + $environment->requiresCaution() => true, + $environment->isLocal() => 'partial', + default => false, + })); + } + + public static function resolveFromInput(mixed $input): self { return match ($input) { true, 'true', '1', 1, 'all', 'full' => self::FULL, diff --git a/packages/core/src/Environment.php b/packages/core/src/Environment.php index 93e758436b..658835b69e 100644 --- a/packages/core/src/Environment.php +++ b/packages/core/src/Environment.php @@ -6,14 +6,24 @@ use function Tempest\env; +/** + * Represents the environment in which the application is running. + */ enum Environment: string { case LOCAL = 'local'; case STAGING = 'staging'; case PRODUCTION = 'production'; - case CI = 'ci'; + case CONTINUOUS_INTEGRATION = 'ci'; case TESTING = 'testing'; - case OTHER = 'other'; + + /** + * Determines if this environment requires caution for destructive operations. + */ + public function requiresCaution(): bool + { + return in_array($this, [self::PRODUCTION, self::STAGING], strict: true); + } public function isProduction(): bool { @@ -30,9 +40,9 @@ public function isLocal(): bool return $this === self::LOCAL; } - public function isCI(): bool + public function isContinuousIntegration(): bool { - return $this === self::CI; + return $this === self::CONTINUOUS_INTEGRATION; } public function isTesting(): bool @@ -40,14 +50,17 @@ public function isTesting(): bool return $this === self::TESTING; } - public function isOther(): bool + /** + * Guesses the environment from the `ENVIRONMENT` environment variable. + */ + public static function guessFromEnvironment(): self { - return $this === self::OTHER; - } + $value = env('ENVIRONMENT', default: 'local'); - public static function fromEnv(): self - { - $value = env('ENVIRONMENT', 'local'); + // Can be removed after https://github.com/tempestphp/tempest-framework/pull/1836 + if (is_null($value)) { + $value = 'local'; + } return self::tryFrom($value) ?? throw new EnvironmentValueWasInvalid($value); } diff --git a/packages/core/src/EnvironmentInitializer.php b/packages/core/src/EnvironmentInitializer.php new file mode 100644 index 0000000000..eaed289523 --- /dev/null +++ b/packages/core/src/EnvironmentInitializer.php @@ -0,0 +1,16 @@ + PHP_VERSION, 'Composer version' => $this->getComposerVersion(), 'Operating system' => $this->getOperatingSystem(), - 'Environment' => $this->appConfig->environment->value, + 'Environment' => $this->environment->value, 'Application URL' => $this->appConfig->baseUri ?: new Insight('Not set', Insight::ERROR), ]; } diff --git a/packages/core/src/EnvironmentValueWasInvalid.php b/packages/core/src/EnvironmentValueWasInvalid.php index 9d9995857a..77fd56a13e 100644 --- a/packages/core/src/EnvironmentValueWasInvalid.php +++ b/packages/core/src/EnvironmentValueWasInvalid.php @@ -12,8 +12,10 @@ final class EnvironmentValueWasInvalid extends Exception { public function __construct(string $value) { - $possibleValues = arr(Environment::cases())->map(fn (Environment $environment) => $environment->value)->implode(', '); + $possibleValues = arr(Environment::cases()) + ->map(fn (Environment $environment) => $environment->value) + ->join(); - parent::__construct("Invalid environment value `{$value}`, possible values are {$possibleValues}."); + parent::__construct("Invalid environment [{$value}]. Possible values are {$possibleValues}."); } } diff --git a/packages/core/src/EnvironmentVariableValidationFailed.php b/packages/core/src/EnvironmentVariableValidationFailed.php new file mode 100644 index 0000000000..696278f1b1 --- /dev/null +++ b/packages/core/src/EnvironmentVariableValidationFailed.php @@ -0,0 +1,30 @@ +map(fn (FailingRule $failingRule) => $validator->getErrorMessage($failingRule, $name)) + ->implode("\n- ") + ->toString(), + ])); + } +} diff --git a/packages/core/src/ExceptionProcessor.php b/packages/core/src/ExceptionProcessor.php deleted file mode 100644 index 23c53883c6..0000000000 --- a/packages/core/src/ExceptionProcessor.php +++ /dev/null @@ -1,13 +0,0 @@ -reported[] = $throwable; - - if (! $this->enabled) { - return; - } - - /** @var class-string<\Tempest\Core\ExceptionProcessor> $processor */ - foreach ($this->appConfig->exceptionProcessors as $processor) { - $handler = $this->container->get($processor); - $handler->process($throwable); - } - } -} diff --git a/packages/core/src/ExceptionTester.php b/packages/core/src/ExceptionTester.php deleted file mode 100644 index 25f41e3b2c..0000000000 --- a/packages/core/src/ExceptionTester.php +++ /dev/null @@ -1,90 +0,0 @@ -reporter->enabled = ! $prevent; - - return $this; - } - - /** - * Asserts that the given `$exception` has been reported. - * - * @param null|Closure $callback A callback accepting the exception instance. The assertion fails if the callback returns `false`. - * @param null|int $count If specified, the assertion fails if the exception has been reported a different amount of times. - */ - public function assertReported(string|object $exception, ?Closure $callback = null, ?int $count = null): self - { - Assert::assertNotNull( - actual: $reports = $this->findReports($exception), - message: 'The exception was not reported.', - ); - - if ($count !== null) { - Assert::assertCount($count, $reports, sprintf('Expected %s report(s), got %s.', $count, count($reports))); - } - - if ($callback !== null) { - foreach ($reports as $dispatch) { - Assert::assertNotFalse($callback($dispatch), 'The callback failed.'); - } - } - - return $this; - } - - /** - * Asserts that the given `$exception` was not reported. - */ - public function assertNotReported(string|object $exception): self - { - Assert::assertEmpty( - actual: $this->findReports($exception), - message: 'The exception was reported.', - ); - - return $this; - } - - /** - * Asserts that no exceptions were reported. - */ - public function assertNothingReported(): self - { - Assert::assertEmpty( - actual: $this->reporter->reported, - message: sprintf('There are unexpected reported exceptions: [%s]', implode(', ', $this->reporter->reported)), - ); - - return $this; - } - - private function findReports(string|object $exception): array - { - return array_filter($this->reporter->reported, function (string|object $reported) use ($exception) { - if ($reported === $exception) { - return true; - } - - if (class_exists($exception) && is_a($reported, $exception, allow_string: true)) { - return true; - } - - return false; - }); - } -} diff --git a/packages/core/src/ExceptionHandlerInitializer.php b/packages/core/src/Exceptions/ExceptionHandlerInitializer.php similarity index 76% rename from packages/core/src/ExceptionHandlerInitializer.php rename to packages/core/src/Exceptions/ExceptionHandlerInitializer.php index fe3e24e01f..7f0cb87233 100644 --- a/packages/core/src/ExceptionHandlerInitializer.php +++ b/packages/core/src/Exceptions/ExceptionHandlerInitializer.php @@ -1,11 +1,12 @@ get(AppConfig::class); - return match (true) { PHP_SAPI === 'cli' => $container->get(ConsoleExceptionHandler::class), - $config->environment->isLocal() => $container->get(DevelopmentExceptionHandler::class), default => $container->get(HttpExceptionHandler::class), }; } diff --git a/packages/core/src/Exceptions/ExceptionProcessor.php b/packages/core/src/Exceptions/ExceptionProcessor.php new file mode 100644 index 0000000000..7ccdfb0894 --- /dev/null +++ b/packages/core/src/Exceptions/ExceptionProcessor.php @@ -0,0 +1,17 @@ +get(ExceptionsConfig::class), + container: $container, + ); + } +} diff --git a/packages/core/src/Exceptions/ExceptionReporter.php b/packages/core/src/Exceptions/ExceptionReporter.php new file mode 100644 index 0000000000..9d40e3c0d4 --- /dev/null +++ b/packages/core/src/Exceptions/ExceptionReporter.php @@ -0,0 +1,16 @@ +implements(ExceptionProcessor::class)) { + if ($class->implements(ExceptionReporter::class)) { $this->discoveryItems->add($location, $class->getName()); } } @@ -28,7 +27,7 @@ public function discover(DiscoveryLocation $location, ClassReflector $class): vo public function apply(): void { foreach ($this->discoveryItems as $className) { - $this->appConfig->exceptionProcessors[] = $className; + $this->config->addReporter($className); } } } diff --git a/packages/core/src/Exceptions/ExceptionTester.php b/packages/core/src/Exceptions/ExceptionTester.php new file mode 100644 index 0000000000..0290234cdd --- /dev/null +++ b/packages/core/src/Exceptions/ExceptionTester.php @@ -0,0 +1,118 @@ +recordExceptions(); + $this->processor->enabled = $allow; + + return $this; + } + + /** + * Prevents exceptions from being processed, which means they will not go through the reporting process. This is the default behavior. + */ + public function preventProcessing(bool $prevent = true): self + { + return $this->allowProcessing(! $prevent); + } + + /** + * Asserts that the given `$exception` has been processed. + * + * @param null|Closure $callback A callback accepting the exception instance. The assertion fails if the callback returns `false`. + * @param null|int $count If specified, the assertion fails if the exception has been processed a different amount of times. + */ + public function assertProcessed(string|object $exception, ?Closure $callback = null, ?int $count = null): self + { + Assert::assertNotNull( + actual: $reports = $this->findRecordedProcessings($exception), + message: 'The exception was not processed.', + ); + + if ($count !== null) { + Assert::assertCount($count, $reports, sprintf('Expected %s report(s), got %s.', $count, count($reports))); + } + + if ($callback !== null) { + foreach ($reports as $dispatch) { + Assert::assertNotFalse($callback($dispatch), 'The callback failed.'); + } + } + + return $this; + } + + /** + * Asserts that the given `$exception` was not processed. + */ + public function assertNotProcessed(string|object $exception): self + { + Assert::assertEmpty( + actual: $this->findRecordedProcessings($exception), + message: 'The exception was processed.', + ); + + return $this; + } + + /** + * Asserts that no exceptions were processed. + */ + public function assertNothingProcessed(): self + { + Assert::assertEmpty( + actual: $this->processor->processed, + message: sprintf('There are unexpected processed exceptions: [%s]', implode(', ', $this->processor->processed)), + ); + + return $this; + } + + private function findRecordedProcessings(string|object $exception): array + { + return array_filter($this->processor->processed, function (string|object $reported) use ($exception) { + if ($reported === $exception) { + return true; + } + + if (class_exists($exception) && is_a($reported, $exception, allow_string: true)) { + return true; + } + + return false; + }); + } + + /** + * Records exceptions being reported. + */ + private function recordExceptions(): self + { + $this->container->unregister(ExceptionProcessor::class); + $this->processor = new TestingExceptionProcessor( + processor: $this->container->get(ExceptionProcessor::class), + enabled: true, + ); + + $this->container->singleton(ExceptionProcessor::class, $this->processor); + + return $this; + } +} diff --git a/packages/core/src/Exceptions/ExceptionsConfig.php b/packages/core/src/Exceptions/ExceptionsConfig.php new file mode 100644 index 0000000000..9d0c651ed8 --- /dev/null +++ b/packages/core/src/Exceptions/ExceptionsConfig.php @@ -0,0 +1,39 @@ +> $reporters + * @param bool $logging Whether exception logging is enabled. + */ + public function __construct( + public bool $logging = true, + public array $reporters = [], + ) {} + + /** + * Adds an exception reporter to the configuration. + * + * @param class-string $reporter + */ + public function addReporter(string $reporter): void + { + if ($this->logging === false && $reporter === LoggingExceptionReporter::class) { + return; + } + + $this->reporters[] = $reporter; + } + + /** + * Replaces the list of exception reporters. + * + * @param array> $reporters + */ + public function setReporters(array $reporters): void + { + $this->reporters = $reporters; + } +} diff --git a/packages/core/src/Exceptions/GenericExceptionProcessor.php b/packages/core/src/Exceptions/GenericExceptionProcessor.php new file mode 100644 index 0000000000..fa9e723349 --- /dev/null +++ b/packages/core/src/Exceptions/GenericExceptionProcessor.php @@ -0,0 +1,31 @@ +config->reporters as $reporter) { + try { + $handler = $this->container->get($reporter); + $handler->report($throwable); + } catch (Throwable) { + // @mago-expect lint:no-empty-catch-clause + // If something went wrong with the exception reporter, + // we silently ignore it to avoid infinite loops. + } + } + } +} diff --git a/packages/core/src/Exceptions/LoggingExceptionReporter.php b/packages/core/src/Exceptions/LoggingExceptionReporter.php new file mode 100644 index 0000000000..8454a4b471 --- /dev/null +++ b/packages/core/src/Exceptions/LoggingExceptionReporter.php @@ -0,0 +1,29 @@ +logger->error( + message: $throwable->getMessage() ?: '(no message)', + context: $throwable instanceof ProvidesContext + ? $throwable->context() + : [], + ); + + $this->logger->error($throwable->getTraceAsString()); + } +} diff --git a/packages/core/src/Exceptions/TestingExceptionProcessor.php b/packages/core/src/Exceptions/TestingExceptionProcessor.php new file mode 100644 index 0000000000..0635f5e03d --- /dev/null +++ b/packages/core/src/Exceptions/TestingExceptionProcessor.php @@ -0,0 +1,29 @@ + List of processed exceptions. + */ + private(set) array $processed = []; + + public function __construct( + private(set) ExceptionProcessor $processor, + public bool $enabled, + ) {} + + public function process(Throwable $throwable): void + { + $this->processed[] = $throwable; + + if ($this->enabled === false) { + return; + } + + $this->processor->process($throwable); + } +} diff --git a/packages/core/src/Exceptions/exceptions.config.php b/packages/core/src/Exceptions/exceptions.config.php new file mode 100644 index 0000000000..d7dd7f74fc --- /dev/null +++ b/packages/core/src/Exceptions/exceptions.config.php @@ -0,0 +1,5 @@ +container->addInitializer(DiscoveryCacheInitializer::class); - $this->container->invoke( - LoadDiscoveryClasses::class, - discoveryLocations: $this->discoveryLocations, - ); + $this->container->invoke(LoadDiscoveryClasses::class, discoveryLocations: $this->discoveryLocations); return $this; } @@ -179,7 +174,9 @@ public function loadDiscovery(): self public function loadConfig(): self { $this->container->addInitializer(ConfigCacheInitializer::class); - $this->container->invoke(LoadConfig::class); + + $loadConfig = $this->container->get(LoadConfig::class, environment: Environment::guessFromEnvironment()); + $loadConfig(); return $this; } @@ -223,7 +220,7 @@ public function event(object $event): self public function registerEmergencyExceptionHandler(): self { - $environment = Environment::fromEnv(); + $environment = Environment::guessFromEnvironment(); // During tests, PHPUnit registers its own error handling. if ($environment->isTesting()) { @@ -232,7 +229,7 @@ public function registerEmergencyExceptionHandler(): self // In development, we want to register a developer-friendly error // handler as soon as possible to catch any kind of exception. - if (PHP_SAPI !== 'cli' && ! $environment->isProduction()) { + if (PHP_SAPI !== 'cli' && $environment->isLocal()) { new RegisterEmergencyExceptionHandler()->register(); } @@ -241,8 +238,6 @@ public function registerEmergencyExceptionHandler(): self public function registerExceptionHandler(): self { - $appConfig = $this->container->get(AppConfig::class); - // During tests, PHPUnit registers its own error handling. if ($appConfig->environment->isTesting()) { return $this; @@ -255,6 +250,7 @@ public function registerExceptionHandler(): self $handler = $this->container->get(ExceptionHandler::class); + ini_set('display_errors', 'Off'); // @mago-expect lint:no-ini-set set_exception_handler($handler->handle(...)); set_error_handler(function (int $code, string $message, string $filename, int $line) use ($handler): bool { $handler->handle(new ErrorException( @@ -265,7 +261,7 @@ public function registerExceptionHandler(): self )); return true; - }, error_levels: E_ERROR); + }, error_levels: E_ALL); return $this; } diff --git a/packages/core/src/Insight.php b/packages/core/src/Insight.php index 969cbb049e..079662d45b 100644 --- a/packages/core/src/Insight.php +++ b/packages/core/src/Insight.php @@ -24,7 +24,5 @@ final class Insight public function __construct( private(set) string $value, private string $type = self::NORMAL, - ) { - $this->value = $value; - } + ) {} } diff --git a/packages/core/src/Kernel/LoadConfig.php b/packages/core/src/Kernel/LoadConfig.php index 951e812da6..9efc0396f3 100644 --- a/packages/core/src/Kernel/LoadConfig.php +++ b/packages/core/src/Kernel/LoadConfig.php @@ -4,20 +4,22 @@ namespace Tempest\Core\Kernel; -use Tempest\Core\AppConfig; use Tempest\Core\ConfigCache; +use Tempest\Core\Environment; use Tempest\Core\Kernel; use Tempest\Support\Arr\MutableArray; use Tempest\Support\Filesystem; use Tempest\Support\Str; +use function Tempest\root_path; + /** @internal */ final readonly class LoadConfig { public function __construct( private Kernel $kernel, private ConfigCache $cache, - private AppConfig $appConfig, + private Environment $environment, ) {} public function __invoke(): void @@ -52,14 +54,15 @@ public function find(): array return $configPaths ->filter(fn (string $path) => match (true) { - $this->appConfig->environment->isLocal() => ! Str\contains($path, [...$suffixes['production'], ...$suffixes['staging'], ...$suffixes['testing']]), - $this->appConfig->environment->isProduction() => ! Str\contains($path, [...$suffixes['staging'], ...$suffixes['testing'], ...$suffixes['development']]), - $this->appConfig->environment->isStaging() => ! Str\contains($path, [...$suffixes['testing'], ...$suffixes['development'], ...$suffixes['production']]), + $this->environment->isLocal() => ! Str\contains($path, [...$suffixes['production'], ...$suffixes['staging'], ...$suffixes['testing']]), + $this->environment->isProduction() => ! Str\contains($path, [...$suffixes['staging'], ...$suffixes['testing'], ...$suffixes['development']]), + $this->environment->isStaging() => ! Str\contains($path, [...$suffixes['testing'], ...$suffixes['development'], ...$suffixes['production']]), default => true, }) ->sortByCallback(function (string $path1, string $path2) use ($suffixes): int { $getPriority = fn (string $path): int => match (true) { Str\contains($path, DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR) => 0, + ! Str\contains($path, root_path()) => 0, Str\contains($path, $suffixes['testing']) => 6, Str\contains($path, $suffixes['development']) => 5, Str\contains($path, $suffixes['production']) => 4, diff --git a/packages/core/src/LogExceptionProcessor.php b/packages/core/src/LogExceptionProcessor.php deleted file mode 100644 index f74569f675..0000000000 --- a/packages/core/src/LogExceptionProcessor.php +++ /dev/null @@ -1,26 +0,0 @@ - $throwable::class, - 'exception' => $throwable->getMessage(), - 'trace' => $throwable->getTraceAsString(), - 'context' => $throwable instanceof HasContext - ? $throwable->context() - : [], - ]; - - Debug::resolve()->log($items, writeToOut: false); - } -} diff --git a/packages/core/src/HasContext.php b/packages/core/src/ProvidesContext.php similarity index 53% rename from packages/core/src/HasContext.php rename to packages/core/src/ProvidesContext.php index e3b7e59f5a..40a5fb9e67 100644 --- a/packages/core/src/HasContext.php +++ b/packages/core/src/ProvidesContext.php @@ -2,10 +2,10 @@ namespace Tempest\Core; -interface HasContext +interface ProvidesContext { /** - * Provides context for the exception-handling pipeline. + * Provides context for debugging. */ public function context(): iterable; } diff --git a/packages/core/src/PublishesFiles.php b/packages/core/src/PublishesFiles.php index c2bc10cc9c..410d5d156b 100644 --- a/packages/core/src/PublishesFiles.php +++ b/packages/core/src/PublishesFiles.php @@ -10,12 +10,12 @@ use Tempest\Console\HasConsole; use Tempest\Container\Inject; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; -use Tempest\Generation\Enums\StubFileType; -use Tempest\Generation\Exceptions\FileGenerationFailedException; -use Tempest\Generation\Exceptions\FileGenerationWasAborted; -use Tempest\Generation\StubFileGenerator; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; +use Tempest\Generation\Php\Exceptions\FileGenerationFailedException; +use Tempest\Generation\Php\Exceptions\FileGenerationWasAborted; +use Tempest\Generation\Php\StubFileGenerator; +use Tempest\Generation\Php\StubFileType; use Tempest\Reflection\FunctionReflector; use Tempest\Support\Filesystem; use Tempest\Support\Json; diff --git a/packages/core/src/Tempest.php b/packages/core/src/Tempest.php index 1b30622079..370eacb6ef 100644 --- a/packages/core/src/Tempest.php +++ b/packages/core/src/Tempest.php @@ -8,19 +8,15 @@ final readonly class Tempest { - public static function boot( - ?string $root = null, - /** @var \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */ - array $discoveryLocations = [], - ?string $internalStorage = null, - ): Container { - $root ??= getcwd(); - - // Kernel - return FrameworkKernel::boot( - root: $root, + /** @param \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */ + public static function boot(?string $root = null, array $discoveryLocations = [], ?string $internalStorage = null): Container + { + $kernel = FrameworkKernel::boot( + root: $root ?? getcwd(), discoveryLocations: $discoveryLocations, internalStorage: $internalStorage, - )->container; + ); + + return $kernel->container; } } diff --git a/packages/core/src/functions.php b/packages/core/src/functions.php index 4c258d29f9..3e93e0267e 100644 --- a/packages/core/src/functions.php +++ b/packages/core/src/functions.php @@ -2,95 +2,105 @@ declare(strict_types=1); -namespace Tempest { - use Closure; - use Stringable; - use Tempest\Core\Composer; - use Tempest\Core\DeferredTasks; - use Tempest\Core\ExceptionReporter; - use Tempest\Core\Kernel; - use Tempest\Support\Namespace\PathCouldNotBeMappedToNamespace; - use Throwable; +namespace Tempest; - use function Tempest\Support\Namespace\to_psr4_namespace; - use function Tempest\Support\Path\to_absolute_path; +use Closure; +use Stringable; +use Tempest\Container; +use Tempest\Core\Composer; +use Tempest\Core\DeferredTasks; +use Tempest\Core\EnvironmentVariableValidationFailed; +use Tempest\Core\Kernel; +use Tempest\Intl\Translator; +use Tempest\Support\Namespace\PathCouldNotBeMappedToNamespace; +use Tempest\Validation\Rule; +use Tempest\Validation\Validator; - /** - * Creates an absolute path scoped to the root of the project. - */ - function root_path(Stringable|string ...$parts): string - { - return to_absolute_path(get(Kernel::class)->root, ...$parts); - } +use function Tempest\Support\Namespace\to_psr4_namespace; +use function Tempest\Support\Path\to_absolute_path; - /** - * Creates an absolute path scoped to the main directory of the project. - */ - function src_path(Stringable|string ...$parts): string - { - return root_path(get(Composer::class)->mainNamespace->path, ...$parts); - } +/** + * Creates an absolute path scoped to the root of the project. + */ +function root_path(Stringable|string ...$parts): string +{ + return to_absolute_path(Container\get(Kernel::class)->root, ...$parts); +} - /** - * Creates an absolute path scoped to the framework's internal storage directory. - */ - function internal_storage_path(Stringable|string ...$parts): string - { - return to_absolute_path(get(Kernel::class)->internalStorage, ...$parts); - } +/** + * Creates an absolute path scoped to the main directory of the project. + */ +function src_path(Stringable|string ...$parts): string +{ + return root_path(Container\get(Composer::class)->mainNamespace->path, ...$parts); +} - /** - * Converts the given path to a registered namespace. The path is expected to be absolute, or relative to the root of the project. - * - * @throws PathCouldNotBeMappedToNamespace If the path cannot be mapped to registered namespace - */ - function registered_namespace(Stringable|string ...$parts): string - { - return to_psr4_namespace(get(Composer::class)->namespaces, root_path(...$parts), root: root_path()); - } +/** + * Creates an absolute path scoped to the framework's internal storage directory. + */ +function internal_storage_path(Stringable|string ...$parts): string +{ + return to_absolute_path(Container\get(Kernel::class)->internalStorage, ...$parts); +} - /** - * Converts the given path to the main namespace. The path is expected to be absolute, or relative to the root of the project. - * - * @throws PathCouldNotBeMappedToNamespace If the path cannot be mapped to the main namespace - */ - function src_namespace(Stringable|string ...$parts): string - { - return to_psr4_namespace(get(Composer::class)->mainNamespace, root_path(...$parts), root: root_path()); - } +/** + * Converts the given path to a registered namespace. The path is expected to be absolute, or relative to the root of the project. + * + * @throws PathCouldNotBeMappedToNamespace If the path cannot be mapped to registered namespace + */ +function registered_namespace(Stringable|string ...$parts): string +{ + return to_psr4_namespace(Container\get(Composer::class)->namespaces, root_path(...$parts), root: root_path()); +} - /** - * Retrieves the given `$key` from the environment variables. If `$key` is not defined, `$default` is returned instead. - */ - function env(string $key, mixed $default = null): mixed - { - $value = getenv($key); +/** + * Converts the given path to the main namespace. The path is expected to be absolute, or relative to the root of the project. + * + * @throws PathCouldNotBeMappedToNamespace If the path cannot be mapped to the main namespace + */ +function src_namespace(Stringable|string ...$parts): string +{ + return to_psr4_namespace(Container\get(Composer::class)->mainNamespace, root_path(...$parts), root: root_path()); +} - if ($value === false) { - return $default; - } +/** + * Retrieves the given `$key` from the environment variables. If `$key` is not defined, `$default` is returned instead. + * + * @param Rule[] $rules Optional validation rules for the value of this environment variable. If one of the rules don't pass, an exception is thrown, preventing the application from booting. + */ +function env(string $key, mixed $default = null, array $rules = []): mixed +{ + $value = getenv($key); + $value = match (is_string($value) ? mb_strtolower($value) : $value) { + 'true' => true, + 'false' => false, + false, 'null', '' => $default, + default => $value, + }; - return match (strtolower($value)) { - 'true' => true, - 'false' => false, - 'null', '' => null, - default => $value, - }; + if ($rules === [] || ! class_exists(Validator::class) || ! interface_exists(Translator::class)) { + return $value; } - /** - * Defer a task, will be run after a request has been sent or a command has executed - */ - function defer(Closure $closure): void - { - get(DeferredTasks::class)->add($closure); - } + $validator = Container\get(Validator::class); + $failures = $validator->validateValue($value, $rules); - /** - * Passes the given exception through registered exception processors. - */ - function report(Throwable $throwable): void - { - get(ExceptionReporter::class)->report($throwable); + if ($failures === []) { + return $value; } + + throw new EnvironmentVariableValidationFailed( + name: $key, + value: $value, + failingRules: $failures, + validator: $validator, + ); +} + +/** + * Defer a task, will be run after a request has been sent or a command has executed + */ +function defer(Closure $closure): void +{ + Container\get(DeferredTasks::class)->add($closure); } diff --git a/packages/core/tests/EnvTest.php b/packages/core/tests/EnvTest.php new file mode 100644 index 0000000000..eebe77a16d --- /dev/null +++ b/packages/core/tests/EnvTest.php @@ -0,0 +1,120 @@ +markTestSkipped('`tempest/intl` is required for this test.'); + } + + if (! class_exists(Validator::class)) { + $this->markTestSkipped('`tempest/validation` is required for this test.'); + } + + $container = new GenericContainer(); + $container->singleton(Translator::class, new GenericTranslator( + config: new IntlConfig(currentLocale: Locale::ENGLISH, fallbackLocale: Locale::ENGLISH), + catalog: new GenericCatalog([ + 'en' => [ + 'validation_error' => [ + 'is_numeric' => '{{{$field} must be a numeric value}}', + ], + ], + ]), + formatter: new MessageFormatter(), + )); + + GenericContainer::setInstance($container); + } + + #[Test] + #[TestWith([null, null])] + #[TestWith(['', null])] + #[TestWith(['null', null])] + #[TestWith([false, null])] + #[TestWith(['FALSE', false])] + #[TestWith(['false', false])] + #[TestWith(['TRUE', true])] + #[TestWith(['true', true])] + #[TestWith(['foo', 'foo'])] + #[TestWith(['FOO', 'FOO'])] + #[TestWith([1, '1'])] + public function basic(mixed $value, mixed $expected): void + { + putenv("_ENV_TESTING_KEY={$value}"); + + $this->assertSame($expected, env('_ENV_TESTING_KEY')); + } + + #[Test] + #[TestWith([null, 'fallback', 'fallback'])] + #[TestWith([false, 'fallback', 'fallback'])] + #[TestWith(['', 'fallback', 'fallback'])] + #[TestWith(['false', 'fallback', false])] + #[TestWith(['true', 'fallback', true])] + #[TestWith([false, '', ''])] + #[TestWith([null, '', ''])] + #[TestWith(['', '', ''])] + #[TestWith([false, false, false])] + #[TestWith([null, false, false])] + #[TestWith(['', false, false])] + public function default(mixed $value, mixed $default, mixed $expected): void + { + putenv("_ENV_TESTING_KEY={$value}"); + + $this->assertSame($expected, env('_ENV_TESTING_KEY', default: $default)); + } + + #[Test] + public function fails_with_failing_rules(): void + { + $this->expectException(EnvironmentVariableValidationFailed::class); + $this->expectExceptionMessageMatches('*_ENV_TESTING_KEY must be a numeric value*'); + + putenv('_ENV_TESTING_KEY=foo'); + env('_ENV_TESTING_KEY', rules: [new IsNumeric()]); + } + + #[Test] + #[TestWith([null, null])] + #[TestWith(['', null])] + #[TestWith([false, null])] + public function default_taken_into_account(mixed $value, mixed $default): void + { + $this->expectException(EnvironmentVariableValidationFailed::class); + + putenv("_ENV_TESTING_KEY={$value}"); + env('_ENV_TESTING_KEY', default: $default, rules: [new IsNotNull()]); + } + + #[Test] + public function can_pass(): void + { + putenv('_ENV_TESTING_KEY=true'); + + $this->assertSame(true, env('_ENV_TESTING_KEY', rules: [new IsBoolean()])); + } +} diff --git a/packages/core/tests/EnvironmentTest.php b/packages/core/tests/EnvironmentTest.php new file mode 100644 index 0000000000..5f955f0e3e --- /dev/null +++ b/packages/core/tests/EnvironmentTest.php @@ -0,0 +1,45 @@ +assertSame(Environment::LOCAL, Environment::guessFromEnvironment()); + } + + #[Test] + public function throws_on_unknown_value(): void + { + putenv('ENVIRONMENT=unknown'); + + $this->expectException(EnvironmentValueWasInvalid::class); + + Environment::guessFromEnvironment(); + } + + #[Test] + public function can_be_resolved_from_container(): void + { + $container = new GenericContainer(); + $container->addInitializer(EnvironmentInitializer::class); + + putenv('ENVIRONMENT=staging'); + $this->assertSame(Environment::STAGING, $container->get(Environment::class)); + + // ensure it's a singleton + putenv('ENVIRONMENT=production'); + $this->assertSame(Environment::STAGING, $container->get(Environment::class)); + } +} diff --git a/packages/cryptography/composer.json b/packages/cryptography/composer.json index 40b072222a..a60e22ff57 100644 --- a/packages/cryptography/composer.json +++ b/packages/cryptography/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/container": "dev-main", "tempest/support": "dev-main", "tempest/clock": "dev-main" diff --git a/packages/cryptography/src/Encryption/EncryptionKey.php b/packages/cryptography/src/Encryption/EncryptionKey.php index 0f4cb16c65..e6a88e7247 100644 --- a/packages/cryptography/src/Encryption/EncryptionKey.php +++ b/packages/cryptography/src/Encryption/EncryptionKey.php @@ -16,7 +16,7 @@ public function __construct( } if (strlen($value) !== $algorithm->getKeyLength()) { - throw EncryptionKeyWasInvalid::becauseLengthMismatched($algorithm); + throw EncryptionKeyWasInvalid::becauseLengthMismatched($algorithm, strlen($value)); } } diff --git a/packages/cryptography/src/Encryption/Exceptions/DecryptionFailed.php b/packages/cryptography/src/Encryption/Exceptions/DecryptionFailed.php index a206fd55e5..2df2b93a62 100644 --- a/packages/cryptography/src/Encryption/Exceptions/DecryptionFailed.php +++ b/packages/cryptography/src/Encryption/Exceptions/DecryptionFailed.php @@ -3,9 +3,9 @@ namespace Tempest\Cryptography\Encryption\Exceptions; use Exception; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; -final class DecryptionFailed extends Exception implements EncryptionException, HasContext +final class DecryptionFailed extends Exception implements EncryptionException, ProvidesContext { public function __construct( string $message, diff --git a/packages/cryptography/src/Encryption/Exceptions/EncryptionFailed.php b/packages/cryptography/src/Encryption/Exceptions/EncryptionFailed.php index f46f8e5526..d8ad49f228 100644 --- a/packages/cryptography/src/Encryption/Exceptions/EncryptionFailed.php +++ b/packages/cryptography/src/Encryption/Exceptions/EncryptionFailed.php @@ -3,9 +3,9 @@ namespace Tempest\Cryptography\Encryption\Exceptions; use Exception; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; -final class EncryptionFailed extends Exception implements EncryptionException, HasContext +final class EncryptionFailed extends Exception implements EncryptionException, ProvidesContext { public function __construct( string $message, diff --git a/packages/cryptography/src/Encryption/Exceptions/EncryptionKeyWasInvalid.php b/packages/cryptography/src/Encryption/Exceptions/EncryptionKeyWasInvalid.php index 317201ff2a..2bd7cfb299 100644 --- a/packages/cryptography/src/Encryption/Exceptions/EncryptionKeyWasInvalid.php +++ b/packages/cryptography/src/Encryption/Exceptions/EncryptionKeyWasInvalid.php @@ -16,14 +16,17 @@ public function __construct( public static function becauseItIsMissing(EncryptionAlgorithm $algorithm): self { - return new self('The encryption key is missing or empty. Ensure you have a `SIGNING_KEY` environment variable.', $algorithm); + return new self( + message: 'The encryption key is missing or empty. Generate a valid `SIGNING_KEY` with `php tempest key:generate`.', + algorithm: $algorithm, + ); } - public static function becauseLengthMismatched(EncryptionAlgorithm $algorithm): self + public static function becauseLengthMismatched(EncryptionAlgorithm $algorithm, int $actualLength): self { return new self( - "The encryption key length does not match the expected length ({$algorithm->getKeyLength()}).", - $algorithm, + message: "The encryption key length ({$actualLength}) does not match the expected length ({$algorithm->getKeyLength()}). Generate a valid `SIGNING_KEY` with `php tempest key:generate`.", + algorithm: $algorithm, ); } } diff --git a/packages/cryptography/src/GenerateSigningKeyCommand.php b/packages/cryptography/src/GenerateSigningKeyCommand.php index fb22a76594..f65b1da33c 100644 --- a/packages/cryptography/src/GenerateSigningKeyCommand.php +++ b/packages/cryptography/src/GenerateSigningKeyCommand.php @@ -21,7 +21,11 @@ public function __construct( private Console $console, ) {} - #[ConsoleCommand('key:generate', description: 'Generates the signing key required to sign and verify data.')] + #[ConsoleCommand( + name: 'key:generate', + description: 'Generates the signing key required to sign and verify data.', + aliases: ['generate:key'], + )] public function __invoke(bool $override = true): ExitCode { $key = EncryptionKey::generate($this->encryptionConfig->algorithm); diff --git a/packages/cryptography/src/Signing/Exceptions/SigningKeyWasInvalid.php b/packages/cryptography/src/Signing/Exceptions/SigningKeyWasInvalid.php index 45d5dfd95d..211bdc05dc 100644 --- a/packages/cryptography/src/Signing/Exceptions/SigningKeyWasInvalid.php +++ b/packages/cryptography/src/Signing/Exceptions/SigningKeyWasInvalid.php @@ -8,6 +8,6 @@ final class SigningKeyWasInvalid extends Exception implements SigningException { public static function becauseItIsMissing(): self { - return new self('The signing key is missing or empty. Ensure you have a `SIGNING_KEY` environment variable.'); + return new self('The signing key is missing or empty. Generate a valid `SIGNING_KEY` with `php tempest key:generate`.'); } } diff --git a/packages/database/composer.json b/packages/database/composer.json index 3fe7e0ba3f..74792cf84b 100644 --- a/packages/database/composer.json +++ b/packages/database/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "ext-pdo": "*", "tempest/container": "dev-main", "tempest/event-bus": "dev-main", diff --git a/packages/database/src/Builder/ModelInspector.php b/packages/database/src/Builder/ModelInspector.php index b687e874db..7b349e3a85 100644 --- a/packages/database/src/Builder/ModelInspector.php +++ b/packages/database/src/Builder/ModelInspector.php @@ -11,31 +11,60 @@ use Tempest\Database\PrimaryKey; use Tempest\Database\Relation; use Tempest\Database\Table; +use Tempest\Database\Uuid; use Tempest\Database\Virtual; use Tempest\Mapper\SerializeAs; use Tempest\Mapper\SerializeWith; use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; use Tempest\Support\Arr\ImmutableArray; +use Tempest\Support\Memoization\HasMemoization; use Tempest\Validation\Exceptions\ValidationFailed; use Tempest\Validation\SkipValidation; use Tempest\Validation\Validator; +use function Tempest\Container\get; use function Tempest\Database\inspect; -use function Tempest\get; use function Tempest\Support\arr; use function Tempest\Support\str; final class ModelInspector { - private(set) ?ClassReflector $reflector; + use HasMemoization; - private(set) object|string $instance; + private static array $inspectors = []; + + private(set) ?ClassReflector $reflector = null; + + private(set) object|string|null $instance = null; private Validator $validator { get => get(Validator::class); } + public static function reset(): void + { + self::$inspectors = []; + } + + public static function forModel(object|string $model): self + { + $key = match (true) { + is_string($model) => $model, + $model instanceof HasMany => $model->property->getIterableType()->getName(), + $model instanceof BelongsTo => $model->property->getType()->getName(), + $model instanceof HasOne => $model->property->getType()->getName(), + $model instanceof ClassReflector => $model->getName(), + default => null, + }; + + if ($key === null) { + return new self($model); + } + + return self::$inspectors[$key] ??= new self($model); + } + public function __construct( private(set) object|string $model, ) { @@ -50,12 +79,11 @@ public function __construct( } else { try { $this->reflector = new ClassReflector($model); + $this->instance = $model; } catch (ReflectionException) { $this->reflector = null; } } - - $this->instance = $model; } public function isObjectModel(): bool @@ -65,27 +93,31 @@ public function isObjectModel(): bool public function getTableDefinition(): TableDefinition { - if (! $this->isObjectModel()) { - return new TableDefinition($this->instance); - } + return $this->memoize('getTableDefinition', function () { + if (! $this->isObjectModel()) { + return new TableDefinition($this->model); + } - $specificName = $this->reflector - ->getAttribute(Table::class) - ?->name; + $specificName = $this->reflector + ->getAttribute(Table::class) + ?->name; - $conventionalName = get(DatabaseConfig::class) - ->namingStrategy - ->getName($this->reflector->getName()); + $conventionalName = get(DatabaseConfig::class) + ->namingStrategy + ->getName($this->reflector->getName()); - return new TableDefinition($specificName ?? $conventionalName); + return new TableDefinition($specificName ?? $conventionalName); + }); } public function getFieldDefinition(string $field): FieldDefinition { - return new FieldDefinition( - $this->getTableDefinition(), - $field, - ); + return $this->memoize('getFieldDefinition' . $field, function () use ($field) { + return new FieldDefinition( + $this->getTableDefinition(), + $field, + ); + }); } public function getTableName(): string @@ -95,162 +127,174 @@ public function getTableName(): string public function getPropertyValues(): array { - if (! $this->isObjectModel()) { - return []; - } + return $this->memoize('getPropertyValues', function () { + if (! $this->isObjectModel()) { + return []; + } - if (! is_object($this->instance)) { - return []; - } + if (! is_object($this->instance)) { + return []; + } - $values = []; + $values = []; - foreach ($this->reflector->getProperties() as $property) { - if ($property->isVirtual()) { - continue; - } + foreach ($this->reflector->getProperties() as $property) { + if ($property->isVirtual()) { + continue; + } - if ($property->hasAttribute(Virtual::class)) { - continue; - } + if ($property->hasAttribute(Virtual::class)) { + continue; + } - if (! $property->isInitialized($this->instance)) { - continue; - } + if (! $property->isInitialized($this->instance)) { + continue; + } - if ($this->getHasMany($property->getName()) || $this->getHasOne($property->getName())) { - continue; - } + if ($this->getHasMany($property->getName()) || $this->getHasOne($property->getName())) { + continue; + } - $name = $property->getName(); + $name = $property->getName(); - $values[$name] = $property->getValue($this->instance); - } + $values[$name] = $property->getValue($this->instance); + } - return $values; + return $values; + }); } public function getBelongsTo(string $name): ?BelongsTo { - if (! $this->isObjectModel()) { - return null; - } + return $this->memoize('getBelongsTo' . $name, function () use ($name) { + if (! $this->isObjectModel()) { + return null; + } - $name = str($name)->camel(); + $name = str($name)->camel(); - $singularizedName = $name->singularizeLastWord(); + $singularizedName = $name->singularizeLastWord(); - if (! $singularizedName->equals($name)) { - return $this->getBelongsTo($singularizedName); - } + if (! $singularizedName->equals($name)) { + return $this->getBelongsTo($singularizedName); + } - if (! $this->reflector->hasProperty($name)) { - return null; - } + if (! $this->reflector->hasProperty($name)) { + return null; + } - $property = $this->reflector->getProperty($name); + $property = $this->reflector->getProperty($name); - if ($belongsTo = $property->getAttribute(BelongsTo::class)) { - return $belongsTo; - } + if ($belongsTo = $property->getAttribute(BelongsTo::class)) { + return $belongsTo; + } - if ($property->hasAttribute(Virtual::class)) { - return null; - } + if ($property->hasAttribute(Virtual::class)) { + return null; + } - if (! $property->getType()->isRelation()) { - return null; - } + if (! $property->getType()->isRelation()) { + return null; + } - if ($property->hasAttribute(SerializeWith::class) || $property->getType()->asClass()->hasAttribute(SerializeWith::class)) { - return null; - } + if ($property->hasAttribute(SerializeWith::class) || $property->getType()->asClass()->hasAttribute(SerializeWith::class)) { + return null; + } - if ($property->getType()->asClass()->hasAttribute(SerializeAs::class)) { - return null; - } + if ($property->getType()->asClass()->hasAttribute(SerializeAs::class)) { + return null; + } - if ($property->hasAttribute(HasOne::class)) { - return null; - } + if ($property->hasAttribute(HasOne::class)) { + return null; + } - $belongsTo = new BelongsTo(); - $belongsTo->property = $property; + $belongsTo = new BelongsTo(); + $belongsTo->property = $property; - return $belongsTo; + return $belongsTo; + }); } public function getHasOne(string $name): ?HasOne { - if (! $this->isObjectModel()) { - return null; - } + return $this->memoize('getHasOne' . $name, function () use ($name) { + if (! $this->isObjectModel()) { + return null; + } - $name = str($name)->camel(); + $name = str($name)->camel(); - $singularizedName = $name->singularizeLastWord(); + $singularizedName = $name->singularizeLastWord(); - if (! $singularizedName->equals($name)) { - return $this->getHasOne($singularizedName); - } + if (! $singularizedName->equals($name)) { + return $this->getHasOne($singularizedName); + } - if (! $this->reflector->hasProperty($name)) { - return null; - } + if (! $this->reflector->hasProperty($name)) { + return null; + } - $property = $this->reflector->getProperty($name); + $property = $this->reflector->getProperty($name); - if ($hasOne = $property->getAttribute(HasOne::class)) { - return $hasOne; - } + if ($hasOne = $property->getAttribute(HasOne::class)) { + return $hasOne; + } - return null; + return null; + }); } public function getHasMany(string $name): ?HasMany { - if (! $this->isObjectModel()) { - return null; - } + return $this->memoize('getHasMany' . $name, function () use ($name) { + if (! $this->isObjectModel()) { + return null; + } - $name = str($name)->camel(); + $name = str($name)->camel(); - if (! $this->reflector->hasProperty($name)) { - return null; - } + if (! $this->reflector->hasProperty($name)) { + return null; + } - $property = $this->reflector->getProperty($name); + $property = $this->reflector->getProperty($name); - if ($hasMany = $property->getAttribute(HasMany::class)) { - return $hasMany; - } + if ($hasMany = $property->getAttribute(HasMany::class)) { + return $hasMany; + } - if ($property->hasAttribute(Virtual::class)) { - return null; - } + if ($property->hasAttribute(Virtual::class)) { + return null; + } - if (! $property->getIterableType()?->isRelation()) { - return null; - } + if (! $property->getIterableType()?->isRelation()) { + return null; + } - $hasMany = new HasMany(); - $hasMany->property = $property; + $hasMany = new HasMany(); + $hasMany->property = $property; - return $hasMany; + return $hasMany; + }); } public function isRelation(string|PropertyReflector $name): bool { $name = $name instanceof PropertyReflector ? $name->getName() : $name; - return $this->getBelongsTo($name) !== null || $this->getHasOne($name) !== null || $this->getHasMany($name) !== null; + return $this->memoize('isRelation' . $name, function () use ($name) { + return $this->getBelongsTo($name) !== null || $this->getHasOne($name) !== null || $this->getHasMany($name) !== null; + }); } public function getRelation(string|PropertyReflector $name): ?Relation { $name = $name instanceof PropertyReflector ? $name->getName() : $name; - return $this->getBelongsTo($name) ?? $this->getHasOne($name) ?? $this->getHasMany($name); + return $this->memoize('getRelation' . $name, function () use ($name) { + return $this->getBelongsTo($name) ?? $this->getHasOne($name) ?? $this->getHasMany($name); + }); } /** @@ -258,19 +302,21 @@ public function getRelation(string|PropertyReflector $name): ?Relation */ public function getRelations(): ImmutableArray { - if (! $this->isObjectModel()) { - return arr(); - } + return $this->memoize('getRelations', function () { + if (! $this->isObjectModel()) { + return arr(); + } - $relationFields = arr(); + $relationFields = arr(); - foreach ($this->reflector->getPublicProperties() as $property) { - if ($relation = $this->getRelation($property->getName())) { - $relationFields[] = $relation; + foreach ($this->reflector->getPublicProperties() as $property) { + if ($relation = $this->getRelation($property->getName())) { + $relationFields[] = $relation; + } } - } - return $relationFields; + return $relationFields; + }); } /** @@ -490,7 +536,7 @@ public function getName(): string return $this->reflector->getName(); } - return $this->instance; + return $this->model; } public function getQualifiedPrimaryKey(): ?string @@ -549,4 +595,14 @@ public function getPrimaryKeyValue(): ?PrimaryKey return $primaryKeyProperty->getValue($this->instance); } + + public function hasUuidPrimaryKey(): bool + { + return $this->getPrimaryKeyProperty()?->hasAttribute(Uuid::class) ?? false; + } + + public function isUuidPrimaryKey(PropertyReflector $property): bool + { + return $property->getType()->matches(PrimaryKey::class) && $property->hasAttribute(Uuid::class); + } } diff --git a/packages/database/src/Builder/QueryBuilders/HasWhereQueryBuilderMethods.php b/packages/database/src/Builder/QueryBuilders/HasWhereQueryBuilderMethods.php index ea48c17968..0353395b7c 100644 --- a/packages/database/src/Builder/QueryBuilders/HasWhereQueryBuilderMethods.php +++ b/packages/database/src/Builder/QueryBuilders/HasWhereQueryBuilderMethods.php @@ -11,7 +11,7 @@ /** * @template TModel of object * @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\BuildsQuery - * @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereConditions + * @phpstan-require-implements \Tempest\Database\Builder\QueryBuilders\SupportsWhereStatements * @use \Tempest\Database\Builder\QueryBuilders\HasConvenientWhereMethods */ trait HasWhereQueryBuilderMethods diff --git a/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php b/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php index 0cf62c3fbf..a438e90351 100644 --- a/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php +++ b/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php @@ -4,6 +4,8 @@ use Closure; use Tempest\Database\Builder\ModelInspector; +use Tempest\Database\Database; +use Tempest\Database\DatabaseContext; use Tempest\Database\Exceptions\HasManyRelationCouldNotBeInsterted; use Tempest\Database\Exceptions\HasOneRelationCouldNotBeInserted; use Tempest\Database\Exceptions\ModelDidNotHavePrimaryColumn; @@ -19,8 +21,10 @@ use Tempest\Reflection\PropertyReflector; use Tempest\Support\Arr; use Tempest\Support\Conditions\HasConditions; +use Tempest\Support\Random; use Tempest\Support\Str\ImmutableString; +use function Tempest\Container\get; use function Tempest\Database\inspect; use function Tempest\Support\str; @@ -40,6 +44,14 @@ final class InsertQueryBuilder implements BuildsQuery public ModelInspector $model; + private Database $database { + get => get(Database::class, $this->onDatabase); + } + + private DatabaseContext $context { + get => new DatabaseContext(dialect: $this->database->dialect); + } + /** * @param class-string|string|TModel $model */ @@ -210,7 +222,7 @@ private function addHasManyRelationCallback(string $relationName, array $relatio ? $this->removeTablePrefix($hasMany->ownerJoin) : $this->getDefaultForeignKeyName(); - $insert = Arr\map_iterable( + $insert = Arr\map( array: $relations, map: fn ($item) => $this->prepareRelationItem($item, $foreignKey, $parentId), ); @@ -294,7 +306,7 @@ private function handleStandardHasOneRelation(HasOne $hasOne, object|array $rela private function resolveData(): array { - return Arr\map_iterable( + return Arr\map( array: $this->rows, map: fn (object|iterable $model) => $this->resolveModelData($model), ); @@ -390,7 +402,15 @@ private function resolveObjectData(object $model): array $entry = []; foreach ($modelClass->getPublicProperties() as $property) { + $propertyName = $property->getName(); + if (! $property->isInitialized($model)) { + if ($definition->isUuidPrimaryKey($property)) { + $uuid = new PrimaryKey(Random\uuid()); + $property->setValue($model, $uuid); + $entry[$propertyName] = $this->serializeValue($property, $uuid); + } + continue; } @@ -398,8 +418,6 @@ private function resolveObjectData(object $model): array continue; } - $propertyName = $property->getName(); - if ($property->hasAttribute(Virtual::class)) { continue; } @@ -469,7 +487,10 @@ private function serializeValue(PropertyReflector $property, mixed $value): mixe return null; } - return $this->serializerFactory->forProperty($property)?->serialize($value) ?? $value; + return $this->serializerFactory + ->in($this->context) + ->forProperty($property) + ?->serialize($value) ?? $value; } private function serializeIterableValue(string $key, mixed $value): mixed diff --git a/packages/database/src/Builder/QueryBuilders/QueryBuilder.php b/packages/database/src/Builder/QueryBuilders/QueryBuilder.php index 1b2a55da37..9394d6f87b 100644 --- a/packages/database/src/Builder/QueryBuilders/QueryBuilder.php +++ b/packages/database/src/Builder/QueryBuilders/QueryBuilder.php @@ -6,10 +6,10 @@ use Tempest\Database\PrimaryKey; use Tempest\Mapper\SerializerFactory; +use function Tempest\Container\get; use function Tempest\Database\inspect; use function Tempest\Database\query; -use function Tempest\get; -use function Tempest\make; +use function Tempest\Mapper\make; use function Tempest\Support\arr; /** @@ -264,7 +264,10 @@ public function create(mixed ...$params): object if ($id !== null && $primaryKeyProperty !== null) { $primaryKeyName = $primaryKeyProperty->getName(); - $model->{$primaryKeyName} = new PrimaryKey($id); + + if (! $inspector->hasUuidPrimaryKey() || $model->{$primaryKeyName} === null) { + $model->{$primaryKeyName} = new PrimaryKey($id); + } } return $model; diff --git a/packages/database/src/Builder/QueryBuilders/SelectQueryBuilder.php b/packages/database/src/Builder/QueryBuilders/SelectQueryBuilder.php index 459874662e..b348653c55 100644 --- a/packages/database/src/Builder/QueryBuilders/SelectQueryBuilder.php +++ b/packages/database/src/Builder/QueryBuilders/SelectQueryBuilder.php @@ -6,6 +6,8 @@ use Closure; use Tempest\Database\Builder\ModelInspector; +use Tempest\Database\Database; +use Tempest\Database\DatabaseContext; use Tempest\Database\Direction; use Tempest\Database\Exceptions\ModelDidNotHavePrimaryColumn; use Tempest\Database\Mappers\SelectModelMapper; @@ -27,8 +29,9 @@ use Tempest\Support\Paginator\Paginator; use Tempest\Support\Str\ImmutableString; +use function Tempest\Container\get; use function Tempest\Database\inspect; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @template TModel of object @@ -52,6 +55,14 @@ final class SelectQueryBuilder implements BuildsQuery, SupportsWhereStatements, public array $bindings = []; + private Database $database { + get => get(Database::class, $this->onDatabase); + } + + private DatabaseContext $context { + get => new DatabaseContext(dialect: $this->database->dialect); + } + public ImmutableArray $wheres { get => $this->select->where; set => $this->select->where; @@ -87,6 +98,7 @@ public function first(mixed ...$bindings): mixed $result = map($query->fetch()) ->with(SelectModelMapper::class) + ->in($this->context) ->to($this->model->getName()); if ($result === []) { @@ -175,6 +187,7 @@ public function all(mixed ...$bindings): array return map($query->fetch()) ->with(SelectModelMapper::class) + ->in($this->context) ->to($this->model->getName()); } diff --git a/packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php b/packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php index f0841d674d..b0ebd8fd46 100644 --- a/packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php +++ b/packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php @@ -4,6 +4,8 @@ use Tempest\Database\Builder\ModelInspector; use Tempest\Database\Builder\WhereOperator; +use Tempest\Database\Database; +use Tempest\Database\DatabaseContext; use Tempest\Database\Exceptions\CouldNotUpdateRelation; use Tempest\Database\Exceptions\HasManyRelationCouldNotBeUpdated; use Tempest\Database\Exceptions\HasOneRelationCouldNotBeUpdated; @@ -22,8 +24,8 @@ use Tempest\Support\Conditions\HasConditions; use Tempest\Support\Str\ImmutableString; +use function Tempest\Container\get; use function Tempest\Database\inspect; -use function Tempest\get; /** * @template TModel of object @@ -45,6 +47,14 @@ final class UpdateQueryBuilder implements BuildsQuery, SupportsWhereStatements private ?PrimaryKey $primaryKeyForRelations = null; + private Database $database { + get => get(Database::class, $this->onDatabase); + } + + private DatabaseContext $context { + get => new DatabaseContext(dialect: $this->database->dialect); + } + public ImmutableArray $wheres { get => $this->update->where; } @@ -253,7 +263,9 @@ private function resolveRelationValue(PropertyReflector $property, string $colum private function serializeValue(PropertyReflector $property, mixed $value): mixed { - $serializer = $this->serializerFactory->forProperty($property); + $serializer = $this->serializerFactory + ->in($this->context) + ->forProperty($property); if ($value !== null && $serializer !== null) { return $serializer->serialize($value); diff --git a/packages/database/src/Casters/DataTransferObjectCaster.php b/packages/database/src/Casters/DataTransferObjectCaster.php new file mode 100644 index 0000000000..4f9d6dfc5c --- /dev/null +++ b/packages/database/src/Casters/DataTransferObjectCaster.php @@ -0,0 +1,85 @@ +getType() + : $type; + + if ($type->isUnion()) { + foreach ($type->split() as $memberType) { + if (static::accepts($memberType)) { + return true; + } + } + + return false; + } + + return $type->isClass() && $type->asClass()->getAttribute(SerializeAs::class) !== null; + } + + public function cast(mixed $input): mixed + { + if (is_string($input) && Json\is_valid($input)) { + return $this->deserialize(Json\decode($input)); + } + + if (is_array($input)) { + return $this->deserialize($input); + } + + if (is_string($input)) { + throw new ValueCouldNotBeCast('json string'); + } + + return $input; + } + + private function deserialize(mixed $input): mixed + { + if (is_array($input) && isset($input['type'], $input['data'])) { + $class = Arr\find_key( + array: $this->mapperConfig->serializationMap, + value: $input['type'], + ) ?: $input['type']; + + return map($this->deserialize($input['data'])) + ->in($this->context) + ->to($class); + } + + if (is_array($input)) { + return array_map(fn (mixed $value) => $this->deserialize($value), $input); + } + + return $input; + } +} diff --git a/packages/database/src/Casters/PrimaryKeyCaster.php b/packages/database/src/Casters/PrimaryKeyCaster.php index dbf595675d..fa43779143 100644 --- a/packages/database/src/Casters/PrimaryKeyCaster.php +++ b/packages/database/src/Casters/PrimaryKeyCaster.php @@ -4,11 +4,25 @@ namespace Tempest\Database\Casters; +use Tempest\Core\Priority; use Tempest\Database\PrimaryKey; use Tempest\Mapper\Caster; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final readonly class PrimaryKeyCaster implements Caster +#[Priority(Priority::HIGHEST)] +final readonly class PrimaryKeyCaster implements Caster, DynamicCaster { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(PrimaryKey::class); + } + public function cast(mixed $input): PrimaryKey { if ($input instanceof PrimaryKey) { diff --git a/packages/database/src/Commands/MakeMigrationCommand.php b/packages/database/src/Commands/MakeMigrationCommand.php index 30a59be94d..4edf9fc412 100644 --- a/packages/database/src/Commands/MakeMigrationCommand.php +++ b/packages/database/src/Commands/MakeMigrationCommand.php @@ -8,14 +8,15 @@ use Tempest\Console\ConsoleArgument; use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; +use Tempest\Database\Config\DatabaseConfig; use Tempest\Database\Enums\MigrationType; use Tempest\Database\Stubs\ObjectMigrationStub; use Tempest\Database\Stubs\UpMigrationStub; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; -use Tempest\Generation\Exceptions\FileGenerationFailedException; -use Tempest\Generation\Exceptions\FileGenerationWasAborted; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; +use Tempest\Generation\Php\Exceptions\FileGenerationFailedException; +use Tempest\Generation\Php\Exceptions\FileGenerationWasAborted; use Tempest\Support\Str; use Tempest\Validation\Rules\EndsWith; use Tempest\Validation\Rules\IsNotEmptyString; @@ -26,6 +27,10 @@ final class MakeMigrationCommand { use PublishesFiles; + public function __construct( + private DatabaseConfig $databaseConfig, + ) {} + #[ConsoleCommand( name: 'make:migration', description: 'Creates a new migration file', @@ -73,7 +78,7 @@ private function generateRawFile(string $filename, StubFile $stubFile): string $suggestedPath = Str\replace( string: $this->getSuggestedPath('Dummy'), search: ['Dummy', '.php'], - replace: [date('Y-m-d') . '_' . $filename, '.sql'], + replace: [$this->databaseConfig->migrationNamingStrategy->generatePrefix() . '_' . $filename, '.sql'], ); $targetPath = $this->promptTargetPath($suggestedPath, rules: [ @@ -120,7 +125,7 @@ private function generateClassFile(string $filename, StubFile $stubFile): string targetPath: $targetPath, shouldOverride: $this->askForOverride($targetPath), replacements: [ - 'dummy-date' => date('Y-m-d'), + 'dummy-date' => $this->databaseConfig->migrationNamingStrategy->generatePrefix(), 'dummy-table-name' => $tableName, ], manipulations: [ diff --git a/packages/database/src/Commands/MakeModelCommand.php b/packages/database/src/Commands/MakeModelCommand.php index effd3ef4a7..b44f5558f6 100644 --- a/packages/database/src/Commands/MakeModelCommand.php +++ b/packages/database/src/Commands/MakeModelCommand.php @@ -8,7 +8,7 @@ use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; use Tempest\Database\Stubs\DatabaseModelStub; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\DataObjects\StubFile; final class MakeModelCommand { diff --git a/packages/database/src/Config/DatabaseConfig.php b/packages/database/src/Config/DatabaseConfig.php index 0635d2f68e..581a5b878b 100644 --- a/packages/database/src/Config/DatabaseConfig.php +++ b/packages/database/src/Config/DatabaseConfig.php @@ -5,27 +5,64 @@ namespace Tempest\Database\Config; use Tempest\Container\HasTag; +use Tempest\Database\Migrations\MigrationNamingStrategy; use Tempest\Database\Tables\NamingStrategy; interface DatabaseConfig extends HasTag { + /** + * PDO data source name connection string. + */ public string $dsn { get; } + /** + * The naming strategy for database tables and columns. + */ public NamingStrategy $namingStrategy { get; } + /** + * The naming strategy for migration file prefixes. + */ + public MigrationNamingStrategy $migrationNamingStrategy { + get; + } + + /** + * The database dialect (MySQL, PostgreSQL, SQLite). + */ public DatabaseDialect $dialect { get; } + /** + * The database username for authentication. + */ public ?string $username { get; } + /** + * The database password for authentication. + */ public ?string $password { get; } + + /** + * Whether to use persistent database connections. + */ + public bool $usePersistentConnection { + get; + } + + /** + * PDO connection options built from configuration properties. + */ + public array $options { + get; + } } diff --git a/packages/database/src/Config/MysqlConfig.php b/packages/database/src/Config/MysqlConfig.php index cdde854f64..e146df0343 100644 --- a/packages/database/src/Config/MysqlConfig.php +++ b/packages/database/src/Config/MysqlConfig.php @@ -4,7 +4,11 @@ namespace Tempest\Database\Config; +use PDO; +use Pdo\Mysql; use SensitiveParameter; +use Tempest\Database\Migrations\DatePrefixStrategy; +use Tempest\Database\Migrations\MigrationNamingStrategy; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; use UnitEnum; @@ -24,6 +28,57 @@ final class MysqlConfig implements DatabaseConfig get => DatabaseDialect::MYSQL; } + public bool $usePersistentConnection { + get => $this->persistent; + } + + public MigrationNamingStrategy $migrationNamingStrategy { + get => $this->migrationNaming; + } + + public array $options { + get { + $options = []; + + if ($this->persistent) { + $options[PDO::ATTR_PERSISTENT] = true; + } + + if ($this->certificateAuthority !== null) { + $options[Mysql::ATTR_SSL_CA] = $this->certificateAuthority; + } + + if ($this->verifyServerCertificate !== null) { + $options[Mysql::ATTR_SSL_VERIFY_SERVER_CERT] = $this->verifyServerCertificate; + } + + if ($this->clientCertificate !== null) { + $options[Mysql::ATTR_SSL_CERT] = $this->clientCertificate; + } + + if ($this->clientKey !== null) { + $options[Mysql::ATTR_SSL_KEY] = $this->clientKey; + } + + return $options; + } + } + + /** + * @param string $host The MySQL server hostname or IP address. + * @param string $port The MySQL server port number. + * @param string $username The MySQL username for authentication. + * @param string $password The MySQL password for authentication. + * @param string $database The database name to connect to. + * @param bool $persistent Whether to use persistent connections. Persistent connections are not closed at the end of the script and are cached for reuse when another script requests a connection using the same credentials. + * @param bool|null $verifyServerCertificate Whether to verify the server's SSL certificate. Set to false for self-signed certificates (not recommended for production). + * @param string|null $certificateAuthority Path to the SSL Certificate Authority (CA) file. Required for SSL/TLS connections to verify the server's certificate. + * @param string|null $clientCertificate Path to the client's SSL certificate file. Used for mutual TLS authentication. + * @param string|null $clientKey Path to the client's SSL private key file. Used for mutual TLS authentication. + * @param NamingStrategy $namingStrategy The naming strategy for database tables and columns. + * @param MigrationNamingStrategy $migrationNaming The naming strategy for migration file prefixes. + * @param string|UnitEnum|null $tag An optional tag to identify this database configuration. + */ public function __construct( #[SensitiveParameter] public string $host = 'localhost', @@ -35,7 +90,13 @@ public function __construct( public string $password = '', #[SensitiveParameter] public string $database = 'app', + public bool $persistent = false, + public ?bool $verifyServerCertificate = null, + public ?string $certificateAuthority = null, + public ?string $clientCertificate = null, + public ?string $clientKey = null, public NamingStrategy $namingStrategy = new PluralizedSnakeCaseStrategy(), + public MigrationNamingStrategy $migrationNaming = new DatePrefixStrategy(), public null|string|UnitEnum $tag = null, ) {} } diff --git a/packages/database/src/Config/PostgresConfig.php b/packages/database/src/Config/PostgresConfig.php index 13b447f4c8..173b1475f9 100644 --- a/packages/database/src/Config/PostgresConfig.php +++ b/packages/database/src/Config/PostgresConfig.php @@ -4,7 +4,10 @@ namespace Tempest\Database\Config; +use PDO; use SensitiveParameter; +use Tempest\Database\Migrations\DatePrefixStrategy; +use Tempest\Database\Migrations\MigrationNamingStrategy; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; use UnitEnum; @@ -26,6 +29,37 @@ final class PostgresConfig implements DatabaseConfig get => DatabaseDialect::POSTGRESQL; } + public bool $usePersistentConnection { + get => $this->persistent; + } + + public MigrationNamingStrategy $migrationNamingStrategy { + get => $this->migrationNaming; + } + + public array $options { + get { + $options = []; + + if ($this->persistent) { + $options[PDO::ATTR_PERSISTENT] = true; + } + + return $options; + } + } + + /** + * @param string $host The PostgreSQL server hostname or IP address. + * @param string $port The PostgreSQL server port number. + * @param string $username The PostgreSQL username for authentication. + * @param string $password The PostgreSQL password for authentication. + * @param string $database The database name to connect to. + * @param bool $persistent Whether to use persistent connections. Persistent connections are not closed at the end of the script and are cached for reuse when another script requests a connection using the same credentials. + * @param NamingStrategy $namingStrategy The naming strategy for database tables and columns. + * @param MigrationNamingStrategy $migrationNaming The naming strategy for migration file prefixes. + * @param string|UnitEnum|null $tag An optional tag to identify this database configuration. + */ public function __construct( #[SensitiveParameter] public string $host = '127.0.0.1', @@ -37,7 +71,9 @@ public function __construct( public string $password = '', #[SensitiveParameter] public string $database = 'app', + public bool $persistent = false, public NamingStrategy $namingStrategy = new PluralizedSnakeCaseStrategy(), + public MigrationNamingStrategy $migrationNaming = new DatePrefixStrategy(), public null|string|UnitEnum $tag = null, ) {} } diff --git a/packages/database/src/Config/SQLiteConfig.php b/packages/database/src/Config/SQLiteConfig.php index 155373b2cf..6b6cd72efd 100644 --- a/packages/database/src/Config/SQLiteConfig.php +++ b/packages/database/src/Config/SQLiteConfig.php @@ -4,7 +4,10 @@ namespace Tempest\Database\Config; +use PDO; use SensitiveParameter; +use Tempest\Database\Migrations\DatePrefixStrategy; +use Tempest\Database\Migrations\MigrationNamingStrategy; use Tempest\Database\Tables\NamingStrategy; use Tempest\Database\Tables\PluralizedSnakeCaseStrategy; use UnitEnum; @@ -30,10 +33,39 @@ final class SQLiteConfig implements DatabaseConfig get => DatabaseDialect::SQLITE; } + public bool $usePersistentConnection { + get => $this->persistent; + } + + public MigrationNamingStrategy $migrationNamingStrategy { + get => $this->migrationNaming; + } + + public array $options { + get { + $options = []; + + if ($this->persistent) { + $options[PDO::ATTR_PERSISTENT] = true; + } + + return $options; + } + } + + /** + * @param string $path Path to the SQLite database file. Use ':memory:' for an in-memory database. + * @param bool $persistent Whether to use persistent connections. Persistent connections are not closed at the end of the script and are cached for reuse when another script requests a connection using the same credentials. + * @param NamingStrategy $namingStrategy The naming strategy for database tables and columns. + * @param MigrationNamingStrategy $migrationNaming The naming strategy for migration file prefixes. + * @param string|UnitEnum|null $tag An optional tag to identify this database configuration. + */ public function __construct( #[SensitiveParameter] public string $path = 'localhost', + public bool $persistent = false, public NamingStrategy $namingStrategy = new PluralizedSnakeCaseStrategy(), + public MigrationNamingStrategy $migrationNaming = new DatePrefixStrategy(), public null|string|UnitEnum $tag = null, ) {} } diff --git a/packages/database/src/Connection/PDOConnection.php b/packages/database/src/Connection/PDOConnection.php index a4b9bf6963..1b3e6ae98e 100644 --- a/packages/database/src/Connection/PDOConnection.php +++ b/packages/database/src/Connection/PDOConnection.php @@ -102,6 +102,7 @@ public function connect(): void dsn: $this->config->dsn, username: $this->config->username, password: $this->config->password, + options: $this->config->options, ); } } diff --git a/packages/database/src/Database.php b/packages/database/src/Database.php index 4118951e66..6f12cf132c 100644 --- a/packages/database/src/Database.php +++ b/packages/database/src/Database.php @@ -6,25 +6,55 @@ use Tempest\Database\Builder\QueryBuilders\BuildsQuery; use Tempest\Database\Config\DatabaseDialect; +use Tempest\Support\Str\ImmutableString; use UnitEnum; +/** + * Represents a database that can execute queries. + */ interface Database { + /** + * The dialect of this database. + */ public DatabaseDialect $dialect { get; } + /** + * The tag associated with this database, if any. + */ public null|string|UnitEnum $tag { get; } + /** + * Executes the given query. + */ public function execute(BuildsQuery|Query $query): void; + /** + * Returns the last inserted primary key, if any. + */ public function getLastInsertId(): ?PrimaryKey; + /** + * Fetches all results for the given query. + */ public function fetch(BuildsQuery|Query $query): array; + /** + * Fetches the first result for the given query. + */ public function fetchFirst(BuildsQuery|Query $query): ?array; + /** + * Executes the given callback within a transaction. + */ public function withinTransaction(callable $callback): bool; + + /** + * Returns the raw SQL representation of the given query for debugging purposes. + */ + public function getRawSql(Query $query): ImmutableString; } diff --git a/packages/database/src/DatabaseContext.php b/packages/database/src/DatabaseContext.php new file mode 100644 index 0000000000..c92b1514ec --- /dev/null +++ b/packages/database/src/DatabaseContext.php @@ -0,0 +1,15 @@ +get(Connection::class, $tag); return new GenericDatabase( - $connection, - new GenericTransactionManager($connection), - $container->get(SerializerFactory::class), + connection: $connection, + transactionManager: new GenericTransactionManager($connection), + serializerFactory: $container->get(SerializerFactory::class), ); } } diff --git a/packages/database/src/Exceptions/QueryWasInvalid.php b/packages/database/src/Exceptions/QueryWasInvalid.php index f6745fe328..5afe13807f 100644 --- a/packages/database/src/Exceptions/QueryWasInvalid.php +++ b/packages/database/src/Exceptions/QueryWasInvalid.php @@ -6,10 +6,10 @@ use Exception; use PDOException; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; use Tempest\Database\Query; -final class QueryWasInvalid extends Exception implements HasContext +final class QueryWasInvalid extends Exception implements ProvidesContext { public readonly PDOException $pdoException; diff --git a/packages/database/src/GenericDatabase.php b/packages/database/src/GenericDatabase.php index 4843e782b2..e91004e9c4 100644 --- a/packages/database/src/GenericDatabase.php +++ b/packages/database/src/GenericDatabase.php @@ -13,6 +13,7 @@ use Tempest\Database\Exceptions\QueryWasInvalid; use Tempest\Database\Transactions\TransactionManager; use Tempest\Mapper\SerializerFactory; +use Tempest\Support\Str\ImmutableString; use Throwable; use UnitEnum; @@ -29,6 +30,10 @@ final class GenericDatabase implements Database get => $this->connection->config->tag; } + private DatabaseContext $context { + get => new DatabaseContext(dialect: $this->dialect); + } + public function __construct( private(set) readonly Connection $connection, private(set) readonly TransactionManager $transactionManager, @@ -123,22 +128,28 @@ public function withinTransaction(callable $callback): bool return true; } + public function getRawSql(Query $query): ImmutableString + { + return new RawSql( + dialect: $this->dialect, + sql: (string) $query->compile(), + bindings: $query->bindings, + serializerFactory: $this->serializerFactory, + )->toImmutableString(); + } + private function resolveBindings(Query $query): array { $bindings = []; - foreach ($query->bindings as $key => $value) { - // Database handle booleans differently. We might need a database-aware serializer at some point. - if (is_bool($value)) { - $value = match ($this->dialect) { - DatabaseDialect::POSTGRESQL => $value ? 'true' : 'false', - default => $value ? '1' : '0', - }; - } + $serializerFactory = $this->serializerFactory->in($this->context); + foreach ($query->bindings as $key => $value) { if ($value instanceof Query) { $value = $value->execute(); - } elseif ($serializer = $this->serializerFactory->forValue($value)) { + } elseif (is_string($value) || is_numeric($value)) { + // Keep value as is + } elseif ($serializer = $serializerFactory->forValue($value)) { $value = $serializer->serialize($value); } diff --git a/packages/database/src/IsDatabaseModel.php b/packages/database/src/IsDatabaseModel.php index 5ad80830a0..e0f82e984f 100644 --- a/packages/database/src/IsDatabaseModel.php +++ b/packages/database/src/IsDatabaseModel.php @@ -248,7 +248,9 @@ public function save(): self ->insert($this) ->execute(); - $primaryKeyProperty->setValue($this, $id); + if (! $model->hasUuidPrimaryKey()) { + $primaryKeyProperty->setValue($this, $id); + } return $this; } diff --git a/packages/database/src/Mappers/SelectModelMapper.php b/packages/database/src/Mappers/SelectModelMapper.php index 6b21ddeab5..12d8477d95 100644 --- a/packages/database/src/Mappers/SelectModelMapper.php +++ b/packages/database/src/Mappers/SelectModelMapper.php @@ -7,16 +7,21 @@ use Tempest\Database\HasMany; use Tempest\Database\HasOne; use Tempest\Discovery\SkipDiscovery; +use Tempest\Mapper\Context; use Tempest\Mapper\Mapper; use Tempest\Support\Arr\MutableArray; use function Tempest\Database\inspect; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\arr; #[SkipDiscovery] final class SelectModelMapper implements Mapper { + public function __construct( + private Context $context, + ) {} + public function canMap(mixed $from, mixed $to): bool { return false; @@ -33,7 +38,10 @@ public function map(mixed $from, mixed $to): array ->map(fn (array $rows) => $this->normalizeFields($model, $rows)) ->values(); - $objects = map($parsed->toArray())->collection()->to($to); + $objects = map($parsed->toArray()) + ->in($this->context) + ->collection() + ->to($to); foreach ($objects as $i => $object) { foreach ($model->getRelations() as $relation) { diff --git a/packages/database/src/Migrations/DatePrefixStrategy.php b/packages/database/src/Migrations/DatePrefixStrategy.php new file mode 100644 index 0000000000..b0afc7ce07 --- /dev/null +++ b/packages/database/src/Migrations/DatePrefixStrategy.php @@ -0,0 +1,16 @@ +dialect, (string) $this->compile(), $this->bindings)->toImmutableString(); + return $this->database->getRawSql($this); } public function append(string $append): self diff --git a/packages/database/src/QueryStatements/CreateTableStatement.php b/packages/database/src/QueryStatements/CreateTableStatement.php index 169bfb7843..ab3bba8d73 100644 --- a/packages/database/src/QueryStatements/CreateTableStatement.php +++ b/packages/database/src/QueryStatements/CreateTableStatement.php @@ -33,11 +33,28 @@ public static function forModel(string $modelClass): self } /** - * Adds a primary key column to the table. MySQL and SQLite use an auto-incrementing `INTEGER` column, and PostgreSQL uses `SERIAL`. + * Adds a primary key column to the table. + * + * By default, MySQL and SQLite use an auto-incrementing `INTEGER` column, and PostgreSQL uses `SERIAL`. + * When setting `uuid` to `true`, MySQL will use `VARCHAR(36)`, PostgreSQL will use `UUID`, and SQLite will use `TEXT`. + */ + public function primary(string $name = 'id', bool $uuid = false): self + { + if ($uuid) { + $this->uuid($name); + } else { + $this->statements[] = new PrimaryKeyStatement($name); + } + + return $this; + } + + /** + * Adds a UUID v7 primary key column to the table. Uses `VARCHAR(36)` for MySQL, `UUID` for PostgreSQL, and `TEXT` for SQLite. */ - public function primary(string $name = 'id'): self + public function uuid(string $name = 'id'): self { - $this->statements[] = new PrimaryKeyStatement($name); + $this->statements[] = new UuidPrimaryKeyStatement($name); return $this; } diff --git a/packages/database/src/QueryStatements/UuidPrimaryKeyStatement.php b/packages/database/src/QueryStatements/UuidPrimaryKeyStatement.php new file mode 100644 index 0000000000..59245e8203 --- /dev/null +++ b/packages/database/src/QueryStatements/UuidPrimaryKeyStatement.php @@ -0,0 +1,24 @@ + sprintf('`%s` VARCHAR(36) PRIMARY KEY', $this->name), + DatabaseDialect::POSTGRESQL => sprintf('`%s` UUID PRIMARY KEY', $this->name), + DatabaseDialect::SQLITE => sprintf('`%s` TEXT PRIMARY KEY', $this->name), + }; + } +} diff --git a/packages/database/src/RawSql.php b/packages/database/src/RawSql.php index 45335bb0ef..2f8023061d 100644 --- a/packages/database/src/RawSql.php +++ b/packages/database/src/RawSql.php @@ -2,17 +2,22 @@ namespace Tempest\Database; -use BackedEnum; use Tempest\Database\Config\DatabaseDialect; +use Tempest\Mapper\SerializerFactory; +use Tempest\Support\Str; use Tempest\Support\Str\ImmutableString; -use UnitEnum; final class RawSql { + private ?RawSqlDatabaseContext $context { + get => $this->context ??= new RawSqlDatabaseContext($this->dialect); + } + public function __construct( private(set) DatabaseDialect $dialect, private(set) string $sql, private(set) array $bindings, + private SerializerFactory $serializerFactory, ) {} public function compile(): string @@ -39,9 +44,11 @@ public function __toString(): string private function replaceNamedBindings(string $sql, array $bindings): string { foreach ($bindings as $key => $value) { - $placeholder = ':' . $key; - $formattedValue = $this->formatValueForSql($value); - $sql = str_replace($placeholder, $formattedValue, $sql); + $sql = str_replace( + search: ':' . $key, + replace: $this->formatValueForSql($value), + subject: $sql, + ); } return $sql; @@ -71,15 +78,14 @@ private function resolveBindingsForDisplay(): array $bindings = []; foreach ($this->bindings as $key => $value) { - if (is_bool($value)) { - $value = match ($this->dialect) { - DatabaseDialect::POSTGRESQL => $value ? 'true' : 'false', - default => $value ? '1' : '0', - }; + if ($value instanceof Query) { + $bindings[$key] = "({$value->toRawSql()})"; + continue; } - if ($value instanceof Query) { - $value = '(' . $value->toRawSql() . ')'; + if ($serializer = $this->serializerFactory->in($this->context)->forValue($value)) { + $bindings[$key] = $serializer->serialize($value); + continue; } $bindings[$key] = $value; @@ -94,26 +100,6 @@ private function formatValueForSql(mixed $value): string return 'NULL'; } - if (is_string($value)) { - if (str_starts_with($value, '(') && str_ends_with($value, ')')) { - return $value; - } - - return "'" . str_replace("'", "''", $value) . "'"; - } - - if (is_numeric($value)) { - return (string) $value; - } - - if ($value instanceof BackedEnum) { - return $value->value; - } - - if ($value instanceof UnitEnum) { - return $value->name; - } - - return "'" . str_replace("'", "''", (string) $value) . "'"; + return Str\parse($value); } } diff --git a/packages/database/src/RawSqlDatabaseContext.php b/packages/database/src/RawSqlDatabaseContext.php new file mode 100644 index 0000000000..3e6ec3404d --- /dev/null +++ b/packages/database/src/RawSqlDatabaseContext.php @@ -0,0 +1,15 @@ +getType() + : $type; + + return $type->getName() === 'bool' || $type->getName() === 'boolean'; + } + + public function serialize(mixed $input): string + { + if (! is_bool($input)) { + throw new ValueCouldNotBeSerialized('boolean'); + } + + return match ($this->context->dialect) { + DatabaseDialect::POSTGRESQL => $input ? 'true' : 'false', + default => $input ? '1' : '0', + }; + } +} diff --git a/packages/mapper/src/Serializers/DtoSerializer.php b/packages/database/src/Serializers/DataTransferObjectSerializer.php similarity index 65% rename from packages/mapper/src/Serializers/DtoSerializer.php rename to packages/database/src/Serializers/DataTransferObjectSerializer.php index 6d44bafb7a..60162dfcfe 100644 --- a/packages/mapper/src/Serializers/DtoSerializer.php +++ b/packages/database/src/Serializers/DataTransferObjectSerializer.php @@ -1,27 +1,53 @@ getType() + : $type; + + if ($type->isUnion()) { + foreach ($type->split() as $memberType) { + if (static::accepts($memberType)) { + return true; + } + } + + return false; + } + + return $type->isClass() && $type->asClass()->getAttribute(SerializeAs::class) !== null; + } + public function serialize(mixed $input): array|string { - // Support top-level arrays if (is_array($input)) { return Json\encode($this->serializeWithType($input)); } @@ -59,7 +85,7 @@ private function serializeWithType(mixed $input): mixed } if (is_array($input)) { - return Arr\map_iterable($input, $this->serializeWithType(...)); + return Arr\map($input, $this->serializeWithType(...)); } return $input; diff --git a/packages/database/src/Serializers/DateTimeSerializer.php b/packages/database/src/Serializers/DateTimeSerializer.php new file mode 100644 index 0000000000..ddc9cd774e --- /dev/null +++ b/packages/database/src/Serializers/DateTimeSerializer.php @@ -0,0 +1,45 @@ +getType() + : $type; + + return $type->matches(DateTime::class) || $type->matches(DateTimeInterface::class) || $type->matches(NativeDateTimeInterface::class); + } + + public function serialize(mixed $input): string + { + if ($input instanceof NativeDateTimeInterface) { + $input = DateTime::parse($input); + } + + if (! $input instanceof DateTimeInterface) { + throw new ValueCouldNotBeSerialized(DateTimeInterface::class); + } + + return $input->format(FormatPattern::SQL_DATE_TIME); + } +} diff --git a/packages/database/src/Serializers/PrimaryKeySerializer.php b/packages/database/src/Serializers/PrimaryKeySerializer.php index 120b2f946c..ce071a33ba 100644 --- a/packages/database/src/Serializers/PrimaryKeySerializer.php +++ b/packages/database/src/Serializers/PrimaryKeySerializer.php @@ -3,12 +3,24 @@ namespace Tempest\Database\Serializers; use Tempest\Database\PrimaryKey; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class PrimaryKeySerializer implements Serializer +final class PrimaryKeySerializer implements Serializer, DynamicSerializer { - public function serialize(mixed $input): array|string + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(PrimaryKey::class); + } + + public function serialize(mixed $input): string|int { if (! $input instanceof PrimaryKey) { throw new ValueCouldNotBeSerialized(PrimaryKey::class); diff --git a/packages/database/src/Serializers/PrimaryKeySerializerProvider.php b/packages/database/src/Serializers/PrimaryKeySerializerProvider.php deleted file mode 100644 index a7b950cb21..0000000000 --- a/packages/database/src/Serializers/PrimaryKeySerializerProvider.php +++ /dev/null @@ -1,21 +0,0 @@ -serializerFactory->addSerializer(PrimaryKey::class, PrimaryKeySerializer::class); - } -} diff --git a/packages/database/src/Serializers/RawSqlBooleanSerializer.php b/packages/database/src/Serializers/RawSqlBooleanSerializer.php new file mode 100644 index 0000000000..c51c3a3650 --- /dev/null +++ b/packages/database/src/Serializers/RawSqlBooleanSerializer.php @@ -0,0 +1,43 @@ +getType() + : $type; + + return $type->getName() === 'bool' || $type->getName() === 'boolean'; + } + + public function serialize(mixed $input): string + { + if (! is_bool($input)) { + throw new ValueCouldNotBeSerialized('boolean'); + } + + return match ($this->context->dialect) { + DatabaseDialect::POSTGRESQL => $input ? 'true' : 'false', + default => $input ? '1' : '0', + }; + } +} diff --git a/packages/database/src/Serializers/RawSqlDateTimeSerializer.php b/packages/database/src/Serializers/RawSqlDateTimeSerializer.php new file mode 100644 index 0000000000..094351268f --- /dev/null +++ b/packages/database/src/Serializers/RawSqlDateTimeSerializer.php @@ -0,0 +1,45 @@ +getType() + : $type; + + return $type->matches(DateTime::class) || $type->matches(DateTimeInterface::class) || $type->matches(NativeDateTimeInterface::class); + } + + public function serialize(mixed $input): string + { + if ($input instanceof NativeDateTimeInterface) { + $input = DateTime::parse($input); + } + + if (! $input instanceof DateTimeInterface) { + throw new ValueCouldNotBeSerialized(DateTimeInterface::class); + } + + return "'" . $input->format(FormatPattern::SQL_DATE_TIME) . "'"; + } +} diff --git a/packages/database/src/Serializers/RawSqlEnumSerializer.php b/packages/database/src/Serializers/RawSqlEnumSerializer.php new file mode 100644 index 0000000000..9c5ed9677c --- /dev/null +++ b/packages/database/src/Serializers/RawSqlEnumSerializer.php @@ -0,0 +1,43 @@ +getType() + : $input; + + return $type->matches(UnitEnum::class); + } + + public function serialize(mixed $input): string + { + if ($input instanceof BackedEnum) { + return sprintf('"%s"', $input->value); + } + + if ($input instanceof UnitEnum) { + return sprintf('"%s"', $input->name); + } + + throw new ValueCouldNotBeSerialized('enum'); + } +} diff --git a/packages/database/src/Serializers/RawSqlNumberSerializer.php b/packages/database/src/Serializers/RawSqlNumberSerializer.php new file mode 100644 index 0000000000..523d3756d1 --- /dev/null +++ b/packages/database/src/Serializers/RawSqlNumberSerializer.php @@ -0,0 +1,37 @@ +getType() + : $input; + + return in_array($type->getName(), ['int', 'integer', 'float', 'double'], strict: true); + } + + public function serialize(mixed $input): string + { + if (! is_int($input) && ! is_float($input)) { + throw new ValueCouldNotBeSerialized('integer or float'); + } + + return (string) $input; + } +} diff --git a/packages/database/src/Serializers/RawSqlStringSerializer.php b/packages/database/src/Serializers/RawSqlStringSerializer.php new file mode 100644 index 0000000000..42d2c44c95 --- /dev/null +++ b/packages/database/src/Serializers/RawSqlStringSerializer.php @@ -0,0 +1,38 @@ +getType() + : $input; + + return $type->getName() === 'string' || $type->matches(Stringable::class); + } + + public function serialize(mixed $input): string + { + if (! is_string($input) && ! $input instanceof Stringable) { + throw new ValueCouldNotBeSerialized('string'); + } + + return "'" . str_replace("'", "''", (string) $input) . "'"; + } +} diff --git a/packages/database/src/Uuid.php b/packages/database/src/Uuid.php new file mode 100644 index 0000000000..e93d7a9233 --- /dev/null +++ b/packages/database/src/Uuid.php @@ -0,0 +1,29 @@ +|string|TModel $model - * @return QueryBuilder - */ - function query(string|object $model): QueryBuilder - { - return new QueryBuilder($model); - } +use Tempest\Database\Builder\ModelInspector; +use Tempest\Database\Builder\QueryBuilders\QueryBuilder; - /** - * Inspects the given model or table name to provide database insights. - * - * @template TModel of object - * @param class-string|string|TModel $model - * @return ModelInspector - * @internal - */ - function inspect(string|object $model): ModelInspector - { - return new ModelInspector($model); - } +/** + * Creates a new query builder instance for the given model or table name. + * + * @template TModel of object + * @param class-string|string|TModel $model + * @return QueryBuilder + */ +function query(string|object $model): QueryBuilder +{ + return new QueryBuilder($model); +} + +/** + * Inspects the given model or table name to provide database insights. + * + * @template TModel of object + * @param class-string|string|TModel $model + * @return ModelInspector + * @internal + */ +function inspect(string|object $model): ModelInspector +{ + return ModelInspector::forModel($model); } diff --git a/packages/database/tests/Config/DatabaseConfigTest.php b/packages/database/tests/Config/DatabaseConfigTest.php index 04951017b4..02df0a7223 100644 --- a/packages/database/tests/Config/DatabaseConfigTest.php +++ b/packages/database/tests/Config/DatabaseConfigTest.php @@ -5,6 +5,8 @@ namespace Tempest\Database\Tests\Config; use Generator; +use PDO; +use Pdo\Mysql; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -62,4 +64,58 @@ public static function provide_database_drivers(): Generator 'secret', ]; } + + #[DataProvider('provide_database_drivers_with_options')] + #[Test] + public function driver_supports_pdo_options(DatabaseConfig $driver, array $expectedOptions): void + { + $this->assertSame($expectedOptions, $driver->options); + } + + public static function provide_database_drivers_with_options(): Generator + { + yield 'mysql with SSL' => [ + new MysqlConfig( + certificateAuthority: '/etc/ssl/certs/ca-certificates.crt', + persistent: true, + ), + [ + PDO::ATTR_PERSISTENT => true, + Mysql::ATTR_SSL_CA => '/etc/ssl/certs/ca-certificates.crt', + ], + ]; + + yield 'mysql with all SSL options' => [ + new MysqlConfig( + certificateAuthority: '/etc/ssl/certs/ca-certificates.crt', + verifyServerCertificate: false, + clientCertificate: '/path/to/cert.pem', + clientKey: '/path/to/key.pem', + ), + [ + Mysql::ATTR_SSL_CA => '/etc/ssl/certs/ca-certificates.crt', + Mysql::ATTR_SSL_VERIFY_SERVER_CERT => false, + Mysql::ATTR_SSL_CERT => '/path/to/cert.pem', + Mysql::ATTR_SSL_KEY => '/path/to/key.pem', + ], + ]; + + yield 'postgresql with persistent' => [ + new PostgresConfig( + persistent: true, + ), + [ + PDO::ATTR_PERSISTENT => true, + ], + ]; + + yield 'sqlite with persistent' => [ + new SQLiteConfig( + persistent: true, + ), + [ + PDO::ATTR_PERSISTENT => true, + ], + ]; + } } diff --git a/packages/database/tests/GenericDatabaseTest.php b/packages/database/tests/GenericDatabaseTest.php index a7f3b91381..ead08fdd67 100644 --- a/packages/database/tests/GenericDatabaseTest.php +++ b/packages/database/tests/GenericDatabaseTest.php @@ -6,6 +6,7 @@ use Exception; use PHPUnit\Framework\TestCase; +use Tempest\Container\GenericContainer; use Tempest\Database\Connection\Connection; use Tempest\Database\GenericDatabase; use Tempest\Database\Transactions\GenericTransactionManager; @@ -33,7 +34,7 @@ public function test_it_executes_transactions(): void $database = new GenericDatabase( $connection, new GenericTransactionManager($connection), - new SerializerFactory(), + new SerializerFactory(new GenericContainer()), ); $result = $database->withinTransaction(function () { @@ -60,7 +61,7 @@ public function test_it_rolls_back_transactions_on_failure(): void $database = new GenericDatabase( $connection, new GenericTransactionManager($connection), - new SerializerFactory(), + new SerializerFactory(new GenericContainer()), ); $result = $database->withinTransaction(function (): never { diff --git a/packages/database/tests/QueryStatements/CreateTableStatementTest.php b/packages/database/tests/QueryStatements/CreateTableStatementTest.php index 37256c8ad2..2d68cefe94 100644 --- a/packages/database/tests/QueryStatements/CreateTableStatementTest.php +++ b/packages/database/tests/QueryStatements/CreateTableStatementTest.php @@ -178,4 +178,59 @@ public static function provide_fk_create_table_database_drivers_explicit(): Gene SQL, ]; } + + #[DataProvider('provide_uuid_primary_database_dialects')] + public function test_create_table_with_uuid_primary(DatabaseDialect $dialect, string $validSql): void + { + $uuid = new CreateTableStatement('users') + ->uuid('uuid') + ->text('name') + ->text('email') + ->compile($dialect); + + $primary = new CreateTableStatement('users') + ->primary('uuid', uuid: true) + ->text('name') + ->text('email') + ->compile($dialect); + + $this->assertSame($validSql, $uuid); + $this->assertSame($validSql, $primary); + } + + public static function provide_uuid_primary_database_dialects(): iterable + { + yield 'mysql' => [ + DatabaseDialect::MYSQL, + << [ + DatabaseDialect::POSTGRESQL, + << [ + DatabaseDialect::SQLITE, + <<compile(DatabaseDialect::MYSQL); + + $this->assertSame('`uuid` VARCHAR(36) PRIMARY KEY', $compiled); + } + + #[Test] + public function postgresql_compilation(): void + { + $statement = new UuidPrimaryKeyStatement('uuid'); + $compiled = $statement->compile(DatabaseDialect::POSTGRESQL); + + $this->assertSame('`uuid` UUID PRIMARY KEY', $compiled); + } + + #[Test] + public function sqlite_compilation(): void + { + $statement = new UuidPrimaryKeyStatement('uuid'); + $compiled = $statement->compile(DatabaseDialect::SQLITE); + + $this->assertSame('`uuid` TEXT PRIMARY KEY', $compiled); + } + + #[Test] + public function default_column_name(): void + { + $statement = new UuidPrimaryKeyStatement(); + $compiled = $statement->compile(DatabaseDialect::MYSQL); + + $this->assertSame('`id` VARCHAR(36) PRIMARY KEY', $compiled); + } +} diff --git a/packages/database/tests/Serializers/BooleanSerializerTest.php b/packages/database/tests/Serializers/BooleanSerializerTest.php new file mode 100644 index 0000000000..1dcf82ea15 --- /dev/null +++ b/packages/database/tests/Serializers/BooleanSerializerTest.php @@ -0,0 +1,27 @@ +assertSame($expected, $serializer->serialize($input)); + } +} diff --git a/packages/database/tests/Serializers/PrimaryKeySerializerTest.php b/packages/database/tests/Serializers/PrimaryKeySerializerTest.php new file mode 100644 index 0000000000..ce676b1138 --- /dev/null +++ b/packages/database/tests/Serializers/PrimaryKeySerializerTest.php @@ -0,0 +1,30 @@ +assertSame('foo', $serializer->serialize(new PrimaryKey('foo'))); + $this->assertSame(123, $serializer->serialize(new PrimaryKey(123))); + } + + #[Test] + public function throws_if_not_pk(): void + { + $this->expectException(ValueCouldNotBeSerialized::class); + + $serializer = new PrimaryKeySerializer(); + $serializer->serialize('not-a-pk'); + } +} diff --git a/packages/datetime/composer.json b/packages/datetime/composer.json index 56661c1111..fe6751a1eb 100644 --- a/packages/datetime/composer.json +++ b/packages/datetime/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/intl": "dev-main", "tempest/support": "dev-main" }, diff --git a/packages/datetime/src/functions.php b/packages/datetime/src/functions.php index 8d97238d01..0c4a9fc044 100644 --- a/packages/datetime/src/functions.php +++ b/packages/datetime/src/functions.php @@ -1,293 +1,293 @@ getSeconds(); + $nanoseconds = $timestamp->getNanoseconds(); - $seconds = $timestamp->getSeconds(); - $nanoseconds = $timestamp->getNanoseconds(); + // Intl formatter cannot handle nanoseconds and microseconds, do it manually instead. + $fraction = substr((string) $nanoseconds, 0, $secondsStyle->value); - // Intl formatter cannot handle nanoseconds and microseconds, do it manually instead. - $fraction = substr((string) $nanoseconds, 0, $secondsStyle->value); + if ($fraction !== '') { + $fraction = '.' . $fraction; + } - if ($fraction !== '') { - $fraction = '.' . $fraction; - } + $pattern = match ($useZ) { + true => "yyyy-MM-dd'T'HH:mm:ss@ZZZZZ", + false => "yyyy-MM-dd'T'HH:mm:ss@xxx", + }; - $pattern = match ($useZ) { - true => "yyyy-MM-dd'T'HH:mm:ss@ZZZZZ", - false => "yyyy-MM-dd'T'HH:mm:ss@xxx", - }; + $formatter = namespace\create_intl_date_formatter( + pattern: $pattern, + timezone: $timezone, + ); - $formatter = namespace\create_intl_date_formatter( - pattern: $pattern, - timezone: $timezone, - ); + $rfcString = $formatter->format($seconds); - $rfcString = $formatter->format($seconds); + return str_replace('@', $fraction, $rfcString); +} - return str_replace('@', $fraction, $rfcString); +/** + * @internal + */ +function create_intl_date_formatter( + ?DateStyle $dateStyle = null, + ?TimeStyle $timeStyle = null, + null|FormatPattern|string $pattern = null, + ?Timezone $timezone = null, + ?Locale $locale = null, +): IntlDateFormatter { + if ($pattern instanceof FormatPattern) { + $pattern = $pattern->value; } + $dateStyle ??= DateStyle::default(); + $timeStyle ??= TimeStyle::default(); + $locale ??= current_locale(); + $timezone ??= Timezone::default(); + + return new IntlDateFormatter( + locale: $locale->value, + dateType: match ($dateStyle) { + DateStyle::NONE => IntlDateFormatter::NONE, + DateStyle::SHORT => IntlDateFormatter::SHORT, + DateStyle::MEDIUM => IntlDateFormatter::MEDIUM, + DateStyle::LONG => IntlDateFormatter::LONG, + DateStyle::FULL => IntlDateFormatter::FULL, + DateStyle::RELATIVE_SHORT => IntlDateFormatter::RELATIVE_SHORT, + DateStyle::RELATIVE_MEDIUM => IntlDateFormatter::RELATIVE_MEDIUM, + DateStyle::RELATIVE_LONG => IntlDateFormatter::RELATIVE_LONG, + DateStyle::RELATIVE_FULL => IntlDateFormatter::RELATIVE_FULL, + }, + timeType: match ($timeStyle) { + TimeStyle::NONE => IntlDateFormatter::NONE, + TimeStyle::SHORT => IntlDateFormatter::SHORT, + TimeStyle::MEDIUM => IntlDateFormatter::MEDIUM, + TimeStyle::LONG => IntlDateFormatter::LONG, + TimeStyle::FULL => IntlDateFormatter::FULL, + }, + timezone: namespace\to_intl_timezone($timezone), + calendar: IntlDateFormatter::GREGORIAN, + pattern: $pattern, + ); +} + +/** + * @internal + */ +function default_timezone(): Timezone +{ /** - * @internal + * `date_default_timezone_get` function might return any of the "Others" timezones + * mentioned in PHP doc: https://www.php.net/manual/en/timezones.others.php. + * + * Those timezones are not supported by Tempest (aside from UTC), as they are considered "legacy". */ - function create_intl_date_formatter( - ?DateStyle $dateStyle = null, - ?TimeStyle $timeStyle = null, - null|FormatPattern|string $pattern = null, - ?Timezone $timezone = null, - ?Locale $locale = null, - ): IntlDateFormatter { - if ($pattern instanceof FormatPattern) { - $pattern = $pattern->value; - } + $timezoneId = date_default_timezone_get(); - $dateStyle ??= DateStyle::default(); - $timeStyle ??= TimeStyle::default(); - $locale ??= current_locale(); - $timezone ??= Timezone::default(); - - return new IntlDateFormatter( - locale: $locale->value, - dateType: match ($dateStyle) { - DateStyle::NONE => IntlDateFormatter::NONE, - DateStyle::SHORT => IntlDateFormatter::SHORT, - DateStyle::MEDIUM => IntlDateFormatter::MEDIUM, - DateStyle::LONG => IntlDateFormatter::LONG, - DateStyle::FULL => IntlDateFormatter::FULL, - DateStyle::RELATIVE_SHORT => IntlDateFormatter::RELATIVE_SHORT, - DateStyle::RELATIVE_MEDIUM => IntlDateFormatter::RELATIVE_MEDIUM, - DateStyle::RELATIVE_LONG => IntlDateFormatter::RELATIVE_LONG, - DateStyle::RELATIVE_FULL => IntlDateFormatter::RELATIVE_FULL, - }, - timeType: match ($timeStyle) { - TimeStyle::NONE => IntlDateFormatter::NONE, - TimeStyle::SHORT => IntlDateFormatter::SHORT, - TimeStyle::MEDIUM => IntlDateFormatter::MEDIUM, - TimeStyle::LONG => IntlDateFormatter::LONG, - TimeStyle::FULL => IntlDateFormatter::FULL, - }, - timezone: namespace\to_intl_timezone($timezone), - calendar: IntlDateFormatter::GREGORIAN, - pattern: $pattern, - ); - } + return Timezone::tryFrom($timezoneId) ?? Timezone::UTC; +} +/** + * @return array{int, int} + * + * @internal + */ +function high_resolution_time(): array +{ /** - * @internal + * @var null|array{int, int} $offset */ - function default_timezone(): Timezone - { - /** - * `date_default_timezone_get` function might return any of the "Others" timezones - * mentioned in PHP doc: https://www.php.net/manual/en/timezones.others.php. - * - * Those timezones are not supported by Tempest (aside from UTC), as they are considered "legacy". - */ - $timezoneId = date_default_timezone_get(); - - return Timezone::tryFrom($timezoneId) ?? Timezone::UTC; - } + static $offset = null; - /** - * @return array{int, int} - * - * @internal - */ - function high_resolution_time(): array - { - /** - * @var null|array{int, int} $offset - */ - static $offset = null; - - if ($offset === null) { - $offset = hrtime(); - - if ($offset === false) { // @phpstan-ignore-line identical.alwaysFalse - throw new \RuntimeException('The system does not provide a monotonic timer.'); - } - - $time = system_time(); - - $offset = [ - $time[0] - $offset[0], - $time[1] - $offset[1], - ]; + if ($offset === null) { + $offset = hrtime(); + + if ($offset === false) { // @phpstan-ignore-line identical.alwaysFalse + throw new \RuntimeException('The system does not provide a monotonic timer.'); } - [$secondsOffset, $nanosecondsOffset] = $offset; - [$seconds, $nanoseconds] = hrtime(); + $time = system_time(); - $nanosecondsAdjusted = $nanoseconds + $nanosecondsOffset; + $offset = [ + $time[0] - $offset[0], + $time[1] - $offset[1], + ]; + } - if ($nanosecondsAdjusted >= NANOSECONDS_PER_SECOND) { - ++$seconds; - $nanosecondsAdjusted -= NANOSECONDS_PER_SECOND; - } elseif ($nanosecondsAdjusted < 0) { - --$seconds; - $nanosecondsAdjusted += NANOSECONDS_PER_SECOND; - } + [$secondsOffset, $nanosecondsOffset] = $offset; + [$seconds, $nanoseconds] = hrtime(); - $seconds += $secondsOffset; - $nanoseconds = $nanosecondsAdjusted; + $nanosecondsAdjusted = $nanoseconds + $nanosecondsOffset; - return [$seconds, $nanoseconds]; + if ($nanosecondsAdjusted >= NANOSECONDS_PER_SECOND) { + ++$seconds; + $nanosecondsAdjusted -= NANOSECONDS_PER_SECOND; + } elseif ($nanosecondsAdjusted < 0) { + --$seconds; + $nanosecondsAdjusted += NANOSECONDS_PER_SECOND; } - /** - * @internal - */ - function intl_parse( - string $rawString, - ?DateStyle $dateStyle = null, - ?TimeStyle $timeStyle = null, - null|FormatPattern|string $pattern = null, - ?Timezone $timezone = null, - ?Locale $locale = null, - ): int { - $formatter = namespace\create_intl_date_formatter($dateStyle, $timeStyle, $pattern, $timezone, $locale); - - $timestamp = $formatter->parse($rawString); - - if ($timestamp === false) { - // Only show pattern in the exception if it was provided. - if (null !== $pattern) { - $formatter_pattern = $pattern instanceof FormatPattern ? $pattern->value : $pattern; - - throw new ParserException(sprintf( - "Unable to interpret '%s' as a valid date/time using pattern '%s'.", - $rawString, - $formatter_pattern, - )); - } - - throw new ParserException("Unable to interpret '{$rawString}' as a valid date/time."); + $seconds += $secondsOffset; + $nanoseconds = $nanosecondsAdjusted; + + return [$seconds, $nanoseconds]; +} + +/** + * @internal + */ +function intl_parse( + string $rawString, + ?DateStyle $dateStyle = null, + ?TimeStyle $timeStyle = null, + null|FormatPattern|string $pattern = null, + ?Timezone $timezone = null, + ?Locale $locale = null, +): int { + $formatter = namespace\create_intl_date_formatter($dateStyle, $timeStyle, $pattern, $timezone, $locale); + + $timestamp = $formatter->parse($rawString); + + if ($timestamp === false) { + // Only show pattern in the exception if it was provided. + if (null !== $pattern) { + $formatter_pattern = $pattern instanceof FormatPattern ? $pattern->value : $pattern; + + throw new ParserException(sprintf( + "Unable to interpret '%s' as a valid date/time using pattern '%s'.", + $rawString, + $formatter_pattern, + )); } - return (int) $timestamp; + throw new ParserException("Unable to interpret '{$rawString}' as a valid date/time."); } - /** - * @return array{int, int} - * - * @internal - */ - function system_time(): array - { - $time = microtime(); + return (int) $timestamp; +} - $parts = explode(' ', $time); - $seconds = (int) $parts[1]; - $nanoseconds = (int) ((float) $parts[0] * (float) NANOSECONDS_PER_SECOND); +/** + * @return array{int, int} + * + * @internal + */ +function system_time(): array +{ + $time = microtime(); - return [$seconds, $nanoseconds]; - } + $parts = explode(' ', $time); + $seconds = (int) $parts[1]; + $nanoseconds = (int) ((float) $parts[0] * (float) NANOSECONDS_PER_SECOND); - /** - * @internal - */ - function to_intl_timezone(Timezone $timezone): IntlTimeZone - { - $value = $timezone->value; + return [$seconds, $nanoseconds]; +} - if (str_starts_with($value, '+') || str_starts_with($value, '-')) { - $value = 'GMT' . $value; - } +/** + * @internal + */ +function to_intl_timezone(Timezone $timezone): IntlTimeZone +{ + $value = $timezone->value; - $tz = IntlTimeZone::createTimeZone($value); + if (str_starts_with($value, '+') || str_starts_with($value, '-')) { + $value = 'GMT' . $value; + } - if ($tz === null) { // @phpstan-ignore-line identical.alwaysFalse - throw new \RuntimeException(sprintf( - 'Failed to create intl timezone from timezone "%s" ("%s" / "%s").', - $timezone->name, - $timezone->value, - $value, - )); - } + $tz = IntlTimeZone::createTimeZone($value); - if ($tz->getID() === 'Etc/Unknown' && $tz->getRawOffset() === 0) { - throw new \RuntimeException(sprintf( - 'Failed to create a valid intl timezone, unknown timezone "%s" ("%s" / "%s") given.', - $timezone->name, - $timezone->value, - $value, - )); - } + if ($tz === null) { // @phpstan-ignore-line identical.alwaysFalse + throw new \RuntimeException(sprintf( + 'Failed to create intl timezone from timezone "%s" ("%s" / "%s").', + $timezone->name, + $timezone->value, + $value, + )); + } - return $tz; + if ($tz->getID() === 'Etc/Unknown' && $tz->getRawOffset() === 0) { + throw new \RuntimeException(sprintf( + 'Failed to create a valid intl timezone, unknown timezone "%s" ("%s" / "%s") given.', + $timezone->name, + $timezone->value, + $value, + )); } + return $tz; +} + +/** + * @internal + */ +function create_intl_calendar_from_date_time( + Timezone $timezone, + int $year, + int $month, + int $day, + int $hours, + int $minutes, + int $seconds, +): IntlCalendar { /** - * @internal + * @var IntlCalendar $calendar */ - function create_intl_calendar_from_date_time( - Timezone $timezone, - int $year, - int $month, - int $day, - int $hours, - int $minutes, - int $seconds, - ): IntlCalendar { - /** - * @var IntlCalendar $calendar - */ - $calendar = IntlCalendar::createInstance(to_intl_timezone($timezone)); - - $calendar->setDateTime($year, $month - 1, $day, $hours, $minutes, $seconds); - - return $calendar; - } + $calendar = IntlCalendar::createInstance(to_intl_timezone($timezone)); + + $calendar->setDateTime($year, $month - 1, $day, $hours, $minutes, $seconds); + + return $calendar; } diff --git a/packages/debug/composer.json b/packages/debug/composer.json index d5eae92891..a5b07f5b93 100644 --- a/packages/debug/composer.json +++ b/packages/debug/composer.json @@ -4,10 +4,13 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/highlight": "^2.11.4", "symfony/var-dumper": "^7.1" }, + "require-dev": { + "tempest/console": "dev-main" + }, "autoload": { "psr-4": { "Tempest\\Debug\\": "src" @@ -15,5 +18,10 @@ "files": [ "src/functions.php" ] + }, + "autoload-dev": { + "psr-4": { + "Tempest\\Debug\\Tests\\": "tests" + } } } diff --git a/packages/debug/src/Debug.php b/packages/debug/src/Debug.php index 138128a58c..9ba9757931 100644 --- a/packages/debug/src/Debug.php +++ b/packages/debug/src/Debug.php @@ -4,36 +4,40 @@ namespace Tempest\Debug; -use Exception; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\VarDumper; use Tempest\Container\GenericContainer; use Tempest\EventBus\EventBus; use Tempest\Highlight\Themes\TerminalStyle; -use Tempest\Log\LogConfig; +use Tempest\Support\Filesystem; +use Throwable; final readonly class Debug { private function __construct( - private ?LogConfig $logConfig = null, + private ?DebugConfig $config = null, private ?EventBus $eventBus = null, ) {} public static function resolve(): self { try { - $container = GenericContainer::instance(); - return new self( - logConfig: $container?->get(LogConfig::class), - eventBus: $container?->get(EventBus::class), + config: GenericContainer::instance()->get(DebugConfig::class), + eventBus: GenericContainer::instance()->get(EventBus::class), ); - } catch (Exception) { + } catch (Throwable) { return new self(); } } + /** + * Logs and/or dumps the given items. + * + * @param bool $writeToLog Whether to write the items to the log file. + * @param bool $writeToOut Whether to dump the items to the standard output. + */ public function log(array $items, bool $writeToLog = true, bool $writeToOut = true): void { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); @@ -52,26 +56,19 @@ public function log(array $items, bool $writeToLog = true, bool $writeToOut = tr private function writeToLog(array $items, string $callPath): void { - if (! $this->logConfig?->debugLogPath) { + if (! $this->config?->logPath) { return; } - $directory = dirname($this->logConfig->debugLogPath); - - if (! is_dir($directory)) { - mkdir(directory: $directory, recursive: true); - } + Filesystem\create_directory_for_file($this->config->logPath); - $handle = @fopen($this->logConfig->debugLogPath, 'a'); - - if (! $handle) { + if (! ($handle = @fopen($this->config->logPath, 'a'))) { return; } foreach ($items as $key => $item) { - $output = $this->createDump($item) . $callPath; - - fwrite($handle, "{$key} " . $output . PHP_EOL); + fwrite($handle, TerminalStyle::BG_BLUE(" {$key} ") . TerminalStyle::FG_GRAY(' → ' . TerminalStyle::ITALIC($callPath))); + fwrite($handle, $this->createCliDump($item) . PHP_EOL); } fclose($handle); @@ -82,27 +79,27 @@ private function writeToOut(array $items, string $callPath): void foreach ($items as $key => $item) { if (defined('STDOUT')) { fwrite(STDOUT, TerminalStyle::BG_BLUE(" {$key} ") . ' '); - - $output = $this->createDump($item); - - fwrite(STDOUT, $output); - - fwrite(STDOUT, $callPath . PHP_EOL); + fwrite(STDOUT, $this->createCliDump($item)); + fwrite(STDOUT, TerminalStyle::DIM('→ ' . TerminalStyle::ITALIC($callPath)) . PHP_EOL . PHP_EOL); } else { echo - sprintf( - '%s (%s)', - 'Source Code Pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace', - $key, - $callPath, + vsprintf( + <<%s (%s) + HTML, + [ + 'Source Code Pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace', + $key, + $callPath, + ], ) ; @@ -111,10 +108,9 @@ private function writeToOut(array $items, string $callPath): void } } - private function createDump(mixed $input): string + private function createCliDump(mixed $input): string { $cloner = new VarCloner(); - $output = ''; $dumper = new CliDumper(function ($line, $depth) use (&$output): void { @@ -126,7 +122,6 @@ private function createDump(mixed $input): string }); $dumper->setColors(true); - $dumper->dump($cloner->cloneVar($input)); return preg_replace( diff --git a/packages/debug/src/DebugConfig.php b/packages/debug/src/DebugConfig.php new file mode 100644 index 0000000000..11cc9fad50 --- /dev/null +++ b/packages/debug/src/DebugConfig.php @@ -0,0 +1,13 @@ + 'null', + is_bool($value) => sprintf('bool<%s>', $value ? 'true' : 'false'), + is_int($value) => (string) $value, + is_float($value) => (string) $value, + is_string($value) => mb_strlen($value) > 50 + ? sprintf('string<%s>', mb_strlen($value)) + : sprintf('"%s"', $value), + is_array($value) => sprintf('array<%s>', count($value)), + is_object($value) => sprintf('object<%s>', $value::class), + is_resource($value) => 'resource', + default => get_debug_type($value), + }; + } +} diff --git a/packages/debug/src/Stacktrace/CodeSnippet.php b/packages/debug/src/Stacktrace/CodeSnippet.php new file mode 100644 index 0000000000..21a214848a --- /dev/null +++ b/packages/debug/src/Stacktrace/CodeSnippet.php @@ -0,0 +1,26 @@ + $lines + */ + public function __construct( + public array $lines, + public int $highlightedLine, + ) {} + + public function getStartLine(): int + { + return array_key_first($this->lines) ?? 0; + } + + public function getEndLine(): int + { + return array_key_last($this->lines) ?? 0; + } +} diff --git a/packages/debug/src/Stacktrace/Frame.php b/packages/debug/src/Stacktrace/Frame.php new file mode 100644 index 0000000000..95592f4508 --- /dev/null +++ b/packages/debug/src/Stacktrace/Frame.php @@ -0,0 +1,148 @@ + $arguments + */ + public function __construct( + private(set) int $line, + private(set) ?string $class, + private(set) ?string $function, + private(set) ?string $type, + private(set) bool $isVendor, + private(set) ?CodeSnippet $snippet, + private(set) string $absoluteFile, + private(set) string $relativeFile, + private(set) array $arguments, + private(set) int $index, + ) {} + + public static function fromArray(array $frame, int $contextLines = 5, ?string $rootPath = null, int $index = 1): self + { + $absoluteFile = $frame['file'] ?? ''; + $line = $frame['line'] ?? 0; + $isVendor = self::isVendorFile($absoluteFile, $rootPath); + $snippet = null; + + if ($absoluteFile && $line && ! $isVendor && file_exists($absoluteFile)) { + $snippet = self::extractCodeSnippet($absoluteFile, $line, $contextLines); + } + + return new self( + line: $line, + class: $frame['class'] ?? null, + function: $frame['function'] ?? null, + type: $frame['type'] ?? null, + isVendor: $isVendor, + snippet: $snippet, + absoluteFile: $absoluteFile, + relativeFile: $rootPath ? to_relative_path($rootPath, $absoluteFile) : $absoluteFile, + arguments: self::extractArguments($frame), + index: $index, + ); + } + + /** + * @return array + */ + public static function extractArguments(array $frame): array + { + if (! isset($frame['args']) || ! is_array($frame['args'])) { + return []; + } + + $arguments = $frame['args']; + $parameterNames = []; + + try { + $reflection = isset($frame['class'], $frame['function']) + ? new ReflectionMethod(objectOrMethod: $frame['class'], method: $frame['function']) + : new ReflectionFunction(function: $frame['function']); + + $parameterNames = array_map( + callback: fn (ReflectionParameter $param) => $param->getName(), + array: $reflection->getParameters(), + ); + } catch (\Throwable) { + // @mago-expect lint:no-empty-catch-clause + } + + $result = []; + foreach ($arguments as $index => $value) { + $result[] = Argument::make( + name: $parameterNames[$index] ?? $index, + value: $value, + ); + } + + return $result; + } + + public static function isVendorFile(string $file, ?string $rootPath = null): bool + { + if ($file === '') { + return false; + } + + if ($rootPath !== null) { + return ! str_starts_with( + haystack: str_replace('\\', '/', $file), + needle: str_replace('\\', '/', $rootPath), + ); + } + + return str_contains($file, '/vendor/') || str_contains($file, '\\vendor\\'); + } + + public static function extractCodeSnippet(string $file, int $line, int $contextLines): ?CodeSnippet + { + $fileLines = file($file, FILE_IGNORE_NEW_LINES); + + if ($fileLines === false) { + return null; + } + + $startLine = max(1, $line - $contextLines); + $endLine = min(count($fileLines), $line + $contextLines); + $lines = []; + + for ($i = $startLine; $i <= $endLine; $i++) { + $lines[$i] = $fileLines[$i - 1]; + } + + if ($lines === []) { + return null; + } + + return new CodeSnippet( + lines: $lines, + highlightedLine: $line, + ); + } + + public function getMethodName(): string + { + if (! $this->class) { + return $this->function ?? ''; + } + + $type = match ($this->type) { + '::' => '::', + '->' => '->', + default => '', + }; + + return $this->class . $type . ($this->function ?? ''); + } +} diff --git a/packages/debug/src/Stacktrace/Stacktrace.php b/packages/debug/src/Stacktrace/Stacktrace.php new file mode 100644 index 0000000000..16ad4cd002 --- /dev/null +++ b/packages/debug/src/Stacktrace/Stacktrace.php @@ -0,0 +1,124 @@ + */ + public array $applicationFrames { + get => array_values(array_filter( + array: $this->frames, + callback: fn (Frame $frame) => ! $frame->isVendor, + )); + } + + /** @var array */ + public array $vendorFrames { + get => array_values(array_filter( + array: $this->frames, + callback: fn (Frame $frame) => $frame->isVendor, + )); + } + + /** + * @param array $frames + */ + public function __construct( + private(set) string $message, + private(set) string $exceptionClass, + private(set) array $frames, + private(set) int $line, + private(set) string $absoluteFile, + private(set) string $relativeFile, + ) {} + + public static function fromThrowable(Throwable $throwable, int $contextLines = 5, ?string $rootPath = null): self + { + $frames = []; + $snippet = null; + $trace = $throwable->getTrace(); + $firstTraceFrame = $trace[0] ?? null; + $exceptionFile = $throwable->getFile(); + $exceptionLine = $throwable->getLine(); + $isVendor = Frame::isVendorFile($exceptionFile, $rootPath); + + if ($exceptionFile && $exceptionLine && ! $isVendor && file_exists($exceptionFile)) { + $snippet = Frame::extractCodeSnippet($exceptionFile, $exceptionLine, $contextLines); + } + + $frames[] = new Frame( + line: $exceptionLine, + class: $firstTraceFrame['class'] ?? null, + function: $firstTraceFrame['function'] ?? null, + type: $firstTraceFrame['type'] ?? null, + isVendor: $isVendor, + snippet: $snippet, + absoluteFile: $exceptionFile, + relativeFile: $rootPath + ? to_relative_path($rootPath, $exceptionFile) + : $exceptionFile, + arguments: $firstTraceFrame + ? Frame::extractArguments($firstTraceFrame) + : [], + index: 1, + ); + + foreach (array_slice($trace, offset: 1) as $i => $frame) { + $frames[] = Frame::fromArray($frame, $contextLines, $rootPath, $i + 2); + } + + return new self( + message: $throwable->getMessage(), + exceptionClass: $throwable::class, + frames: $frames, + line: $throwable->getLine(), + absoluteFile: $exceptionFile, + relativeFile: $rootPath ? to_relative_path($rootPath, $exceptionFile) : $exceptionFile, + ); + } + + public function prependFrame(Frame $frame): self + { + return new self( + message: $this->message, + exceptionClass: $this->exceptionClass, + frames: [ + // we add our frame + new Frame( + line: $frame->line, + class: $frame->class, + function: $frame->function, + type: $frame->type, + isVendor: $frame->isVendor, + snippet: $frame->snippet, + absoluteFile: $frame->absoluteFile, + relativeFile: $frame->relativeFile, + arguments: $frame->arguments, + index: 1, + ), + // and shift the frame index by one for each frame + ...array_map(fn (Frame $frame) => new Frame( + line: $frame->line, + class: $frame->class, + function: $frame->function, + type: $frame->type, + isVendor: $frame->isVendor, + snippet: $frame->snippet, + absoluteFile: $frame->absoluteFile, + relativeFile: $frame->relativeFile, + arguments: $frame->arguments, + index: $frame->index + 1, + ), $this->frames), + ], + line: $this->line, + absoluteFile: $this->absoluteFile, + relativeFile: $this->relativeFile, + ); + } +} diff --git a/packages/debug/src/TailDebugCommand.php b/packages/debug/src/TailDebugCommand.php new file mode 100644 index 0000000000..ae4338d78e --- /dev/null +++ b/packages/debug/src/TailDebugCommand.php @@ -0,0 +1,44 @@ +debugConfig->logPath; + + if (! $debugLogPath) { + $this->console->error('No debug log configured in DebugConfig.'); + + return; + } + + if ($clear && Filesystem\is_file($debugLogPath)) { + Filesystem\delete_file($debugLogPath); + } + + Filesystem\create_file($debugLogPath); + + $this->console->header('Tailing debug logs', "Reading …"); + + new TailReader()->tail($debugLogPath); + } +} diff --git a/packages/debug/src/debug.config.php b/packages/debug/src/debug.config.php new file mode 100644 index 0000000000..30e24ba3f1 --- /dev/null +++ b/packages/debug/src/debug.config.php @@ -0,0 +1,7 @@ +log($input); - } +if (! function_exists('lw')) { + /** + * Writes the given `$input` to the logs, and dumps it. + * @see \Tempest\Debug\Debug::log() + */ + function lw(mixed ...$input): void + { + Debug::resolve()->log($input); } +} - if (! function_exists('ld')) { - /** - * Writes the given `$input` to the logs, dumps it, and stops the execution of the script. - * @see \Tempest\Debug\Debug::log() - */ - function ld(mixed ...$input): never - { - Debug::resolve()->log($input); - die(); - } +if (! function_exists('ld')) { + /** + * Writes the given `$input` to the logs, dumps it, and stops the execution of the script. + * @see \Tempest\Debug\Debug::log() + */ + function ld(mixed ...$input): never + { + Debug::resolve()->log($input); + die(); } +} - if (! function_exists('ll')) { - /** - * Writes the given `$input` to the logs. - * @see \Tempest\Debug\Debug::log() - */ - function ll(mixed ...$input): void - { - Debug::resolve()->log($input, writeToOut: false); - } +if (! function_exists('ll')) { + /** + * Writes the given `$input` to the logs. + * @see \Tempest\Debug\Debug::log() + */ + function ll(mixed ...$input): void + { + Debug::resolve()->log($input, writeToOut: false); } +} - if (! function_exists('le')) { - /** - * Emits a `ItemsDebugged` event. - * @see \Tempest\Debug\Debug::log() - */ - function le(mixed ...$input): void - { - Debug::resolve()->log($input, writeToOut: false, writeToLog: false); - } +if (! function_exists('le')) { + /** + * Emits a `ItemsDebugged` event. + * @see \Tempest\Debug\Debug::log() + */ + function le(mixed ...$input): void + { + Debug::resolve()->log($input, writeToOut: false, writeToLog: false); } +} - if (! function_exists('dd')) { - /** - * Writes the given `$input` to the logs, dumps it, and stops the execution of the script. - * @see ld() - * @see \Tempest\Debug\Debug::log() - */ - function dd(mixed ...$input): never - { - ld(...$input); - } +if (! function_exists('dd')) { + /** + * Writes the given `$input` to the logs, dumps it, and stops the execution of the script. + * @see ld() + * @see \Tempest\Debug\Debug::log() + */ + function dd(mixed ...$input): never + { + ld(...$input); } +} - if (! function_exists('dump')) { - /** - * Writes the given `$input` to the logs, and dumps it. - * @see lw() - * @see \Tempest\Debug\Debug::log() - */ - function dump(mixed ...$input): void - { - lw(...$input); - } +if (! function_exists('dump')) { + /** + * Writes the given `$input` to the logs, and dumps it. + * @see lw() + * @see \Tempest\Debug\Debug::log() + */ + function dump(mixed ...$input): void + { + lw(...$input); } } diff --git a/packages/debug/tests/StacktraceTest.php b/packages/debug/tests/StacktraceTest.php new file mode 100644 index 0000000000..332a371eea --- /dev/null +++ b/packages/debug/tests/StacktraceTest.php @@ -0,0 +1,227 @@ +createException(); + $stacktrace = Stacktrace::fromThrowable($exception); + + $this->assertSame('Test exception', $stacktrace->message); + $this->assertSame(RuntimeException::class, $stacktrace->exceptionClass); + $this->assertNotEmpty($stacktrace->frames); + $this->assertContainsOnlyInstancesOf(Frame::class, $stacktrace->frames); + } + + #[Test] + public function exception_frame_includes_snippet_for_existing_file(): void + { + $exception = $this->createException(); + $stacktrace = Stacktrace::fromThrowable($exception); + + $exceptionFrame = $stacktrace->frames[0]; + + $this->assertInstanceOf(CodeSnippet::class, $exceptionFrame->snippet); + $this->assertNotEmpty($exceptionFrame->snippet->lines); + } + + #[Test] + public function stacktrace_frame_creates_from_array(): void + { + $frame = Frame::fromArray([ + 'file' => __FILE__, + 'line' => 100, + 'class' => self::class, + 'function' => 'testMethod', + 'type' => '->', + ]); + + $this->assertSame(__FILE__, $frame->absoluteFile); + $this->assertSame(100, $frame->line); + $this->assertSame(self::class, $frame->class); + $this->assertSame('testMethod', $frame->function); + $this->assertSame('->', $frame->type); + $this->assertFalse($frame->isVendor); + } + + #[Test] + public function detects_vendor_files(): void + { + $vendor = Frame::fromArray([ + 'file' => '/path/to/vendor/package/file.php', + 'line' => 10, + ]); + + $this->assertTrue($vendor->isVendor); + + $app = Frame::fromArray([ + 'file' => '/path/to/app/file.php', + 'line' => 10, + ]); + + $this->assertFalse($app->isVendor); + } + + #[Test] + public function extracts_code_snippet_for_non_vendor_files(): void + { + $frame = Frame::fromArray([ + 'file' => __FILE__, + 'line' => 50, + ]); + + $this->assertInstanceOf(CodeSnippet::class, $frame->snippet); + $this->assertNotEmpty($frame->snippet->lines); + $this->assertSame(50, $frame->snippet->highlightedLine); + } + + #[Test] + public function no_snippet_for_vendor_files(): void + { + $frame = Frame::fromArray([ + 'file' => '/path/to/vendor/package/file.php', + 'line' => 10, + ]); + + $this->assertNull($frame->snippet); + } + + #[Test] + public function code_snippet_extracts_context_lines(): void + { + $frame = Frame::fromArray( + frame: [ + 'file' => __FILE__, + 'line' => 50, + ], + contextLines: 3, + ); + + $this->assertNotNull($frame->snippet); + $this->assertGreaterThanOrEqual(47, $frame->snippet->getStartLine()); + $this->assertLessThanOrEqual(53, $frame->snippet->getEndLine()); + } + + #[Test] + public function gets_relative_file_path(): void + { + $frame = Frame::fromArray( + frame: [ + 'file' => '/path/to/project/src/Controller.php', + 'line' => 10, + ], + rootPath: '/path/to/project', + ); + + $this->assertSame('/path/to/project/src/Controller.php', $frame->absoluteFile); + $this->assertSame('src/Controller.php', $frame->relativeFile); + } + + #[Test] + public function gets_method_name(): void + { + $instance = Frame::fromArray([ + 'file' => __FILE__, + 'line' => 10, + 'class' => 'MyClass', + 'function' => 'myMethod', + 'type' => '->', + ]); + + $this->assertSame('MyClass->myMethod', $instance->getMethodName()); + + $static = Frame::fromArray([ + 'file' => __FILE__, + 'line' => 10, + 'class' => 'MyClass', + 'function' => 'staticMethod', + 'type' => '::', + ]); + + $this->assertSame('MyClass::staticMethod', $static->getMethodName()); + + $function = Frame::fromArray([ + 'file' => __FILE__, + 'line' => 10, + 'function' => 'myFunction', + ]); + + $this->assertSame('myFunction', $function->getMethodName()); + } + + #[Test] + public function code_snippet_respects_file_boundaries(): void + { + $frame = Frame::fromArray( + frame: [ + 'file' => __FILE__, + 'line' => 2, + ], + contextLines: 10, + ); + + $this->assertNotNull($frame->snippet); + $this->assertSame(1, $frame->snippet->getStartLine()); + } + + #[Test] + public function detects_vendor_files_with_root_path(): void + { + $rootPath = '/path/to/project'; + + $vendor = Frame::fromArray( + frame: [ + 'file' => '/different/path/vendor/package/file.php', + 'line' => 10, + ], + rootPath: $rootPath, + ); + + $this->assertTrue($vendor->isVendor); + + $app = Frame::fromArray( + frame: [ + 'file' => '/path/to/project/src/Controller.php', + 'line' => 10, + ], + rootPath: $rootPath, + ); + + $this->assertFalse($app->isVendor); + } + + #[Test] + public function stacktrace_uses_root_path_for_vendor_detection(): void + { + $exception = $this->createException(); + $rootPath = dirname(__DIR__, levels: 3); // Get project root + + $stacktrace = Stacktrace::fromThrowable($exception, rootPath: $rootPath); + $frames = $stacktrace->applicationFrames; + + $this->assertNotEmpty($frames); + + foreach ($frames as $frame) { + if (str_starts_with($frame->absoluteFile, $rootPath)) { + $this->assertFalse($frame->isVendor); + } + } + } + + private function createException(): RuntimeException + { + return new RuntimeException('Test exception'); + } +} diff --git a/packages/discovery/composer.json b/packages/discovery/composer.json index a8d380860b..cb28493339 100644 --- a/packages/discovery/composer.json +++ b/packages/discovery/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/reflection": "dev-main", "tempest/support": "dev-main" }, diff --git a/packages/discovery/src/Commands/MakeDiscoveryCommand.php b/packages/discovery/src/Commands/MakeDiscoveryCommand.php index 4513949d41..ddbb812957 100644 --- a/packages/discovery/src/Commands/MakeDiscoveryCommand.php +++ b/packages/discovery/src/Commands/MakeDiscoveryCommand.php @@ -9,8 +9,8 @@ use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; use Tempest\Discovery\Stubs\DiscoveryStub; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { final class MakeDiscoveryCommand diff --git a/packages/discovery/src/DiscoveryItems.php b/packages/discovery/src/DiscoveryItems.php index 85ace2ef14..8b5e1b30ca 100644 --- a/packages/discovery/src/DiscoveryItems.php +++ b/packages/discovery/src/DiscoveryItems.php @@ -17,7 +17,7 @@ public function __construct( private array $items = [], ) {} - public function addForLocation(DiscoveryLocation $location, array $values): self + public function addForLocation(DiscoveryLocation $location, iterable $values): self { $existingValues = $this->items[$location->path] ?? []; diff --git a/packages/discovery/src/DiscoveryLocation.php b/packages/discovery/src/DiscoveryLocation.php index 93bdb84e88..13dd3c8b35 100644 --- a/packages/discovery/src/DiscoveryLocation.php +++ b/packages/discovery/src/DiscoveryLocation.php @@ -13,7 +13,7 @@ final class DiscoveryLocation public readonly string $path; public string $key { - get => (string) crc32($this->path); + get => hash('xxh64', $this->path); } public function __construct( @@ -29,9 +29,14 @@ public static function fromNamespace(Psr4Namespace $namespace): self return new self($namespace->namespace, $namespace->path); } + public function isTempest(): bool + { + return str_starts_with($this->namespace, 'Tempest'); + } + public function isVendor(): bool { - return str_contains($this->path, '/vendor/') || str_contains($this->path, '\\vendor\\') || str_starts_with($this->namespace, 'Tempest'); + return str_contains($this->path, '/vendor/') || str_contains($this->path, '\\vendor\\') || $this->isTempest(); } public function toClassName(string $path): string diff --git a/packages/event-bus/composer.json b/packages/event-bus/composer.json index b80a1d824f..794948ba5f 100644 --- a/packages/event-bus/composer.json +++ b/packages/event-bus/composer.json @@ -2,7 +2,7 @@ "name": "tempest/event-bus", "description": "A lightweight event bus component designed to facilitate event-driven architecture and asynchronous message handling.", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/core": "dev-main", "tempest/container": "dev-main", "tempest/reflection": "dev-main" diff --git a/packages/event-bus/src/EventBus.php b/packages/event-bus/src/EventBus.php index 445e9a0e30..e6cf80e117 100644 --- a/packages/event-bus/src/EventBus.php +++ b/packages/event-bus/src/EventBus.php @@ -5,10 +5,17 @@ namespace Tempest\EventBus; use Closure; +use UnitEnum; interface EventBus { + /** + * Dispatches the given event to all its listeners. The event can be a string, a FQCN or an plain old PHP object. + */ public function dispatch(string|object $event): void; - public function listen(Closure $handler, ?string $event = null): void; + /** + * Adds a listener for the given event. The closure accepts the event object as its first parameter, so the `$event` parameter is optional. + */ + public function listen(Closure $handler, string|UnitEnum|null $event = null): void; } diff --git a/packages/event-bus/src/EventBusConfig.php b/packages/event-bus/src/EventBusConfig.php index 9449b034a3..d0caecd6d9 100644 --- a/packages/event-bus/src/EventBusConfig.php +++ b/packages/event-bus/src/EventBusConfig.php @@ -9,6 +9,7 @@ use Tempest\Core\Middleware; use Tempest\Reflection\FunctionReflector; use Tempest\Reflection\MethodReflector; +use UnitEnum; final class EventBusConfig { @@ -20,8 +21,12 @@ public function __construct( public Middleware $middleware = new Middleware(), ) {} - public function addClosureHandler(Closure $handler, ?string $event = null): self + public function addClosureHandler(Closure $handler, string|UnitEnum|null $event = null): self { + if ($event instanceof UnitEnum) { + $event = $event::class . '::' . $event->name; + } + $event ??= new FunctionReflector($handler) ->getParameter(key: 0) ?->getType() diff --git a/packages/event-bus/src/GenericEventBus.php b/packages/event-bus/src/GenericEventBus.php index 1e2284114f..918932853c 100644 --- a/packages/event-bus/src/GenericEventBus.php +++ b/packages/event-bus/src/GenericEventBus.php @@ -9,16 +9,16 @@ use Tempest\Support\Str; use UnitEnum; -use function Tempest\reflect; +use function Tempest\Reflection\reflect; final readonly class GenericEventBus implements EventBus { public function __construct( private Container $container, - private EventBusConfig $eventBusConfig, + private(set) EventBusConfig $eventBusConfig, ) {} - public function listen(Closure $handler, ?string $event = null): void + public function listen(Closure $handler, string|UnitEnum|null $event = null): void { $this->eventBusConfig->addClosureHandler($handler, $event); } diff --git a/packages/event-bus/src/Testing/EventBusTester.php b/packages/event-bus/src/Testing/EventBusTester.php index f3d3a8fa71..b1409ecf45 100644 --- a/packages/event-bus/src/Testing/EventBusTester.php +++ b/packages/event-bus/src/Testing/EventBusTester.php @@ -6,7 +6,6 @@ use PHPUnit\Framework\Assert; use Tempest\Container\Container; use Tempest\EventBus\EventBus; -use Tempest\EventBus\EventBusConfig; use Tempest\Support\Str; final class EventBusTester @@ -18,16 +17,30 @@ public function __construct( ) {} /** - * Prevents the registered event handlers from being called. + * Records event dispatches, and optionally prevents the registered event handlers from being called. + * + * @param bool $preventHandling Whether to prevent the registered event handlers from being called while still allowing assertions. */ - public function preventEventHandling(): self + public function recordEventDispatches(bool $preventHandling = false): self { - $this->fakeEventBus = new FakeEventBus($this->container->get(EventBusConfig::class)); + $this->fakeEventBus = new FakeEventBus( + genericEventBus: $this->container->get(EventBus::class), + preventHandling: $preventHandling, + ); + $this->container->singleton(EventBus::class, $this->fakeEventBus); return $this; } + /** + * Prevents the registered event handlers from being called. + */ + public function preventEventHandling(): self + { + return $this->recordEventDispatches(preventHandling: true); + } + /** * Asserts that the given `$event` has been dispatched. * @@ -36,7 +49,7 @@ public function preventEventHandling(): self */ public function assertDispatched(string|object $event, ?Closure $callback = null, ?int $count = null): self { - $this->assertFaked(); + $this->assertRecording(); Assert::assertNotEmpty( actual: $dispatches = $this->findDispatches($event), @@ -61,7 +74,7 @@ public function assertDispatched(string|object $event, ?Closure $callback = null */ public function assertNotDispatched(string|object $event): self { - $this->assertFaked(); + $this->assertRecording(); Assert::assertEmpty($this->findDispatches($event), 'The event was dispatched.'); @@ -75,7 +88,7 @@ public function assertNotDispatched(string|object $event): self */ public function assertListeningTo(string $event, ?int $count = null): self { - $this->assertFaked(); + $this->assertRecording(); Assert::assertNotEmpty( actual: $handlers = $this->findHandlersFor($event), @@ -109,12 +122,15 @@ private function findHandlersFor(string|object $event): array { $eventName = Str\parse($event) ?: $event::class; - return $this->fakeEventBus->eventBusConfig->handlers[$eventName] ?? []; + return $this->fakeEventBus->handlers[$eventName] ?? []; } - private function assertFaked(): self + private function assertRecording(): self { - Assert::assertTrue(isset($this->fakeEventBus), 'Asserting against the event bus require the `preventEventHandling()` method to be called first.'); + Assert::assertTrue( + isset($this->fakeEventBus), + 'Asserting against the event bus require the `recordEventHandling()` or `preventEventHandling()` method to be called first.', + ); return $this; } diff --git a/packages/event-bus/src/Testing/FakeEventBus.php b/packages/event-bus/src/Testing/FakeEventBus.php index 841b703d63..9003c08be0 100644 --- a/packages/event-bus/src/Testing/FakeEventBus.php +++ b/packages/event-bus/src/Testing/FakeEventBus.php @@ -3,24 +3,37 @@ namespace Tempest\EventBus\Testing; use Closure; +use Tempest\EventBus\CallableEventHandler; use Tempest\EventBus\EventBus; -use Tempest\EventBus\EventBusConfig; +use Tempest\EventBus\GenericEventBus; +use UnitEnum; final class FakeEventBus implements EventBus { + /** @var array */ public array $dispatched = []; + /** @var array> */ + public array $handlers { + get => $this->genericEventBus->eventBusConfig->handlers; + } + public function __construct( - public EventBusConfig $eventBusConfig, + private(set) GenericEventBus $genericEventBus, + public bool $preventHandling = true, ) {} - public function listen(Closure $handler, ?string $event = null): void + public function dispatch(string|object $event): void { - $this->eventBusConfig->addClosureHandler($handler, $event); + $this->dispatched[] = $event; + + if ($this->preventHandling === false) { + $this->genericEventBus->dispatch($event); + } } - public function dispatch(string|object $event): void + public function listen(Closure $handler, string|UnitEnum|null $event = null): void { - $this->dispatched[] = $event; + $this->genericEventBus->listen($handler, $event); } } diff --git a/packages/event-bus/src/functions.php b/packages/event-bus/src/functions.php index 4b49355f17..3869d85c16 100644 --- a/packages/event-bus/src/functions.php +++ b/packages/event-bus/src/functions.php @@ -2,28 +2,29 @@ declare(strict_types=1); -namespace Tempest { - use Closure; - use Tempest\EventBus\EventBus; - use Tempest\EventBus\EventBusConfig; +namespace Tempest\EventBus; - /** - * Dispatches the given `$event`, triggering all associated event listeners. - */ - function event(string|object $event): void - { - $eventBus = get(EventBus::class); +use Closure; +use Tempest\Container; +use Tempest\EventBus\EventBus; +use Tempest\EventBus\EventBusConfig; - $eventBus->dispatch($event); - } +/** + * Dispatches the given `$event`, triggering all associated event listeners. + */ +function event(string|object $event): void +{ + $eventBus = Container\get(EventBus::class); - /** - * Registers a closure-based event listener for the given `$event`. - */ - function listen(Closure $handler, ?string $event = null): void - { - $config = get(EventBusConfig::class); + $eventBus->dispatch($event); +} + +/** + * Registers a closure-based event listener for the given `$event`. + */ +function listen(Closure $handler, ?string $event = null): void +{ + $config = Container\get(EventBusConfig::class); - $config->addClosureHandler($handler, $event); - } + $config->addClosureHandler($handler, $event); } diff --git a/packages/event-bus/tests/EventBusTest.php b/packages/event-bus/tests/EventBusTest.php index 0a8c5c041c..3f47529ba3 100644 --- a/packages/event-bus/tests/EventBusTest.php +++ b/packages/event-bus/tests/EventBusTest.php @@ -13,6 +13,7 @@ use Tempest\EventBus\EventBusConfig; use Tempest\EventBus\EventHandler; use Tempest\EventBus\GenericEventBus; +use Tempest\EventBus\Tests\Fixtures\EventEnum; use Tempest\EventBus\Tests\Fixtures\EventInterface; use Tempest\EventBus\Tests\Fixtures\EventInterfaceHandler; use Tempest\EventBus\Tests\Fixtures\EventInterfaceImplementation; @@ -22,8 +23,8 @@ use Tempest\EventBus\Tests\Fixtures\MyService; use Tempest\Reflection\MethodReflector; -use function Tempest\get; -use function Tempest\listen; +use function Tempest\Container\get; +use function Tempest\EventBus\listen; /** * @internal @@ -190,4 +191,24 @@ public function test_interface_handlers(): void $this->assertTrue(EventInterfaceHandler::$itHappened); } + + public function test_closure_based_handlers_using_listen_method_and_enums(): void + { + $container = new GenericContainer(); + $config = new EventBusConfig(); + $eventBus = new GenericEventBus($container, $config); + $hasHappened = false; + + $eventBus->listen(function () use (&$hasHappened): void { + $hasHappened = true; + }, EventEnum::TWO); + + $eventBus->dispatch(EventEnum::ONE); + + $this->assertFalse($hasHappened); + + $eventBus->dispatch(EventEnum::TWO); + + $this->assertTrue($hasHappened); + } } diff --git a/packages/event-bus/tests/Fixtures/EventEnum.php b/packages/event-bus/tests/Fixtures/EventEnum.php new file mode 100644 index 0000000000..8244cb897c --- /dev/null +++ b/packages/event-bus/tests/Fixtures/EventEnum.php @@ -0,0 +1,11 @@ +getStatusCode()), + status: Status::fromCode($response->getStatusCode()), body: $response->getBody()->getContents(), headers: $response->getHeaders(), ); diff --git a/packages/http/composer.json b/packages/http/composer.json index 17700a16e6..cdb45d1b03 100644 --- a/packages/http/composer.json +++ b/packages/http/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/core": "dev-main", "tempest/clock": "dev-main", "tempest/console": "dev-main", diff --git a/packages/http/src/Commands/MakeRequestCommand.php b/packages/http/src/Commands/MakeRequestCommand.php index aebe4ee5d6..0c9474fd17 100644 --- a/packages/http/src/Commands/MakeRequestCommand.php +++ b/packages/http/src/Commands/MakeRequestCommand.php @@ -7,7 +7,7 @@ use Tempest\Console\ConsoleArgument; use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\DataObjects\StubFile; use Tempest\Http\Stubs\RequestStub; final class MakeRequestCommand diff --git a/packages/http/src/Commands/MakeResponseCommand.php b/packages/http/src/Commands/MakeResponseCommand.php index 8d009b0ecc..160b892929 100644 --- a/packages/http/src/Commands/MakeResponseCommand.php +++ b/packages/http/src/Commands/MakeResponseCommand.php @@ -7,7 +7,7 @@ use Tempest\Console\ConsoleArgument; use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\DataObjects\StubFile; use Tempest\Http\Stubs\ResponseStub; final class MakeResponseCommand diff --git a/packages/http/src/Cookie/Cookie.php b/packages/http/src/Cookie/Cookie.php index 7a704d6cc0..71ff00fd6c 100644 --- a/packages/http/src/Cookie/Cookie.php +++ b/packages/http/src/Cookie/Cookie.php @@ -28,9 +28,9 @@ public function __construct( public ?int $maxAge = null, public ?string $domain = null, public ?string $path = '/', - public bool $secure = false, + public bool $secure = true, public bool $httpOnly = false, - public ?SameSite $sameSite = null, + public SameSite $sameSite = SameSite::LAX, ) {} public function withValue(string $value): self diff --git a/packages/http/src/GenericResponse.php b/packages/http/src/GenericResponse.php index 7647430395..a5b9c78236 100644 --- a/packages/http/src/GenericResponse.php +++ b/packages/http/src/GenericResponse.php @@ -22,14 +22,6 @@ public function __construct( $this->body = $body; $this->view = $view; - foreach ($headers as $key => $values) { - if (! is_array($values)) { - $values = [$values]; - } - - foreach ($values as $value) { - $this->addHeader($key, $value); - } - } + $this->addHeaders($headers); } } diff --git a/packages/http/src/Header.php b/packages/http/src/Header.php index a539500c36..1b28132aea 100644 --- a/packages/http/src/Header.php +++ b/packages/http/src/Header.php @@ -4,16 +4,27 @@ namespace Tempest\Http; +use BackedEnum; + final class Header { public function __construct( public string $name, - /** @var array $values */ + /** @var array $values */ public array $values = [], ) {} public function add(mixed $value): void { + if ($value instanceof BackedEnum) { + $value = $value->value; + } + $this->values[] = $value; } + + public function first(): mixed + { + return array_first($this->values); + } } diff --git a/packages/http/src/HttpRequestFailed.php b/packages/http/src/HttpRequestFailed.php index 14375346e6..ed3999c3d7 100644 --- a/packages/http/src/HttpRequestFailed.php +++ b/packages/http/src/HttpRequestFailed.php @@ -3,37 +3,38 @@ namespace Tempest\Http; use Exception; -use Tempest\Core\HasContext; -use Throwable; +use Tempest\Core\ProvidesContext; /** - * Represents an HTTP exception. + * Stops the request's execution and return a response with the given status. Optionally, a message may be provided. */ -final class HttpRequestFailed extends Exception implements HasContext +final class HttpRequestFailed extends Exception implements ProvidesContext { + /** + * @param Status $status The HTTP status code to send as a response. + * @param string|null $message An optional message that will be displayed to the client. + * @param Response|null $cause The response that caused the failure, if any. + * @param Request|null $request The request that failed, for debug purposes. + */ public function __construct( - public readonly Request $request, - public readonly Status $status, + private(set) readonly Status $status, ?string $message = null, - public readonly ?Response $cause = null, - ?Throwable $previous = null, + private(set) readonly ?Response $cause = null, + private(set) readonly ?Request $request = null, ) { parent::__construct( - message: $message ?: 'Failed request: ' . $status->value . ' ' . $this->status->description(), + message: $message ?: '', code: $status->value, - previous: $previous, ); } public function context(): array { - return [ - 'request_uri' => $this->request->uri, - 'request_method' => $this->request->method->value, - 'status' => $this->status->value, - 'message' => $this->message, - 'cause' => $this->cause, - 'previous' => $this->getPrevious()?->getMessage(), - ]; + return array_filter([ + 'request_uri' => $this->request?->uri, + 'request_method' => $this->request?->method->value, + 'status_code' => $this->status->value, + 'original_response' => $this->cause ? $this->cause::class : null, + ]); } } diff --git a/packages/http/src/IsRequest.php b/packages/http/src/IsRequest.php index 0d698c608c..1ee0020fb6 100644 --- a/packages/http/src/IsRequest.php +++ b/packages/http/src/IsRequest.php @@ -8,7 +8,7 @@ use Tempest\Http\Session\Session; use Tempest\Validation\SkipValidation; -use function Tempest\get; +use function Tempest\Container\get; use function Tempest\Support\Arr\get_by_key; use function Tempest\Support\Arr\has_key; use function Tempest\Support\str; @@ -141,7 +141,7 @@ public function accepts(ContentType ...$contentTypes): bool $header = $this->headers->get(name: 'accept') ?? ''; /** @var array{mediaType:string,subType:string} */ - $mediaTypes = []; + $acceptedMediaTypes = []; foreach (str($header)->explode(separator: ',') as $acceptedType) { $acceptedType = str($acceptedType)->trim(); @@ -150,20 +150,20 @@ public function accepts(ContentType ...$contentTypes): bool continue; } - $mediaTypes[] = [ + $acceptedMediaTypes[] = [ 'mediaType' => $acceptedType->before('/')->toString(), 'subType' => $acceptedType->afterFirst('/')->beforeLast(';q')->toString(), ]; } - if (count($mediaTypes) === 0) { + if (count($acceptedMediaTypes) === 0) { return true; } foreach ($contentTypes as $contentType) { - [$mediaType, $subType] = explode('/', $contentType->value); + [$mediaType, $subType] = explode('/', string: $contentType->value); - foreach ($mediaTypes as $acceptedType) { + foreach ($acceptedMediaTypes as $acceptedType) { if ( ($acceptedType['mediaType'] === '*' || $acceptedType['mediaType'] === $mediaType) && ($acceptedType['subType'] === '*' || $acceptedType['subType'] === $subType) diff --git a/packages/http/src/IsResponse.php b/packages/http/src/IsResponse.php index 92b1bdc503..0eda90fb79 100644 --- a/packages/http/src/IsResponse.php +++ b/packages/http/src/IsResponse.php @@ -10,8 +10,9 @@ use Tempest\Http\Cookie\CookieManager; use Tempest\Http\Session\Session; use Tempest\View\View; +use UnitEnum; -use function Tempest\get; +use function Tempest\Container\get; /** @phpstan-require-implements \Tempest\Http\Response */ trait IsResponse @@ -23,73 +24,84 @@ trait IsResponse /** @var \Tempest\Http\Header[] */ private(set) array $headers = []; - public Session $session { - get => get(Session::class); - } - public CookieManager $cookieManager { get => get(CookieManager::class); } + public Session $session { + get => get(Session::class); + } + private(set) ?View $view = null; public function getHeader(string $name): ?Header { - return $this->headers[$name] ?? null; + return array_find( + array: $this->headers, + callback: fn (Header $header) => strcasecmp($header->name, $name) === 0, + ); } - public function addHeader(string $key, string $value): self + public function addHeaders(array $headers): self { - $this->headers[$key] ??= new Header($key); + foreach ($headers as $key => $values) { + if (! is_array($values)) { + $values = [$values]; + } - $this->headers[$key]->add($value); + foreach ($values as $value) { + $this->addHeader($key, $value); + } + } return $this; } - public function removeHeader(string $key): self + public function addHeader(string $key, string $value): self { - unset($this->headers[$key]); + $this->headers[$key] ??= new Header($key); + + $this->headers[$key]->add($value); return $this; } - public function addSession(string $name, mixed $value): self + public function removeHeader(string $key): self { - $this->session->set($name, $value); + unset($this->headers[$key]); return $this; } - public function removeSession(string $name): self + public function addCookie(Cookie $cookie): self { - $this->session->remove($name); + $this->cookieManager->add($cookie); return $this; } - public function destroySession(): self + public function removeCookie(string $key): self { - $this->session->destroy(); + $this->cookieManager->remove($key); return $this; } - public function addCookie(Cookie $cookie): self + public function addSession(string $name, mixed $value): self { - $this->cookieManager->add($cookie); + $this->session->set($name, $value); return $this; } - public function removeCookie(string $key): self + public function removeSession(string $name): self { - $this->cookieManager->remove($key); + $this->session->remove($name); return $this; } - public function flash(string $key, mixed $value): self + public function flash(string|UnitEnum $key, mixed $value): self { $this->session->flash($key, $value); diff --git a/packages/http/src/Mappers/PsrRequestToGenericRequestMapper.php b/packages/http/src/Mappers/PsrRequestToGenericRequestMapper.php index d215f2e5a2..0dd4278551 100644 --- a/packages/http/src/Mappers/PsrRequestToGenericRequestMapper.php +++ b/packages/http/src/Mappers/PsrRequestToGenericRequestMapper.php @@ -17,7 +17,7 @@ use Tempest\Support\Arr; use Throwable; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\arr; final readonly class PsrRequestToGenericRequestMapper implements Mapper @@ -63,7 +63,7 @@ public function map(mixed $from, mixed $to): GenericRequest 'path' => $from->getUri()->getPath(), 'query' => $query, 'files' => $uploads, - 'cookies' => Arr\filter(Arr\map_iterable( + 'cookies' => Arr\filter(Arr\map( array: $_COOKIE, map: function (string $value, string $key) { try { diff --git a/packages/http/src/Mappers/RequestToObjectMapper.php b/packages/http/src/Mappers/RequestToObjectMapper.php index 96f7b0655b..e79e482bbc 100644 --- a/packages/http/src/Mappers/RequestToObjectMapper.php +++ b/packages/http/src/Mappers/RequestToObjectMapper.php @@ -12,7 +12,7 @@ use Tempest\Reflection\PropertyReflector; use Tempest\Validation\Validator; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\arr; final readonly class RequestToObjectMapper implements Mapper diff --git a/packages/http/src/Mappers/RequestToPsrRequestMapper.php b/packages/http/src/Mappers/RequestToPsrRequestMapper.php index f6b4746708..b41ea04e21 100644 --- a/packages/http/src/Mappers/RequestToPsrRequestMapper.php +++ b/packages/http/src/Mappers/RequestToPsrRequestMapper.php @@ -5,6 +5,7 @@ namespace Tempest\Http\Mappers; use Laminas\Diactoros\ServerRequest; +use Laminas\Diactoros\Stream; use Psr\Http\Message\ServerRequestInterface as PsrRequest; use Tempest\Http\Request; use Tempest\Mapper\Mapper; @@ -19,7 +20,7 @@ public function canMap(mixed $from, mixed $to): bool public function map(mixed $from, mixed $to): PsrRequest { /** @var Request $from */ - return new ServerRequest( + $request = new ServerRequest( uploadedFiles: $from->files, uri: $from->uri, method: $from->method->value, @@ -28,5 +29,15 @@ public function map(mixed $from, mixed $to): PsrRequest queryParams: $from->query, parsedBody: $from->body, ); + + if ($from->raw !== null && count($from->body) === 0) { + $stream = new Stream('php://temp', mode: 'r+'); + $stream->write($from->raw); + $stream->rewind(); + + $request = $request->withBody($stream); + } + + return $request; } } diff --git a/packages/http/src/Request.php b/packages/http/src/Request.php index f2eb0ef31e..794d42c860 100644 --- a/packages/http/src/Request.php +++ b/packages/http/src/Request.php @@ -48,7 +48,7 @@ interface Request public function has(string $key): bool; - public function hasBody(string $key): bool; + public function hasBody(?string $key = null): bool; public function hasQuery(string $key): bool; diff --git a/packages/http/src/RequestHeaders.php b/packages/http/src/RequestHeaders.php index bd311e6d74..9221155fb4 100644 --- a/packages/http/src/RequestHeaders.php +++ b/packages/http/src/RequestHeaders.php @@ -8,6 +8,7 @@ use ArrayIterator; use IteratorAggregate; use LogicException; +use Tempest\Support\Str; use Traversable; final readonly class RequestHeaders implements ArrayAccess, IteratorAggregate @@ -17,11 +18,10 @@ */ public static function normalizeFromArray(array $headers): self { - $normalized = array_combine( + return new self(array_combine( array_map(strtolower(...), array_keys($headers)), - array_values($headers), - ); - return new self($normalized); + array_values(array_map(fn (mixed $value) => Str\parse($value), $headers)), + )); } /** @param array $headers */ @@ -41,9 +41,14 @@ public function offsetGet(mixed $offset): string return $this->get((string) $offset); } - public function get(string $name): ?string + public function get(string $name, ?string $default = null): ?string { - return $this->headers[strtolower($name)] ?? null; + $header = array_find( + array: $this->headers, + callback: fn (mixed $_, string $header) => strcasecmp($header, $name) === 0, + ); + + return $header ?? $default; } public function has(string $name): bool @@ -53,7 +58,7 @@ public function has(string $name): bool public function getHeader(string $name): Header { - return new Header(strtolower($name), array_filter([$this->get($name)])); + return new Header(mb_strtolower($name), array_filter([$this->get($name)])); } public function offsetSet(mixed $offset, mixed $value): void diff --git a/packages/http/src/Response.php b/packages/http/src/Response.php index 36954b5fda..ea7bac8b36 100644 --- a/packages/http/src/Response.php +++ b/packages/http/src/Response.php @@ -8,41 +8,80 @@ use JsonSerializable; use Tempest\Http\Cookie\Cookie; use Tempest\View\View; +use UnitEnum; interface Response { + /** + * Gets the status code of the response. + */ public Status $status { get; } - /** @var \Tempest\Http\Header[] $headers */ + /** + * Gets the headers of the response. + * + * @var \Tempest\Http\Header[] $headers + */ public array $headers { get; } + /** + * Gets the body of the response. + */ public View|string|array|Generator|JsonSerializable|null $body { get; } + /** + * Gets a header by its name, case insensitive. + */ public function getHeader(string $name): ?Header; + /** + * Adds a header to the response. + */ public function addHeader(string $key, string $value): self; + /** + * Removes a header from the response. + */ public function removeHeader(string $key): self; + /** + * Adds a value to the session. + */ public function addSession(string $name, mixed $value): self; - public function flash(string $key, mixed $value): self; - + /** + * Removes a value from the session. + */ public function removeSession(string $name): self; - public function destroySession(): self; + /** + * Flash a value to the session for the next request. + */ + public function flash(string|UnitEnum $key, mixed $value): self; + /** + * Adds a cookie to the response. + */ public function addCookie(Cookie $cookie): self; + /** + * Removes a cookie from the response. + */ public function removeCookie(string $key): self; + /** + * Sets the status code of the response. + */ public function setStatus(Status $status): self; + /** + * Sets the body of the response. + */ public function setBody(View|string|array|Generator|null $body): self; } diff --git a/packages/http/src/Responses/Back.php b/packages/http/src/Responses/Back.php index b6ced4b668..db427bad9a 100644 --- a/packages/http/src/Responses/Back.php +++ b/packages/http/src/Responses/Back.php @@ -7,11 +7,14 @@ use Tempest\Http\IsResponse; use Tempest\Http\Request; use Tempest\Http\Response; -use Tempest\Http\Session\Session; +use Tempest\Http\Session\PreviousUrl; use Tempest\Http\Status; -use function Tempest\get; +use function Tempest\Container\get; +/** + * This response is not fit for stateless requests. + */ final class Back implements Response { use IsResponse; @@ -19,20 +22,14 @@ final class Back implements Response public function __construct(?string $fallback = null) { $this->status = Status::FOUND; - $request = get(Request::class); - - $url = $request->headers['referer'] ?? $request->getSessionValue(Session::PREVIOUS_URL); - if ($url) { - $this->addHeader('Location', $url); - return; - } + $previousUrl = get(PreviousUrl::class); + $request = get(Request::class); - if ($fallback) { - $this->addHeader('Location', $fallback); - return; - } + $url = $previousUrl->get( + default: $request->headers['referer'] ?? $fallback ?? '/', + ); - $this->addHeader('Location', '/'); + $this->addHeader('Location', value: $url); } } diff --git a/packages/http/src/Responses/Invalid.php b/packages/http/src/Responses/Invalid.php deleted file mode 100644 index a77f0499d7..0000000000 --- a/packages/http/src/Responses/Invalid.php +++ /dev/null @@ -1,80 +0,0 @@ - get(Validator::class); - } - - /** - * @param class-string|null $targetClass - */ - public function __construct( - Request $request, - /** @var \Tempest\Validation\Rule[][] $failingRules */ - array $failingRules = [], - ?string $targetClass = null, - ) { - if ($referer = $request->headers['referer'] ?? null) { - $this->addHeader('Location', $referer); - $this->status = Status::FOUND; - } else { - $this->status = Status::BAD_REQUEST; - } - - $this->flash(Session::VALIDATION_ERRORS, $failingRules); - $this->flash(Session::ORIGINAL_VALUES, $this->filterSensitiveFields($request, $targetClass)); - $this->addHeader( - 'x-validation', - Json\encode( - arr($failingRules)->map( - fn (array $failingRulesForField) => arr($failingRulesForField)->map( - fn (Rule $rule) => $this->validator->getErrorMessage($rule), - )->toArray(), - )->toArray(), - ), - ); - } - - /** - * @param class-string|null $targetClass - */ - private function filterSensitiveFields(Request $request, ?string $targetClass): array - { - $body = $request->body; - - if ($targetClass === null) { - return $body; - } - - $reflector = new ClassReflector($targetClass); - - foreach ($reflector->getPublicProperties() as $property) { - if ($property->hasAttribute(SensitiveField::class)) { - unset($body[$property->getName()]); - } - } - - return $body; - } -} diff --git a/packages/http/src/Responses/Json.php b/packages/http/src/Responses/Json.php index b4fb86abed..86f04e7b84 100644 --- a/packages/http/src/Responses/Json.php +++ b/packages/http/src/Responses/Json.php @@ -13,11 +13,13 @@ final class Json implements Response { use IsResponse; - public function __construct(JsonSerializable|array|null $body = null) + public function __construct(JsonSerializable|array|null $body = null, ?Status $status = null, array $headers = []) { - $this->status = Status::OK; + $this->status = $status ?? Status::OK; $this->body = $body; + $this->addHeader('Accept', 'application/json'); $this->addHeader('Content-Type', 'application/json'); + $this->addHeaders($headers); } } diff --git a/packages/http/src/Responses/NotAcceptable.php b/packages/http/src/Responses/NotAcceptable.php new file mode 100644 index 0000000000..5336039a09 --- /dev/null +++ b/packages/http/src/Responses/NotAcceptable.php @@ -0,0 +1,19 @@ +status = Status::NOT_ACCEPTABLE; + } +} diff --git a/packages/http/src/Responses/Redirect.php b/packages/http/src/Responses/Redirect.php index f1cf9015da..f6fbcfc103 100644 --- a/packages/http/src/Responses/Redirect.php +++ b/packages/http/src/Responses/Redirect.php @@ -8,14 +8,21 @@ use Tempest\Http\Response; use Tempest\Http\Status; +/** + * This response is not fit for stateless requests. + */ final class Redirect implements Response { use IsResponse; public function __construct( private(set) string $to, + bool $permanent = false, ) { - $this->status = Status::FOUND; + $this->status = $permanent + ? Status::MOVED_PERMANENTLY + : Status::FOUND; + $this->addHeader('Location', $to); } diff --git a/packages/http/src/Responses/TooManyRequests.php b/packages/http/src/Responses/TooManyRequests.php new file mode 100644 index 0000000000..d9844252f5 --- /dev/null +++ b/packages/http/src/Responses/TooManyRequests.php @@ -0,0 +1,55 @@ +status = Status::TOO_MANY_REQUESTS; + + // Set body as array to ensure the original response is returned by exception handlers + // when this response is wrapped in HttpRequestFailed (see JsonExceptionRenderer) + $this->body = [ + 'error' => 'Too Many Requests', + 'retry_after' => $retryAfter, + ]; + + if ($retryAfter !== null) { + $this->addHeader('Retry-After', (string) $retryAfter); + } + + if ($limit !== null) { + $this->addHeader('X-RateLimit-Limit', (string) $limit); + } + + if ($remaining !== null) { + $this->addHeader('X-RateLimit-Remaining', (string) $remaining); + } + + if ($resetAt !== null) { + $this->addHeader('X-RateLimit-Reset', (string) $resetAt); + } + } +} diff --git a/packages/http/src/Session/CleanupSessionsCommand.php b/packages/http/src/Session/CleanupSessionsCommand.php index 829b8d55fb..a7335ceda3 100644 --- a/packages/http/src/Session/CleanupSessionsCommand.php +++ b/packages/http/src/Session/CleanupSessionsCommand.php @@ -22,10 +22,12 @@ public function __construct( #[Schedule(Every::MINUTE)] public function __invoke(): void { - $this->eventBus->listen(function (SessionDestroyed $event): void { - $this->console->keyValue((string) $event->id, "DESTROYED"); - }); + $this->eventBus->listen($this->onSessionDeleted(...)); + $this->sessionManager->deleteExpiredSessions(); + } - $this->sessionManager->cleanup(); + private function onSessionDeleted(SessionDeleted $event): void + { + $this->console->keyValue((string) $event->id, "DESTROYED"); } } diff --git a/packages/http/src/Session/CsrfTokenDidNotMatch.php b/packages/http/src/Session/CsrfTokenDidNotMatch.php deleted file mode 100644 index c71bedda02..0000000000 --- a/packages/http/src/Session/CsrfTokenDidNotMatch.php +++ /dev/null @@ -1,13 +0,0 @@ - $errors + */ + public function setErrors(array $errors): void + { + $this->session->flash(self::VALIDATION_ERRORS_KEY, $errors); + } + + /** + * Gets all validation errors. + * + * @return array + */ + public function getErrors(): array + { + return $this->session->get(self::VALIDATION_ERRORS_KEY, []); + } + + /** + * Gets validation errors for a specific field. + * + * @return FailingRule[] + */ + public function getErrorsFor(string $field): array + { + return $this->getErrors()[$field] ?? []; + } + + /** + * Checks if there are any validation errors. + */ + public function hasErrors(): bool + { + return $this->getErrors() !== []; + } + + /** + * Checks if a specific field has validation errors. + */ + public function hasError(string $field): bool + { + return $this->getErrorsFor($field) !== []; + } + + /** + * Stores each field's original form values for the next request. + * + * @param array $values + */ + public function setOriginalValues(array $values): void + { + $this->session->flash(self::ORIGINAL_VALUES_KEY, $values); + } + + /** + * Gets all original form values. The keys are the form fields. + * + * @return array + */ + public function values(): array + { + return $this->session->get(self::ORIGINAL_VALUES_KEY, []); + } + + /** + * Gets the original value for a specific field. + */ + public function getOriginalValueFor(string $field, mixed $default = null): mixed + { + return $this->values()[$field] ?? $default; + } + + /** + * Clears all validation errors and original values. + */ + public function clear(): void + { + $this->session->remove(self::VALIDATION_ERRORS_KEY); + $this->session->remove(self::ORIGINAL_VALUES_KEY); + } +} diff --git a/packages/http/src/Session/Installer/CreateSessionsTable.php b/packages/http/src/Session/Installer/CreateSessionsTable.php index 27171765b2..ad32c6f8da 100644 --- a/packages/http/src/Session/Installer/CreateSessionsTable.php +++ b/packages/http/src/Session/Installer/CreateSessionsTable.php @@ -17,11 +17,9 @@ final class CreateSessionsTable implements MigratesUp public function up(): QueryStatement { return new CreateTableStatement('sessions') - ->primary('id') - ->string('session_id') - ->text('data') + ->uuid('id') ->datetime('created_at') ->datetime('last_active_at') - ->index('session_id'); + ->text('data'); } } diff --git a/packages/http/src/Session/ManageSessionMiddleware.php b/packages/http/src/Session/ManageSessionMiddleware.php new file mode 100644 index 0000000000..d15e654e7c --- /dev/null +++ b/packages/http/src/Session/ManageSessionMiddleware.php @@ -0,0 +1,32 @@ +session->cleanup(); + $this->sessionManager->save($this->session); + $this->sessionManager->deleteExpiredSessions(); + } + } +} diff --git a/packages/http/src/Session/Managers/DatabaseSession.php b/packages/http/src/Session/Managers/DatabaseSession.php index 4652c5c96e..fb53f394de 100644 --- a/packages/http/src/Session/Managers/DatabaseSession.php +++ b/packages/http/src/Session/Managers/DatabaseSession.php @@ -6,15 +6,15 @@ use Tempest\Database\PrimaryKey; use Tempest\Database\Table; +use Tempest\Database\Uuid; use Tempest\DateTime\DateTime; #[Table('sessions')] final class DatabaseSession { + #[Uuid] public PrimaryKey $id; - public string $session_id; - public string $data; public DateTime $created_at; diff --git a/packages/http/src/Session/Managers/DatabaseSessionManager.php b/packages/http/src/Session/Managers/DatabaseSessionManager.php index 5cefb00bfb..788eaf5643 100644 --- a/packages/http/src/Session/Managers/DatabaseSessionManager.php +++ b/packages/http/src/Session/Managers/DatabaseSessionManager.php @@ -5,100 +5,116 @@ namespace Tempest\Http\Session\Managers; use Tempest\Clock\Clock; -use Tempest\Database\Database; +use Tempest\DateTime\FormatPattern; use Tempest\Http\Session\Session; use Tempest\Http\Session\SessionConfig; -use Tempest\Http\Session\SessionDestroyed; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; -use Tempest\Support\Arr; -use Tempest\Support\Arr\ArrayInterface; use function Tempest\Database\query; -use function Tempest\event; +use function Tempest\EventBus\event; final readonly class DatabaseSessionManager implements SessionManager { public function __construct( private Clock $clock, private SessionConfig $config, - private Database $database, ) {} - public function create(SessionId $id): Session + public function getOrCreate(SessionId $id): Session { - return $this->persist($id); - } - - public function set(SessionId $id, string $key, mixed $value): void - { - $this->persist($id, [...$this->getData($id), ...[$key => $value]]); - } + $now = $this->clock->now(); + $session = $this->load($id); - public function get(SessionId $id, string $key, mixed $default = null): mixed - { - $value = Arr\get_by_key($this->getData($id), $key, $default); + if ($session === null) { + $session = new Session( + id: $id, + createdAt: $now, + lastActiveAt: $now, + ); - if ($value instanceof ArrayInterface) { - return $value->toArray(); + event(new SessionCreated($session)); } - return $value; + return $session; } - public function all(SessionId $id): array + public function save(Session $session): void { - return $this->getData($id); - } + $session->lastActiveAt = $this->clock->now(); - public function remove(SessionId $id, string $key): void - { - $data = $this->getData($id); - $data = Arr\remove_keys($data, $key); + $existing = query(DatabaseSession::class) + ->select() + ->where('id', (string) $session->id) + ->first(); + + if ($existing === null) { + query(DatabaseSession::class) + ->insert( + id: (string) $session->id, + data: serialize($session->data), + created_at: $session->createdAt, + last_active_at: $session->lastActiveAt, + ) + ->execute(); + + return; + } - $this->persist($id, $data); + query(DatabaseSession::class) + ->update( + data: serialize($session->data), + last_active_at: $session->lastActiveAt, + ) + ->where('id', (string) $session->id) + ->execute(); } - public function destroy(SessionId $id): void + public function delete(Session $session): void { query(DatabaseSession::class) ->delete() - ->where('session_id', (string) $id) + ->where('id', (string) $session->id) ->execute(); - event(new SessionDestroyed($id)); + event(new SessionDeleted($session->id)); } - public function isValid(SessionId $id): bool + public function isValid(Session $session): bool { - $session = $this->resolve($id); - - if ($session === null) { - return false; - } - return $this->clock->now()->before( other: $session->lastActiveAt->plus($this->config->expiration), ); } - public function cleanup(): void + public function deleteExpiredSessions(): void { $expired = $this->clock ->now() ->minus($this->config->expiration); - query(DatabaseSession::class) - ->delete() - ->whereBefore('last_active_at', $expired) - ->execute(); + $expiredSessions = query(DatabaseSession::class) + ->select() + ->where('last_active_at < ?', $expired->format(FormatPattern::SQL_DATE_TIME)) + ->all(); + + foreach ($expiredSessions as $expiredSession) { + query(DatabaseSession::class) + ->delete() + ->where('id', $expiredSession->id) + ->execute(); + + event(new SessionDeleted(new SessionId((string) $expiredSession->id))); + } } - private function resolve(SessionId $id): ?Session + private function load(SessionId $id): ?Session { $session = query(DatabaseSession::class) ->select() - ->where('session_id', (string) $id) + ->where('id', (string) $id) ->first(); if ($session === null) { @@ -112,39 +128,4 @@ private function resolve(SessionId $id): ?Session data: unserialize($session->data), ); } - - /** - * @return array - */ - private function getData(SessionId $id): array - { - return $this->resolve($id)->data ?? []; - } - - /** - * @param array|null $data - */ - private function persist(SessionId $id, ?array $data = null): Session - { - $now = $this->clock->now(); - $session = $this->resolve($id) ?? new Session( - id: $id, - createdAt: $now, - lastActiveAt: $now, - ); - - if ($data !== null) { - $session->data = $data; - } - - query(DatabaseSession::class)->updateOrCreate([ - 'session_id' => (string) $id, - ], [ - 'data' => serialize($session->data), - 'created_at' => $session->createdAt, - 'last_active_at' => $now, - ]); - - return $session; - } } diff --git a/packages/http/src/Session/Managers/FileSessionManager.php b/packages/http/src/Session/Managers/FileSessionManager.php index 5043085f0e..af332393a6 100644 --- a/packages/http/src/Session/Managers/FileSessionManager.php +++ b/packages/http/src/Session/Managers/FileSessionManager.php @@ -7,13 +7,14 @@ use Tempest\Clock\Clock; use Tempest\Http\Session\Session; use Tempest\Http\Session\SessionConfig; -use Tempest\Http\Session\SessionDestroyed; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; use Tempest\Support\Filesystem; use Throwable; -use function Tempest\event; +use function Tempest\EventBus\event; use function Tempest\internal_storage_path; final readonly class FileSessionManager implements SessionManager @@ -23,60 +24,76 @@ public function __construct( private SessionConfig $sessionConfig, ) {} - public function create(SessionId $id): Session + public function getOrCreate(SessionId $id): Session { - return $this->persist($id); - } + $now = $this->clock->now(); + $session = $this->load($id); - public function set(SessionId $id, string $key, mixed $value): void - { - $this->persist($id, [...$this->getData($id), ...[$key => $value]]); + if ($session === null) { + $session = new Session( + id: $id, + createdAt: $now, + lastActiveAt: $now, + ); + + event(new SessionCreated($session)); + } + + return $session; } - public function get(SessionId $id, string $key, mixed $default = null): mixed + public function save(Session $session): void { - return $this->getData($id)[$key] ?? $default; + $session->lastActiveAt = $this->clock->now(); + + Filesystem\write_file( + filename: $this->getPath($session->id), + content: serialize($session), + flags: LOCK_EX, + ); } - public function remove(SessionId $id, string $key): void + public function delete(Session $session): void { - $data = $this->getData($id); + $path = $this->getPath($session->id); - unset($data[$key]); + Filesystem\delete($path); - $this->persist($id, $data); + event(new SessionDeleted($session->id)); } - public function destroy(SessionId $id): void + public function isValid(Session $session): bool { - unlink($this->getPath($id)); - - event(new SessionDestroyed($id)); + return $this->clock->now()->before( + other: $session->lastActiveAt->plus($this->sessionConfig->expiration), + ); } - public function isValid(SessionId $id): bool + public function deleteExpiredSessions(): void { - $session = $this->resolve($id); + $sessionFiles = glob(internal_storage_path($this->sessionConfig->path, '/*')); - if ($session === null) { - return false; + if ($sessionFiles === false) { + return; } - if (! ($session->lastActiveAt ?? null)) { - return false; - } + foreach ($sessionFiles as $sessionFile) { + $id = new SessionId(pathinfo($sessionFile, flags: PATHINFO_FILENAME)); + $session = $this->load($id); - return $this->clock->now()->before( - other: $session->lastActiveAt->plus($this->sessionConfig->expiration), - ); - } + if ($session === null) { + continue; + } - private function getPath(SessionId $id): string - { - return internal_storage_path($this->sessionConfig->path, (string) $id); + if ($this->isValid($session)) { + continue; + } + + $this->delete($session); + } } - private function resolve(SessionId $id): ?Session + private function load(SessionId $id): ?Session { $path = $this->getPath($id); @@ -85,74 +102,19 @@ private function resolve(SessionId $id): ?Session return null; } - $file_pointer = fopen($path, 'rb'); - flock($file_pointer, LOCK_SH); - - $content = Filesystem\read_file($path); - - flock($file_pointer, LOCK_UN); - fclose($file_pointer); - - return unserialize($content, ['allowed_classes' => true]); + return unserialize( + data: Filesystem\read_locked_file($path), + options: [ + 'allowed_classes' => true, + ], + ); } catch (Throwable) { return null; } } - public function all(SessionId $id): array - { - return $this->getData($id); - } - - /** - * @return array - */ - private function getData(SessionId $id): array - { - return $this->resolve($id)->data ?? []; - } - - /** - * @param array|null $data - */ - private function persist(SessionId $id, ?array $data = null): Session - { - $now = $this->clock->now(); - $session = $this->resolve($id) ?? new Session( - id: $id, - createdAt: $now, - lastActiveAt: $now, - ); - - $session->lastActiveAt = $now; - - if ($data !== null) { - $session->data = $data; - } - - Filesystem\write_file($this->getPath($id), serialize($session), LOCK_EX); - - return $session; - } - - public function cleanup(): void + private function getPath(SessionId $id): string { - $sessionFiles = glob(internal_storage_path($this->sessionConfig->path, '/*')); - - foreach ($sessionFiles as $sessionFile) { - $id = new SessionId(pathinfo($sessionFile, PATHINFO_FILENAME)); - - $session = $this->resolve($id); - - if ($session === null) { - continue; - } - - if ($this->isValid($session->id)) { - continue; - } - - $session->destroy(); - } + return internal_storage_path($this->sessionConfig->path, (string) $id); } } diff --git a/packages/http/src/Session/Managers/RedisSessionManager.php b/packages/http/src/Session/Managers/RedisSessionManager.php index ffa5c5b9d4..a2450ff4cc 100644 --- a/packages/http/src/Session/Managers/RedisSessionManager.php +++ b/packages/http/src/Session/Managers/RedisSessionManager.php @@ -5,149 +5,113 @@ namespace Tempest\Http\Session\Managers; use Tempest\Clock\Clock; +use Tempest\Http\Session\Config\RedisSessionConfig; use Tempest\Http\Session\Session; -use Tempest\Http\Session\SessionConfig; -use Tempest\Http\Session\SessionDestroyed; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; use Tempest\KeyValue\Redis\Redis; -use Tempest\Support\Str\ImmutableString; +use Tempest\Support\Str; use Throwable; -use function Tempest\event; +use function Tempest\EventBus\event; final readonly class RedisSessionManager implements SessionManager { public function __construct( private Clock $clock, private Redis $redis, - private SessionConfig $sessionConfig, + private RedisSessionConfig $config, ) {} - public function create(SessionId $id): Session + public function getOrCreate(SessionId $id): Session { - return $this->persist($id); - } + $now = $this->clock->now(); + $session = $this->load($id); - public function set(SessionId $id, string $key, mixed $value): void - { - $this->persist($id, [...$this->getData($id), ...[$key => $value]]); - } + if ($session === null) { + $session = new Session( + id: $id, + createdAt: $now, + lastActiveAt: $now, + ); - public function get(SessionId $id, string $key, mixed $default = null): mixed - { - return $this->getData($id)[$key] ?? $default; + event(new SessionCreated($session)); + } + + return $session; } - public function remove(SessionId $id, string $key): void + public function save(Session $session): void { - $data = $this->getData($id); - - unset($data[$key]); + $session->lastActiveAt = $this->clock->now(); - $this->persist($id, $data); + $this->redis->set( + key: $this->getKey($session->id), + value: serialize($session), + expiration: $this->config->expiration, + ); } - public function destroy(SessionId $id): void + public function delete(Session $session): void { - $this->redis->command('UNLINK', $this->getKey($id)); + $this->redis->command('UNLINK', $this->getKey($session->id)); - event(new SessionDestroyed($id)); + event(new SessionDeleted($session->id)); } - public function isValid(SessionId $id): bool + public function isValid(Session $session): bool { - $session = $this->resolve($id); - - if ($session === null) { - return false; - } - - if (! ($session->lastActiveAt ?? null)) { - return false; - } - return $this->clock->now()->before( - other: $session->lastActiveAt->plus($this->sessionConfig->expiration), + other: $session->lastActiveAt->plus($this->config->expiration), ); } - private function resolve(SessionId $id): ?Session - { - try { - $content = $this->redis->get($this->getKey($id)); - return unserialize($content, ['allowed_classes' => true]); - } catch (Throwable) { - return null; - } - } - - public function all(SessionId $id): array + public function deleteExpiredSessions(): void { - return $this->getData($id); - } - - /** - * @return array - */ - private function getData(SessionId $id): array - { - return $this->resolve($id)->data ?? []; - } + $cursor = '0'; - /** - * @param array|null $data - */ - private function persist(SessionId $id, ?array $data = null): Session - { - $now = $this->clock->now(); - $session = $this->resolve($id) ?? new Session( - id: $id, - createdAt: $now, - lastActiveAt: $now, - ); + do { + /** @var array $keys */ + [$cursor, $keys] = $this->redis->command('SCAN', $cursor, 'MATCH', "{$this->config->prefix}*", 'COUNT', '100'); - $session->lastActiveAt = $now; + foreach ($keys as $key) { + $sessionId = $this->getSessionIdFromKey($key); + $session = $this->load($sessionId); - if ($data !== null) { - $session->data = $data; - } + if ($session === null) { + continue; + } - $this->redis->set($this->getKey($id), serialize($session), $this->sessionConfig->expiration); + if ($this->isValid($session)) { + continue; + } - return $session; + $this->delete($session); + } + } while ($cursor !== '0'); } - private function getKey(SessionId $id): string + private function load(SessionId $id): ?Session { - return sprintf('%s%s', $this->sessionConfig->prefix, $id); + try { + return unserialize( + data: $this->redis->get($this->getKey($id)), + options: ['allowed_classes' => true], + ); + } catch (Throwable) { + return null; + } } - private function getSessionIdFromKey(string $key): SessionId + private function getKey(SessionId $id): string { - return new SessionId( - new ImmutableString($key) - ->afterFirst($this->sessionConfig->prefix) - ->toString(), - ); + return sprintf('%s%s', $this->config->prefix, $id); } - public function cleanup(): void + private function getSessionIdFromKey(string $key): SessionId { - $cursor = '0'; - - do { - $result = $this->redis->command('SCAN', $cursor, 'MATCH', $this->getKey(new SessionId('*')), 'COUNT', '100'); - $cursor = $result[0]; - foreach ($result[1] as $key) { - $sessionId = $this->getSessionIdFromKey($key); - - if ($this->isValid($sessionId)) { - continue; - } - - $this->destroy($sessionId); - } - } while ($cursor !== '0'); + return new SessionId(Str\after_first($key, $this->config->prefix)); } } diff --git a/packages/http/src/Session/PreviousUrl.php b/packages/http/src/Session/PreviousUrl.php new file mode 100644 index 0000000000..f5b78c094d --- /dev/null +++ b/packages/http/src/Session/PreviousUrl.php @@ -0,0 +1,74 @@ +shouldNotTrack($request)) { + return; + } + + $this->session->set(self::PREVIOUS_URL_SESSION_KEY, $request->uri); + } + + /** + * Gets the previous URL, or a default fallback. + */ + public function get(string $default = '/'): string + { + return $this->session->get(self::PREVIOUS_URL_SESSION_KEY, $default); + } + + /** + * Stores the URL where user was trying to go before being redirected. After authentication, the user should be redirect to that URL. + */ + public function setIntended(string $url): void + { + $this->session->set(self::INTENDED_URL_SESSION_KEY, $url); + } + + /** + * Gets and consume the intended URL. + */ + public function getIntended(string $default = '/'): string + { + return $this->session->consume(self::INTENDED_URL_SESSION_KEY, $default); + } + + private function shouldNotTrack(Request $request): bool + { + if ($request->headers->get('x-requested-with') === 'XMLHttpRequest') { + return true; + } + + if ($request->method !== Method::GET) { + return true; + } + + if ($request->headers->get('purpose') === 'prefetch') { + return true; + } + + return false; + } +} diff --git a/packages/http/src/Session/Resolvers/CookieSessionIdResolver.php b/packages/http/src/Session/Resolvers/CookieSessionIdResolver.php index 0d86263efa..4c5077ac76 100644 --- a/packages/http/src/Session/Resolvers/CookieSessionIdResolver.php +++ b/packages/http/src/Session/Resolvers/CookieSessionIdResolver.php @@ -14,6 +14,7 @@ use Tempest\Http\Session\SessionConfig; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionIdResolver; +use Tempest\Support\Str; use function Tempest\Support\str; @@ -44,7 +45,7 @@ public function resolve(): SessionId value: $id, expiresAt: $this->clock->now()->plus($this->sessionConfig->expiration), path: '/', - secure: str($this->appConfig->baseUri)->startsWith('https'), + secure: Str\starts_with($this->appConfig->baseUri, needles: 'https'), httpOnly: true, sameSite: SameSite::LAX, )); diff --git a/packages/http/src/Session/Resolvers/HeaderSessionIdResolver.php b/packages/http/src/Session/Resolvers/HeaderSessionIdResolver.php index bd2a272ea7..ecc26b7245 100644 --- a/packages/http/src/Session/Resolvers/HeaderSessionIdResolver.php +++ b/packages/http/src/Session/Resolvers/HeaderSessionIdResolver.php @@ -10,6 +10,8 @@ use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionIdResolver; +use function Tempest\Support\str; + final readonly class HeaderSessionIdResolver implements SessionIdResolver { public function __construct( diff --git a/packages/http/src/Session/Session.php b/packages/http/src/Session/Session.php index fb275508df..bcd5d2d160 100644 --- a/packages/http/src/Session/Session.php +++ b/packages/http/src/Session/Session.php @@ -4,59 +4,46 @@ namespace Tempest\Http\Session; +use Tempest\DateTime\DateTime; use Tempest\DateTime\DateTimeInterface; -use Tempest\Support\Random; - -use function Tempest\get; - +use Tempest\Support\Str; +use UnitEnum; + +/** + * Represents the current session. + * + * @see ManageSessionMiddleware + * @see SessionManager + */ final class Session { - public const string VALIDATION_ERRORS = '#validation_errors'; - - public const string ORIGINAL_VALUES = '#original_values'; - - public const string PREVIOUS_URL = '#previous_url'; - - public const string CSRF_TOKEN_KEY = '#csrf_token'; - - private array $expiredKeys = []; - - private SessionManager $manager { - get => get(SessionManager::class); - } - /** - * Session token used for cross-site request forgery protection. + * Stores the keys for session values that have expired. */ - public string $token { - get { - if (! $this->get(self::CSRF_TOKEN_KEY)) { - $this->set(self::CSRF_TOKEN_KEY, Random\uuid()); - } - - return $this->get(self::CSRF_TOKEN_KEY); - } - } + private array $expiredKeys = []; public function __construct( - public SessionId $id, - public DateTimeInterface $createdAt, + private(set) SessionId $id, + private(set) DateTimeInterface $createdAt, public DateTimeInterface $lastActiveAt, - /** @var array */ - public array $data = [], + /** @var array */ + private(set) array $data = [], ) {} - public function set(string $key, mixed $value): void + /** + * Sets a value in the session. + */ + public function set(string|UnitEnum $key, mixed $value): void { - $this->manager->set($this->id, $key, $value); + $this->data[Str\parse($key)] = $value; } /** * Stores a value in the session that will be available for the next request only. */ - public function flash(string $key, mixed $value): void + public function flash(string|UnitEnum $key, mixed $value): void { - $this->manager->set($this->id, $key, new FlashValue($value)); + $this->data[Str\parse($key)] = new FlashValue($value); } /** @@ -64,7 +51,7 @@ public function flash(string $key, mixed $value): void */ public function reflash(): void { - foreach ($this->manager->all($this->id) as $key => $value) { + foreach ($this->data as $key => $value) { if (! $value instanceof FlashValue) { continue; } @@ -73,9 +60,13 @@ public function reflash(): void } } - public function get(string $key, mixed $default = null): mixed + /** + * Retrieves a value from the session. + */ + public function get(string|UnitEnum $key, mixed $default = null): mixed { - $value = $this->manager->get($this->id, $key, $default); + $key = Str\parse($key); + $value = $this->data[$key] ?? $default; if ($value instanceof FlashValue) { $this->expiredKeys[$key] = $key; @@ -85,32 +76,12 @@ public function get(string $key, mixed $default = null): mixed return $value; } - /** @return \Tempest\Validation\Rule[] */ - public function getErrorsFor(string $name): array - { - return $this->get(self::VALIDATION_ERRORS)[$name] ?? []; - } - - public function getOriginalValueFor(string $name, mixed $default = ''): mixed - { - return $this->get(self::ORIGINAL_VALUES)[$name] ?? $default; - } - - public function getPreviousUrl(): string - { - return $this->get(self::PREVIOUS_URL, default: ''); - } - - public function setPreviousUrl(string $url): void - { - $this->set(self::PREVIOUS_URL, $url); - } - /** * Retrieves the value for the given key and removes it from the session. */ - public function consume(string $key, mixed $default = null): mixed + public function consume(string|UnitEnum $key, mixed $default = null): mixed { + $key = Str\parse($key); $value = $this->get($key, $default); $this->remove($key); @@ -118,30 +89,61 @@ public function consume(string $key, mixed $default = null): mixed return $value; } + /** + * Retrieves all values from the session. + */ public function all(): array { - return $this->manager->all($this->id); + return $this->data; } - public function remove(string $key): void + /** + * Removes a value from the session. + */ + public function remove(string|UnitEnum $key): void { - $this->manager->remove($this->id, $key); + $key = Str\parse($key); + + if (isset($this->data[$key])) { + unset($this->data[$key]); + } } - public function destroy(): void + /** + * Cleans up expired session values. + */ + public function cleanup(): void + { + foreach ($this->expiredKeys as $key) { + $this->remove($key); + } + } + + /** + * Clears all values from the session. + */ + public function clear(): void { - $this->manager->destroy($this->id); + $this->data = []; } - public function isValid(): bool + public function __serialize(): array { - return $this->manager->isValid($this->id); + return [ + 'id' => (string) $this->id, + 'created_at' => $this->createdAt->getTimestamp()->getSeconds(), + 'last_active_at' => $this->lastActiveAt->getTimestamp()->getSeconds(), + 'data' => $this->data, + 'expired_keys' => $this->expiredKeys, + ]; } - public function cleanup(): void + public function __unserialize(array $data): void { - foreach ($this->expiredKeys as $key) { - $this->manager->remove($this->id, $key); - } + $this->id = new SessionId($data['id']); + $this->createdAt = DateTime::fromTimestamp($data['created_at']); + $this->lastActiveAt = DateTime::fromTimestamp($data['last_active_at']); + $this->data = $data['data']; + $this->expiredKeys = $data['expired_keys']; } } diff --git a/packages/http/src/Session/SessionCreated.php b/packages/http/src/Session/SessionCreated.php new file mode 100644 index 0000000000..6ac675988e --- /dev/null +++ b/packages/http/src/Session/SessionCreated.php @@ -0,0 +1,13 @@ +get(SessionManager::class); $sessionIdResolver = $container->get(SessionIdResolver::class); - return $sessionManager->create($sessionIdResolver->resolve()); + return $sessionManager->getOrCreate($sessionIdResolver->resolve()); } } diff --git a/packages/http/src/Session/SessionManager.php b/packages/http/src/Session/SessionManager.php index 577eaafc46..cc904b1d9b 100644 --- a/packages/http/src/Session/SessionManager.php +++ b/packages/http/src/Session/SessionManager.php @@ -6,19 +6,28 @@ interface SessionManager { - public function create(SessionId $id): Session; - - public function set(SessionId $id, string $key, mixed $value): void; - - public function get(SessionId $id, string $key, mixed $default = null): mixed; - - public function all(SessionId $id): array; - - public function remove(SessionId $id, string $key): void; - - public function destroy(SessionId $id): void; - - public function isValid(SessionId $id): bool; - - public function cleanup(): void; + /** + * Retrieves or creates a session based on its identifier. + */ + public function getOrCreate(SessionId $id): Session; + + /** + * Saves the session data to the server. + */ + public function save(Session $session): void; + + /** + * Removes the session from the server. + */ + public function delete(Session $session): void; + + /** + * Determines whether the session is still valid. + */ + public function isValid(Session $session): bool; + + /** + * Removes all expired sessions from the server. + */ + public function deleteExpiredSessions(): void; } diff --git a/packages/http/src/Session/TrackPreviousUrlMiddleware.php b/packages/http/src/Session/TrackPreviousUrlMiddleware.php new file mode 100644 index 0000000000..a9f4121f1e --- /dev/null +++ b/packages/http/src/Session/TrackPreviousUrlMiddleware.php @@ -0,0 +1,24 @@ +previousUrl->track($request); + + return $next($request); + } +} diff --git a/packages/http/src/Session/VerifyCsrfMiddleware.php b/packages/http/src/Session/VerifyCsrfMiddleware.php deleted file mode 100644 index 36753b8baf..0000000000 --- a/packages/http/src/Session/VerifyCsrfMiddleware.php +++ /dev/null @@ -1,94 +0,0 @@ -cookies->add(new Cookie( - key: self::CSRF_COOKIE_KEY, - value: $this->session->token, - expiresAt: $this->clock->now()->plus($this->sessionConfig->expiration), - path: '/', - secure: Str\starts_with($this->appConfig->baseUri, 'https'), - )); - - if ($this->shouldSkipCheck($request)) { - return $next($request); - } - - $this->ensureTokenMatches($request); - - return $next($request); - } - - private function shouldSkipCheck(Request $request): bool - { - if (in_array($request->method, [Method::GET, Method::HEAD, Method::OPTIONS], strict: true)) { - return true; - } - - if ($this->appConfig->environment->isTesting()) { - return true; - } - - return false; - } - - private function ensureTokenMatches(Request $request): void - { - $tokenFromRequest = $request->get( - key: Session::CSRF_TOKEN_KEY, - ); - - if (! $tokenFromRequest && $request->headers->has(self::CSRF_HEADER_KEY)) { - try { - $tokenFromRequest = $this->encrypter->decrypt( - urldecode($request->headers->get(self::CSRF_HEADER_KEY)), - ); - } catch (EncryptionException|JsonCouldNotBeDecoded) { - throw new CsrfTokenDidNotMatch(); - } - } - - if (! $tokenFromRequest) { - throw new CsrfTokenDidNotMatch(); - } - - if (! hash_equals($this->session->token, $tokenFromRequest)) { - throw new CsrfTokenDidNotMatch(); - } - } -} diff --git a/packages/http/src/Session/x-csrf-token.view.php b/packages/http/src/Session/x-csrf-token.view.php deleted file mode 100644 index f8ea7e763f..0000000000 --- a/packages/http/src/Session/x-csrf-token.view.php +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/packages/http/src/Status.php b/packages/http/src/Status.php index 2080f63106..ae4feedb66 100644 --- a/packages/http/src/Status.php +++ b/packages/http/src/Status.php @@ -4,6 +4,8 @@ namespace Tempest\Http; +use ValueError; + enum Status: int { // Informational @@ -79,9 +81,19 @@ enum Status: int case NOT_EXTENDED = 510; case NETWORK_AUTHENTICATION_REQUIRED = 511; - public static function code(int $code): self + public static function fromCode(int $code): self { - return self::from($code); + try { + return self::from($code); + } catch (ValueError) { + return match (intdiv($code, 100) * 100) { + 100 => self::CONTINUE, + 200 => self::OK, + 300 => self::MULTIPLE_CHOICES, + 400 => self::BAD_REQUEST, + default => self::INTERNAL_SERVER_ERROR, + }; + } } public function description(): string diff --git a/packages/http/src/functions.php b/packages/http/src/functions.php index a3552f8ff2..5e28535395 100644 --- a/packages/http/src/functions.php +++ b/packages/http/src/functions.php @@ -1,14 +1,3 @@ token; -} diff --git a/packages/http/tests/StatusTest.php b/packages/http/tests/StatusTest.php index f8637d932d..e14d5fbd75 100644 --- a/packages/http/tests/StatusTest.php +++ b/packages/http/tests/StatusTest.php @@ -5,6 +5,8 @@ namespace Tempest\Http\Tests; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Tempest\Http\Status; @@ -25,7 +27,7 @@ private static function descriptionToStatus(string $description): Status #[DataProvider('provide_status_code_cases')] public function test_status_code(int $code, string $description): void { - $status = Status::code($code); + $status = Status::fromCode($code); $this->assertSame( self::descriptionToStatus($description), @@ -143,4 +145,16 @@ public static function provide_status_code_cases(): iterable [511, 'Network Authentication Required'], ]; } + + #[TestWith([150, Status::CONTINUE])] + #[TestWith([250, Status::OK])] + #[TestWith([399, Status::MULTIPLE_CHOICES])] + #[TestWith([450, Status::BAD_REQUEST])] + #[TestWith([550, Status::INTERNAL_SERVER_ERROR])] + #[TestWith([650, Status::INTERNAL_SERVER_ERROR])] + #[Test] + public function unknown_status_code_maps_to_class_base(int $code, Status $expected): void + { + $this->assertSame($expected, Status::fromCode($code)); + } } diff --git a/packages/icon/composer.json b/packages/icon/composer.json index 1bf22c13a7..78fad985e4 100644 --- a/packages/icon/composer.json +++ b/packages/icon/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/container": "dev-main", "tempest/http-client": "dev-main", "tempest/support": "dev-main", diff --git a/packages/icon/src/Icon.php b/packages/icon/src/Icon.php index 96d3166280..c97a0b3c37 100644 --- a/packages/icon/src/Icon.php +++ b/packages/icon/src/Icon.php @@ -4,9 +4,7 @@ use Exception; use Tempest\EventBus\EventBus; -use Tempest\Http\GenericRequest; use Tempest\Http\HttpRequestFailed; -use Tempest\Http\Method; use Tempest\Http\Status; use Tempest\HttpClient\HttpClient; use Tempest\Support\Str; @@ -64,7 +62,6 @@ private function fetchSvg(string $collection, string $icon): ?string if ($response->status !== Status::OK) { throw new HttpRequestFailed( - request: new GenericRequest(Method::GET, $url), status: $response->status, cause: $response, ); diff --git a/packages/icon/src/functions.php b/packages/icon/src/functions.php index 90e004c047..bf26b93859 100644 --- a/packages/icon/src/functions.php +++ b/packages/icon/src/functions.php @@ -2,7 +2,7 @@ namespace Tempest\Icon; -use function Tempest\get; +use function Tempest\Container\get; /** * Renders an icon as an SVG snippet. If the icon is not cached, it will be diff --git a/packages/intl/bin/plural-rules.php b/packages/intl/bin/plural-rules.php index f413c49043..19de138307 100755 --- a/packages/intl/bin/plural-rules.php +++ b/packages/intl/bin/plural-rules.php @@ -13,7 +13,7 @@ use Tempest\Support\Filesystem; use Tempest\Support\Json; -use function Tempest\get; +use function Tempest\Container\get; final class PluralRulesMatcherGenerator { diff --git a/packages/intl/composer.json b/packages/intl/composer.json index 4ff8ef7693..6de1f2b9d3 100644 --- a/packages/intl/composer.json +++ b/packages/intl/composer.json @@ -4,12 +4,13 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "doctrine/inflector": "^2.0", "symfony/yaml": "^7.3", "tempest/core": "dev-main", "tempest/container": "dev-main", - "tempest/support": "dev-main" + "tempest/support": "dev-main", + "ext-intl": "*" }, "suggest": { "tempest/datetime": "In order to use the `datetime` function", diff --git a/packages/intl/src/Catalog/CatalogInitializer.php b/packages/intl/src/Catalog/CatalogInitializer.php index 504810ea2e..db4ac01175 100644 --- a/packages/intl/src/Catalog/CatalogInitializer.php +++ b/packages/intl/src/Catalog/CatalogInitializer.php @@ -30,6 +30,7 @@ public function initialize(Container $container): Catalog $messages = match (true) { Str\ends_with($path, '.json') => Json\decode($contents), Str\ends_with($path, ['.yaml', '.yml']) => Yaml::parse($contents), + default => throw new \RuntimeException("Unsupported translation file format: {$path}"), }; foreach (Arr\dot($messages) as $key => $message) { diff --git a/packages/intl/src/MessageFormat/Formatter/FormattingException.php b/packages/intl/src/MessageFormat/Formatter/FormattingException.php index 7a9ed0c5df..abb0c1de06 100644 --- a/packages/intl/src/MessageFormat/Formatter/FormattingException.php +++ b/packages/intl/src/MessageFormat/Formatter/FormattingException.php @@ -2,9 +2,9 @@ namespace Tempest\Intl\MessageFormat\Formatter; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; -final class FormattingException extends \Exception implements HasContext +final class FormattingException extends \Exception implements ProvidesContext { public function __construct( string $message, diff --git a/packages/intl/src/Pluralizer/InflectorPluralizer.php b/packages/intl/src/Pluralizer/InflectorPluralizer.php index 9ff390a03c..ba5381654f 100644 --- a/packages/intl/src/Pluralizer/InflectorPluralizer.php +++ b/packages/intl/src/Pluralizer/InflectorPluralizer.php @@ -26,7 +26,7 @@ public function pluralize(Stringable|string $value, int|array|Countable $count = // @mago-expect lint:identity-comparison if (abs($count) === 1 || preg_match('/^(.*)[A-Za-z0-9\x{0080}-\x{FFFF}]$/u', (string) $value) == 0) { - return $value; + return (string) $value; } return $this->matchCase($this->inflector->pluralize((string) $value), $value); diff --git a/packages/intl/src/functions.php b/packages/intl/src/functions.php index a37226b75d..768d7ce0da 100644 --- a/packages/intl/src/functions.php +++ b/packages/intl/src/functions.php @@ -9,7 +9,7 @@ use Tempest\Intl\Pluralizer\Pluralizer; use Tempest\Intl\Translator; -use function Tempest\get; +use function Tempest\Container\get; /** * Translates the given key with optional arguments. diff --git a/packages/kv-store/composer.json b/packages/kv-store/composer.json index 629555509d..768fd6dc57 100644 --- a/packages/kv-store/composer.json +++ b/packages/kv-store/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/support": "dev-main", "tempest/datetime": "dev-main", "tempest/event-bus": "dev-main" diff --git a/packages/kv-store/src/Redis/PredisClient.php b/packages/kv-store/src/Redis/PredisClient.php index 002a8bd3a4..d2f01d3773 100644 --- a/packages/kv-store/src/Redis/PredisClient.php +++ b/packages/kv-store/src/Redis/PredisClient.php @@ -23,7 +23,13 @@ public function connect(): void return; } - $this->client->connect(); + set_error_handler(static fn () => true); + + try { + $this->client->connect(); + } finally { + restore_error_handler(); + } } public function disconnect(): void diff --git a/packages/log/composer.json b/packages/log/composer.json index bc0a48a29b..8453b6e447 100644 --- a/packages/log/composer.json +++ b/packages/log/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "monolog/monolog": "^3.7.0", "psr/log": "^3.0.0", "tempest/container": "dev-main" diff --git a/packages/log/src/Channels/AppendLogChannel.php b/packages/log/src/Channels/AppendLogChannel.php index 0fc52f6a84..35de7b4e37 100644 --- a/packages/log/src/Channels/AppendLogChannel.php +++ b/packages/log/src/Channels/AppendLogChannel.php @@ -8,18 +8,31 @@ use Monolog\Level; use Monolog\Processor\PsrLogMessageProcessor; use Tempest\Log\LogChannel; +use Tempest\Log\LogLevel; final readonly class AppendLogChannel implements LogChannel { + /** + * @param string $path The log file path. + * @param bool $useLocking Whether to try to lock log file before doing any writes. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write). + */ public function __construct( - private string $path, - private bool $bubble = true, - private ?int $filePermission = null, - private bool $useLocking = false, + private(set) string $path, + private(set) bool $useLocking = false, + private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) bool $bubble = true, + private(set) ?int $filePermission = null, ) {} public function getHandlers(Level $level): array { + if (! $this->minimumLogLevel->includes(LogLevel::fromMonolog($level))) { + return []; + } + return [ new StreamHandler( stream: $this->path, @@ -37,9 +50,4 @@ public function getProcessors(): array new PsrLogMessageProcessor(), ]; } - - public function getPath(): string - { - return $this->path; - } } diff --git a/packages/log/src/Channels/DailyLogChannel.php b/packages/log/src/Channels/DailyLogChannel.php index 18534524d0..938c165760 100644 --- a/packages/log/src/Channels/DailyLogChannel.php +++ b/packages/log/src/Channels/DailyLogChannel.php @@ -8,27 +8,42 @@ use Monolog\Processor\PsrLogMessageProcessor; use Tempest\Log\FileHandlers\RotatingFileHandler; use Tempest\Log\LogChannel; +use Tempest\Log\LogLevel; final readonly class DailyLogChannel implements LogChannel { + /** + * This channel writes logs to a file that is rotated daily. + * + * @param string $path The base log file name. + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param bool $lockFilesDuringWrites Whether to try to lock log file before doing any writes. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write) + */ public function __construct( private string $path, private int $maxFiles = 31, + private LogLevel $minimumLogLevel = LogLevel::DEBUG, + private bool $lockFilesDuringWrites = false, private bool $bubble = true, private ?int $filePermission = null, - private bool $useLocking = false, ) {} public function getHandlers(Level $level): array { + if (! $this->minimumLogLevel->includes(LogLevel::fromMonolog($level))) { + return []; + } + return [ new RotatingFileHandler( filename: $this->path, - maxFiles: $this->maxFiles, + maxFiles: $this->maxFiles ?? 0, level: $level, bubble: $this->bubble, filePermission: $this->filePermission, - useLocking: $this->useLocking, + useLocking: $this->lockFilesDuringWrites, dateFormat: RotatingFileHandler::FILE_PER_DAY, ), ]; diff --git a/packages/log/src/Channels/Slack/PresentationMode.php b/packages/log/src/Channels/Slack/PresentationMode.php new file mode 100644 index 0000000000..76b25ce962 --- /dev/null +++ b/packages/log/src/Channels/Slack/PresentationMode.php @@ -0,0 +1,21 @@ +minimumLogLevel->includes(LogLevel::fromMonolog($level))) { + return []; + } + + return [ + new SlackWebhookHandler( + webhookUrl: $this->webhookUrl, + channel: $this->channelId, + username: $this->username, + level: $level, + useAttachment: $this->mode === PresentationMode::BLOCKS || $this->mode === PresentationMode::BLOCKS_WITH_CONTEXT, + includeContextAndExtra: $this->mode === PresentationMode::BLOCKS_WITH_CONTEXT, + ), + ]; + } + + public function getProcessors(): array + { + return [ + new PsrLogMessageProcessor(), + ]; + } +} diff --git a/packages/log/src/Channels/SysLogChannel.php b/packages/log/src/Channels/SysLogChannel.php new file mode 100644 index 0000000000..5e5caa78da --- /dev/null +++ b/packages/log/src/Channels/SysLogChannel.php @@ -0,0 +1,53 @@ +minimumLogLevel->includes(LogLevel::fromMonolog($level))) { + return []; + } + + return [ + new SyslogHandler( + ident: $this->identity, + facility: $this->facility, + level: $level, + bubble: $this->bubble, + logopts: $this->flags, + ), + ]; + } + + public function getProcessors(): array + { + return [ + new PsrLogMessageProcessor(), + ]; + } +} diff --git a/packages/log/src/Channels/WeeklyLogChannel.php b/packages/log/src/Channels/WeeklyLogChannel.php index af52ab6898..007d582ffe 100644 --- a/packages/log/src/Channels/WeeklyLogChannel.php +++ b/packages/log/src/Channels/WeeklyLogChannel.php @@ -8,19 +8,33 @@ use Monolog\Processor\PsrLogMessageProcessor; use Tempest\Log\FileHandlers\RotatingFileHandler; use Tempest\Log\LogChannel; +use Tempest\Log\LogLevel; final readonly class WeeklyLogChannel implements LogChannel { + /** + * @param string $path The base log file name. + * @param int $maxFiles The maximal amount of files to keep. + * @param bool $lockFilesDuringWrites Whether to try to lock log file before doing any writes. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not. + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write). + */ public function __construct( private string $path, private int $maxFiles = 5, + private bool $lockFilesDuringWrites = false, + private LogLevel $minimumLogLevel = LogLevel::DEBUG, private bool $bubble = true, private ?int $filePermission = null, - private bool $useLocking = false, ) {} public function getHandlers(Level $level): array { + if (! $this->minimumLogLevel->includes(LogLevel::fromMonolog($level))) { + return []; + } + return [ new RotatingFileHandler( filename: $this->path, @@ -28,7 +42,7 @@ public function getHandlers(Level $level): array level: $level, bubble: $this->bubble, filePermission: $this->filePermission, - useLocking: $this->useLocking, + useLocking: $this->lockFilesDuringWrites, dateFormat: RotatingFileHandler::FILE_PER_WEEK, ), ]; diff --git a/packages/log/src/Config/DailyLogConfig.php b/packages/log/src/Config/DailyLogConfig.php new file mode 100644 index 0000000000..281c7ef383 --- /dev/null +++ b/packages/log/src/Config/DailyLogConfig.php @@ -0,0 +1,47 @@ + [ + new DailyLogChannel( + path: $this->path, + maxFiles: $this->maxFiles, + minimumLogLevel: $this->minimumLogLevel, + lockFilesDuringWrites: $this->lockFilesDuringWrites, + filePermission: $this->filePermission, + ), + ...$this->channels, + ]; + } + + /** + * A logging configuration that creates a new log file each day and retains a maximum number of files. + * + * @param string $path The base log file name. + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param array $channels Additional channels to include in the configuration. + * @param bool $lockFilesDuringWrites Whether to try to lock log file before doing any writes. + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) string $path, + private(set) int $maxFiles = 31, + private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) array $channels = [], + private(set) bool $lockFilesDuringWrites = false, + private(set) ?int $filePermission = null, + private(set) ?string $prefix = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/MultipleChannelsLogConfig.php b/packages/log/src/Config/MultipleChannelsLogConfig.php new file mode 100644 index 0000000000..7ffef9125a --- /dev/null +++ b/packages/log/src/Config/MultipleChannelsLogConfig.php @@ -0,0 +1,27 @@ + $this->channels; + } + + /** + * A logging configuration that uses multiple log channels. + * + * @param LogChannel[] $channels The log channels to which log messages will be sent. + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) array $channels, + private(set) ?string $prefix, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/NullLogConfig.php b/packages/log/src/Config/NullLogConfig.php new file mode 100644 index 0000000000..d72a4b711e --- /dev/null +++ b/packages/log/src/Config/NullLogConfig.php @@ -0,0 +1,21 @@ + []; + } + + /** + * A logging configuration that does not log anything. + */ + public function __construct( + private(set) ?string $prefix = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/SimpleLogConfig.php b/packages/log/src/Config/SimpleLogConfig.php new file mode 100644 index 0000000000..3d12dfcbad --- /dev/null +++ b/packages/log/src/Config/SimpleLogConfig.php @@ -0,0 +1,44 @@ + [ + new AppendLogChannel( + path: $this->path, + useLocking: $this->useLocking, + minimumLogLevel: $this->minimumLogLevel, + filePermission: $this->filePermission, + ), + ...$this->channels, + ]; + } + + /** + * A basic logging configuration that appends all logs to a single file. + * + * @param string $path The log file path. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param array $channels Additional channels to include in the configuration. + * @param bool $useLocking Whether to try to lock log file before doing any writes. + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write). + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) string $path, + private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) array $channels = [], + private(set) bool $useLocking = false, + private(set) ?int $filePermission = null, + private(set) ?string $prefix = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/SlackLogConfig.php b/packages/log/src/Config/SlackLogConfig.php new file mode 100644 index 0000000000..b4f471d6fc --- /dev/null +++ b/packages/log/src/Config/SlackLogConfig.php @@ -0,0 +1,46 @@ + [ + new SlackLogChannel( + webhookUrl: $this->webhookUrl, + channelId: $this->channelId, + username: $this->username, + mode: $this->mode, + minimumLogLevel: $this->minimumLogLevel, + ), + ...$this->channels, + ]; + } + + /** + * A logging configuration for sending log messages to a Slack channel using an Incoming Webhook. + * + * @param string $webhookUrl The Slack Incoming Webhook URL. + * @param string|null $channelId The Slack channel ID to send messages to. If null, the default channel configured in the webhook will be used. + * @param string|null $username The username to display as the sender of the message. + * @param PresentationMode $mode The display mode for the Slack messages. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) string $webhookUrl, + private(set) ?string $channelId = null, + private(set) ?string $username = null, + private(set) PresentationMode $mode = PresentationMode::INLINE, + private LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) ?string $prefix = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/SysLogConfig.php b/packages/log/src/Config/SysLogConfig.php new file mode 100644 index 0000000000..e1a72895f1 --- /dev/null +++ b/packages/log/src/Config/SysLogConfig.php @@ -0,0 +1,47 @@ + [ + new SysLogChannel( + identity: $this->identity, + facility: $this->facility, + minimumLogLevel: $this->minimumLogLevel, + bubble: $this->bubble, + flags: $this->flags, + ), + ...$this->channels, + ]; + } + + /** + * A logging configuration for sending log messages to the system logger (syslog). + * + * @param string $identity The identity string to use for each log message. This is typically the application name. + * @param int $facility The syslog facility to use. See https://www.php.net/manual/en/function.openlog.php for available options. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not. + * @param int $flags Options for the openlog system call. See https://www.php.net/manual/en/function.openlog.php + * @param array $channels Additional channels to include in the configuration. + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) string $identity, + private(set) int $facility = LOG_USER, + private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) bool $bubble = true, + private(set) int $flags = LOG_PID, + private(set) array $channels = [], + private(set) ?string $prefix = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/WeeklyLogConfig.php b/packages/log/src/Config/WeeklyLogConfig.php new file mode 100644 index 0000000000..3b57e3e077 --- /dev/null +++ b/packages/log/src/Config/WeeklyLogConfig.php @@ -0,0 +1,47 @@ + [ + new WeeklyLogChannel( + path: $this->path, + maxFiles: $this->maxFiles, + lockFilesDuringWrites: $this->lockFilesDuringWrites, + minimumLogLevel: $this->minimumLogLevel, + filePermission: $this->filePermission, + ), + ...$this->channels, + ]; + } + + /** + * A logging configuration that creates a new log file each week and retains a maximum number of files. + * + * @param string $path The base log file name. + * @param int $maxFiles The maximal amount of files to keep. + * @param LogLevel $minimumLogLevel The minimum log level to record. + * @param null|string $prefix An optional prefix displayed in all log messages. By default, the current environment is used. + * @param bool $lockFilesDuringWrites Whether to try to lock log file before doing any writes. + * @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write). + * @param array $channels Additional channels to include in the configuration. + * @param null|UnitEnum|string $tag An optional tag to identify the logger instance associated to this configuration. + */ + public function __construct( + private(set) string $path, + private(set) int $maxFiles = 5, + private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG, + private(set) array $channels = [], + private(set) ?string $prefix = null, + private(set) bool $lockFilesDuringWrites = false, + private(set) ?int $filePermission = null, + private(set) null|UnitEnum|string $tag = null, + ) {} +} diff --git a/packages/log/src/Config/logs.config.php b/packages/log/src/Config/logs.config.php deleted file mode 100644 index 1b16a3c766..0000000000 --- a/packages/log/src/Config/logs.config.php +++ /dev/null @@ -1,14 +0,0 @@ -logConfig->channels as $channel) { + foreach ($this->logConfig->logChannels as $channel) { $this->resolveDriver($channel, $level)->log($level, $message, $context); } } @@ -95,7 +97,7 @@ private function resolveDriver(LogChannel $channel, MonologLogLevel $level): Mon if (! isset($this->drivers[$key])) { $this->drivers[$key] = new Monolog( - name: $this->logConfig->prefix, + name: $this->logConfig->prefix ?? $this->environment->value, handlers: $channel->getHandlers($level), processors: $channel->getProcessors(), ); diff --git a/packages/log/src/LogConfig.php b/packages/log/src/LogConfig.php index e9293d98e8..4e3502073f 100644 --- a/packages/log/src/LogConfig.php +++ b/packages/log/src/LogConfig.php @@ -4,23 +4,23 @@ namespace Tempest\Log; -use Tempest\Log\Channels\AppendLogChannel; +use Tempest\Container\HasTag; -use function Tempest\root_path; - -final class LogConfig +interface LogConfig extends HasTag { - public function __construct( - /** @var LogChannel[] */ - public array $channels = [], - public string $prefix = 'tempest', - public ?string $debugLogPath = null, - public ?string $serverLogPath = null, - ) { - $this->debugLogPath ??= root_path('/log/debug.log'); + /** + * An optional prefix displayed in all log messages. By default, the current environment is used. + */ + public ?string $prefix { + get; + } - if ($this->channels === []) { - $this->channels[] = new AppendLogChannel(root_path('/log/tempest.log')); - } + /** + * The log channels to which log messages will be sent. + * + * @var LogChannel[] + */ + public array $logChannels { + get; } } diff --git a/packages/log/src/LogLevel.php b/packages/log/src/LogLevel.php index 002ccb8a37..f969504646 100644 --- a/packages/log/src/LogLevel.php +++ b/packages/log/src/LogLevel.php @@ -61,4 +61,26 @@ public static function fromMonolog(Level $level): self Level::Debug => self::DEBUG, }; } + + public function toMonolog(): Level + { + return match ($this) { + self::EMERGENCY => Level::Emergency, + self::ALERT => Level::Alert, + self::CRITICAL => Level::Critical, + self::ERROR => Level::Error, + self::WARNING => Level::Warning, + self::NOTICE => Level::Notice, + self::INFO => Level::Info, + self::DEBUG => Level::Debug, + }; + } + + /** + * Determines if this log level is higher than or equal to the given level. + */ + public function includes(self $level): bool + { + return $this->toMonolog()->includes($level->toMonolog()); + } } diff --git a/packages/log/src/LoggerInitializer.php b/packages/log/src/LoggerInitializer.php index b2bd6d19f7..c879433b76 100644 --- a/packages/log/src/LoggerInitializer.php +++ b/packages/log/src/LoggerInitializer.php @@ -6,18 +6,27 @@ use Psr\Log\LoggerInterface; use Tempest\Container\Container; -use Tempest\Container\Initializer; +use Tempest\Container\DynamicInitializer; use Tempest\Container\Singleton; +use Tempest\Core\Environment; use Tempest\EventBus\EventBus; +use Tempest\Reflection\ClassReflector; +use UnitEnum; -final readonly class LoggerInitializer implements Initializer +final readonly class LoggerInitializer implements DynamicInitializer { + public function canInitialize(ClassReflector $class, null|string|UnitEnum $tag): bool + { + return $class->getType()->matches(Logger::class) || $class->getType()->matches(LoggerInterface::class); + } + #[Singleton] - public function initialize(Container $container): LoggerInterface|Logger + public function initialize(ClassReflector $class, null|string|UnitEnum $tag, Container $container): LoggerInterface|Logger { return new GenericLogger( - $container->get(LogConfig::class), - $container->get(EventBus::class), + logConfig: $container->get(LogConfig::class, $tag), + environment: $container->get(Environment::class), + eventBus: $container->get(EventBus::class), ); } } diff --git a/packages/console/src/Commands/TailProjectLogCommand.php b/packages/log/src/TailLogsCommand.php similarity index 55% rename from packages/console/src/Commands/TailProjectLogCommand.php rename to packages/log/src/TailLogsCommand.php index 0777335f7a..b4cd35543c 100644 --- a/packages/console/src/Commands/TailProjectLogCommand.php +++ b/packages/log/src/TailLogsCommand.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tempest\Console\Commands; +namespace Tempest\Log; use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; @@ -12,53 +12,41 @@ use Tempest\Highlight\Highlighter; use Tempest\Log\Channels\AppendLogChannel; use Tempest\Log\LogConfig; +use Tempest\Support\Filesystem; -final readonly class TailProjectLogCommand +final readonly class TailLogsCommand { public function __construct( private Console $console, - private LogConfig $logConfig, + private LogConfig $config, #[Tag('console')] private Highlighter $highlighter, ) {} - #[ConsoleCommand('tail:project', description: 'Tails the project log')] + #[ConsoleCommand('tail:logs', description: 'Tails the project logs', aliases: ['log:tail', 'logs:tail'])] public function __invoke(): void { $appendLogChannel = null; - foreach ($this->logConfig->channels as $channel) { + foreach ($this->config->logChannels as $channel) { if ($channel instanceof AppendLogChannel) { $appendLogChannel = $channel; - break; } } if ($appendLogChannel === null) { - $this->console->error('No AppendLogChannel registered'); - + $this->console->error('Tailing logs is only supported when a AppendLogChannel is configured.'); return; } - $dir = pathinfo($appendLogChannel->getPath(), PATHINFO_DIRNAME); - - if (! is_dir($dir)) { - mkdir($dir); - } - - if (! file_exists($appendLogChannel->getPath())) { - touch($appendLogChannel->getPath()); - } + Filesystem\create_file($appendLogChannel->path); - $this->console->header('Tailing project logs', "Reading getPath()}'/>…"); + $this->console->header('Tailing project logs', "Reading path}'/>…"); new TailReader()->tail( - path: $appendLogChannel->getPath(), - format: fn (string $text) => $this->highlighter->parse( - $text, - new LogLanguage(), - ), + path: $appendLogChannel->path, + format: fn (string $text) => $this->highlighter->parse($text, new LogLanguage()), ); } } diff --git a/packages/log/src/logging.config.php b/packages/log/src/logging.config.php new file mode 100644 index 0000000000..227806df96 --- /dev/null +++ b/packages/log/src/logging.config.php @@ -0,0 +1,14 @@ +assertSame($expected, LogLevel::fromMonolog($level)); } diff --git a/packages/mail/composer.json b/packages/mail/composer.json index b67757b3d8..5ef478c8ff 100644 --- a/packages/mail/composer.json +++ b/packages/mail/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/reflection": "dev-main", "tempest/support": "dev-main", "tempest/event-bus": "dev-main", diff --git a/packages/mail/src/GenericMailer.php b/packages/mail/src/GenericMailer.php index 5dc2564abf..bbe39026c6 100644 --- a/packages/mail/src/GenericMailer.php +++ b/packages/mail/src/GenericMailer.php @@ -5,7 +5,7 @@ use Symfony\Component\Mailer\Transport\TransportInterface; use Tempest\EventBus\EventBus; -use function Tempest\map; +use function Tempest\Mapper\map; /** * Generic mailer based on Symfony transports. diff --git a/packages/mail/src/Testing/MailTester.php b/packages/mail/src/Testing/MailTester.php index 49b65a55c3..c534347ab1 100644 --- a/packages/mail/src/Testing/MailTester.php +++ b/packages/mail/src/Testing/MailTester.php @@ -16,7 +16,7 @@ use Tempest\Mail\EmailToSymfonyEmailMapper; use Tempest\Support\Arr; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\arr; final class MailTester @@ -92,21 +92,21 @@ public function assertNotSent(string $email): self } public array $from { - get => Arr\map_iterable( + get => Arr\map( array: $this->sentSymfonyEmail->getFrom(), map: fn (SymfonyAddress $address) => new EmailAddress($address->getAddress(), $address->getName()), ); } public array $to { - get => Arr\map_iterable( + get => Arr\map( array: $this->sentSymfonyEmail->getTo(), map: fn (SymfonyAddress $address) => new EmailAddress($address->getAddress(), $address->getName()), ); } public array $attachments { - get => Arr\map_iterable( + get => Arr\map( array: $this->sentSymfonyEmail->getAttachments(), map: fn (DataPart $attachment) => new Attachment( resolve: fn () => $attachment->getBody(), @@ -383,7 +383,7 @@ public function assertAttached(string $filename, ?Closure $callback = null): sel Assert::fail(sprintf( 'Failed asserting that the email has an attachment named `%s`. Existing attachments: %s.', $filename, - Arr\join(Arr\map_iterable($attachments, fn (DataPart $attachment) => $attachment->getName())), + Arr\join(Arr\map($attachments, fn (DataPart $attachment) => $attachment->getName())), )); return $this; diff --git a/packages/mail/src/Testing/TestingMailer.php b/packages/mail/src/Testing/TestingMailer.php index d51b92da3f..82ecdf3196 100644 --- a/packages/mail/src/Testing/TestingMailer.php +++ b/packages/mail/src/Testing/TestingMailer.php @@ -7,7 +7,7 @@ use Tempest\Mail\EmailWasSent; use Tempest\Mail\Mailer; -use function Tempest\get; +use function Tempest\Container\get; final class TestingMailer implements Mailer { diff --git a/packages/mail/src/Transports/RoundRobinMailerConfig.php b/packages/mail/src/Transports/RoundRobinMailerConfig.php index 2eaa9898a6..59f4b78f45 100644 --- a/packages/mail/src/Transports/RoundRobinMailerConfig.php +++ b/packages/mail/src/Transports/RoundRobinMailerConfig.php @@ -42,6 +42,6 @@ public function createTransport(): TransportInterface /** @return TransportInterface[] */ private function buildTransports(): array { - return Arr\map_iterable($this->transports, fn (MailerConfig $config) => $config->createTransport()); + return Arr\map($this->transports, fn (MailerConfig $config) => $config->createTransport()); } } diff --git a/packages/mapper/composer.json b/packages/mapper/composer.json index 679e0d2512..88cde62859 100644 --- a/packages/mapper/composer.json +++ b/packages/mapper/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/validation": "dev-main", "tempest/core": "dev-main" }, diff --git a/packages/mapper/src/Attributes/Context.php b/packages/mapper/src/Attributes/Context.php new file mode 100644 index 0000000000..151fbff47f --- /dev/null +++ b/packages/mapper/src/Attributes/Context.php @@ -0,0 +1,35 @@ +context instanceof BackedEnum) { + return $this->context->value; + } + + if ($this->context instanceof UnitEnum) { + return $this->context->name; + } + + return $this->context; + } + } + + public function __construct( + public BackedEnum|UnitEnum|string $context, + ) {} +} diff --git a/packages/mapper/src/Caster.php b/packages/mapper/src/Caster.php index 4d013045f0..63d8efaf6c 100644 --- a/packages/mapper/src/Caster.php +++ b/packages/mapper/src/Caster.php @@ -6,5 +6,8 @@ interface Caster { + /** + * Creates an object or a scalar value from the given input. + */ public function cast(mixed $input): mixed; } diff --git a/packages/mapper/src/CasterDiscovery.php b/packages/mapper/src/CasterDiscovery.php new file mode 100644 index 0000000000..d298b492ca --- /dev/null +++ b/packages/mapper/src/CasterDiscovery.php @@ -0,0 +1,45 @@ +implements(DynamicCaster::class)) { + return; + } + + $this->discoveryItems->add($location, [ + $class->getAttribute(Context::class)?->name, + $class->getAttribute(Priority::class)?->priority, + $class->getName(), + ]); + } + + public function apply(): void + { + foreach ($this->discoveryItems as [$context, $priority, $casterClass]) { + $this->casterFactory->addCaster( + casterClass: $casterClass, + priority: $priority ?? Priority::NORMAL, + context: $context, + ); + } + } +} diff --git a/packages/mapper/src/CasterFactory.php b/packages/mapper/src/CasterFactory.php index 616bf42d9e..f9f488a27b 100644 --- a/packages/mapper/src/CasterFactory.php +++ b/packages/mapper/src/CasterFactory.php @@ -5,63 +5,100 @@ namespace Tempest\Mapper; use Closure; -use Tempest\Mapper\Casters\DtoCaster; +use Tempest\Container\Container; +use Tempest\Container\Singleton; use Tempest\Reflection\PropertyReflector; +use Tempest\Support\Memoization\HasMemoization; +use UnitEnum; -use function Tempest\get; - +#[Singleton] final class CasterFactory { + use HasMemoization; + /** - * @var array{string|Closure, class-string<\Tempest\Mapper\Caster>|Closure}[] + * @var array, int}[]> */ - private array $casters = []; + private(set) array $casters = []; + + private(set) Context|UnitEnum|string|null $context = null; + + public function __construct( + private readonly Container $container, + ) {} /** * @param class-string<\Tempest\Mapper\Caster> $casterClass */ - public function addCaster(string|Closure $for, string|Closure $casterClass): self + public function addCaster(string $casterClass, int $priority = 0, Context|UnitEnum|string|null $context = null): self { - $this->casters = [[$for, $casterClass], ...$this->casters]; + $context = MappingContext::from($context); + + $this->casters[$context->name] ??= []; + $this->casters[$context->name][] = [$casterClass, $priority]; + + usort($this->casters[$context->name], static fn (array $a, array $b) => $a[1] <=> $b[1]); return $this; } + /** + * Sets the context that should be passed to casters. + */ + public function in(Context|UnitEnum|string $context): self + { + $caster = clone $this; + $caster->context = $context; + + return $caster; + } + public function forProperty(PropertyReflector $property): ?Caster { - $type = $property->getType(); + $context = MappingContext::from($this->context); - // Get CastWith from the property - $castWith = $property->getAttribute(CastWith::class); + return $this->memoize('[' . $context->name . '] ' . $property->getName(), function () use ($property, $context) { + $type = $property->getType(); + $castWith = $property->getAttribute(CastWith::class); - // Get CastWith from the property's type if there's no property-defined CastWith - if ($castWith === null && $type->isClass()) { - $castWith = $type->asClass()->getAttribute(CastWith::class, recursive: true); + if ($castWith === null && ($type->isClass() || $type->isInterface())) { + $castWith = $type->asClass()->getAttribute(CastWith::class, recursive: true); + } + + if ($castWith) { + return $this->container->get($castWith->className, context: $context); + } - if ($castWith === null && $type->asClass()->getAttribute(SerializeAs::class)) { - $castWith = new CastWith(DtoCaster::class); + if ($casterAttribute = $property->getAttribute(ProvidesCaster::class)) { + return $this->container->get($casterAttribute->caster, context: $context); } - } - - // Return the caster if defined with CastWith - if ($castWith !== null) { - // Resolve the caster from the container - return get($castWith->className); - } - - if ($casterAttribute = $property->getAttribute(ProvidesCaster::class)) { - return get($casterAttribute->caster); - } - - // Resolve caster from manual additions - foreach ($this->casters as [$for, $casterClass]) { - if (is_callable($for) && $for($property) || is_string($for) && $type->matches($for) || $type->getName() === $for) { - return is_callable($casterClass) - ? $casterClass($property) - : get($casterClass); + + foreach ($this->resolveCasters() as [$casterClass]) { + if (is_a($casterClass, DynamicCaster::class, allow_string: true)) { + if (! $casterClass::accepts($property)) { + continue; + } + } + + if (is_a($casterClass, ConfigurableCaster::class, allow_string: true)) { + return $casterClass::configure($property, $context); + } + + return $this->container->get($casterClass, context: $context); } - } - return null; + return null; + }); + } + + /** + * @return array{class-string<\Tempest\Mapper\Caster>|Closure,int}[] + */ + private function resolveCasters(): array + { + return [ + ...($this->casters[MappingContext::from($this->context)->name] ?? []), + ...($this->casters[MappingContext::default()->name] ?? []), + ]; } } diff --git a/packages/mapper/src/CasterFactoryInitializer.php b/packages/mapper/src/CasterFactoryInitializer.php deleted file mode 100644 index f2becf80cd..0000000000 --- a/packages/mapper/src/CasterFactoryInitializer.php +++ /dev/null @@ -1,49 +0,0 @@ -addCaster('array', JsonToArrayCaster::class) - ->addCaster('bool', BooleanCaster::class) - ->addCaster('boolean', BooleanCaster::class) - ->addCaster('int', IntegerCaster::class) - ->addCaster('integer', IntegerCaster::class) - ->addCaster('float', FloatCaster::class) - ->addCaster('double', FloatCaster::class) - ->addCaster(fn (PropertyReflector $property) => $property->getIterableType() !== null, fn (PropertyReflector $property) => new ArrayToObjectCollectionCaster($property)) - ->addCaster(fn (PropertyReflector $property) => $property->getType()->isClass(), fn (PropertyReflector $property) => new ObjectCaster($property->getType())) - ->addCaster(UnitEnum::class, fn (PropertyReflector $property) => new EnumCaster($property->getType()->getName())) - ->addCaster(DateTimeInterface::class, DateTimeCaster::fromProperty(...)) - ->addCaster(NativeDateTimeImmutable::class, NativeDateTimeCaster::fromProperty(...)) - ->addCaster(NativeDateTime::class, NativeDateTimeCaster::fromProperty(...)) - ->addCaster(NativeDateTimeInterface::class, NativeDateTimeCaster::fromProperty(...)) - ->addCaster(DateTime::class, DateTimeCaster::fromProperty(...)); - } -} diff --git a/packages/mapper/src/Casters/ArrayToObjectCollectionCaster.php b/packages/mapper/src/Casters/ArrayToObjectCollectionCaster.php index c16670cf75..c3bd24eb61 100644 --- a/packages/mapper/src/Casters/ArrayToObjectCollectionCaster.php +++ b/packages/mapper/src/Casters/ArrayToObjectCollectionCaster.php @@ -4,16 +4,36 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Support\Json; -final readonly class ArrayToObjectCollectionCaster implements Caster +#[Priority(Priority::HIGHEST)] +final readonly class ArrayToObjectCollectionCaster implements Caster, DynamicCaster, ConfigurableCaster { public function __construct( private PropertyReflector $property, ) {} + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + if ($input instanceof TypeReflector) { + return false; + } + + return $input->getIterableType() !== null; + } + + public static function configure(PropertyReflector $property, Context $context): self + { + return new self($property); + } + public function cast(mixed $input): mixed { $values = []; diff --git a/packages/mapper/src/Casters/BooleanCaster.php b/packages/mapper/src/Casters/BooleanCaster.php index d241ca9438..a2eac02b85 100644 --- a/packages/mapper/src/Casters/BooleanCaster.php +++ b/packages/mapper/src/Casters/BooleanCaster.php @@ -4,10 +4,24 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final readonly class BooleanCaster implements Caster +#[Priority(Priority::NORMAL)] +final readonly class BooleanCaster implements Caster, DynamicCaster { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['bool', 'boolean'], strict: true); + } + public function cast(mixed $input): bool { if (is_string($input)) { diff --git a/packages/mapper/src/Casters/DateTimeCaster.php b/packages/mapper/src/Casters/DateTimeCaster.php index 5288908aa4..ee1b116e3f 100644 --- a/packages/mapper/src/Casters/DateTimeCaster.php +++ b/packages/mapper/src/Casters/DateTimeCaster.php @@ -4,23 +4,38 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\DateTime\DateTime; use Tempest\DateTime\DateTimeInterface; use Tempest\DateTime\FormatPattern; use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\HasDateTimeFormat; -final readonly class DateTimeCaster implements Caster +#[Priority(Priority::HIGHEST)] +final readonly class DateTimeCaster implements Caster, DynamicCaster, ConfigurableCaster { public function __construct( private FormatPattern|string $format = FormatPattern::ISO8601, ) {} - public static function fromProperty(PropertyReflector $property): self + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(DateTimeInterface::class); + } + + public static function configure(PropertyReflector $property, Context $context): self { return new self( - $property->getAttribute(HasDateTimeFormat::class)->format ?? FormatPattern::ISO8601, + format: $property->getAttribute(HasDateTimeFormat::class)->format ?? FormatPattern::ISO8601, ); } diff --git a/packages/mapper/src/Casters/DtoCaster.php b/packages/mapper/src/Casters/DtoCaster.php deleted file mode 100644 index 3b9e0e6fb2..0000000000 --- a/packages/mapper/src/Casters/DtoCaster.php +++ /dev/null @@ -1,53 +0,0 @@ -deserialize(Json\decode($input)); - } - - if (is_array($input)) { - return $this->deserialize($input); - } - - if (is_string($input)) { - throw new ValueCouldNotBeCast('json string'); - } - - return $input; - } - - private function deserialize(mixed $input): mixed - { - if (is_array($input) && isset($input['type'], $input['data'])) { - $class = Arr\find_key( - array: $this->mapperConfig->serializationMap, - value: $input['type'], - ) ?: $input['type']; - - return map($this->deserialize($input['data']))->to($class); - } - - if (is_array($input)) { - return array_map(fn (mixed $value) => $this->deserialize($value), $input); - } - - return $input; - } -} diff --git a/packages/mapper/src/Casters/EnumCaster.php b/packages/mapper/src/Casters/EnumCaster.php index 3520662d88..c3fa26e67a 100644 --- a/packages/mapper/src/Casters/EnumCaster.php +++ b/packages/mapper/src/Casters/EnumCaster.php @@ -4,10 +4,17 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use UnitEnum; -final readonly class EnumCaster implements Caster +#[Priority(Priority::HIGHEST)] +final readonly class EnumCaster implements Caster, DynamicCaster, ConfigurableCaster { /** * @param class-string $enum @@ -16,6 +23,20 @@ public function __construct( private string $enum, ) {} + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(UnitEnum::class); + } + + public static function configure(PropertyReflector $property, Context $context): self + { + return new self(enum: $property->getType()->getName()); + } + public function cast(mixed $input): ?object { if ($input === null) { diff --git a/packages/mapper/src/Casters/FloatCaster.php b/packages/mapper/src/Casters/FloatCaster.php index 25030bd3a6..cc0f0ddb11 100644 --- a/packages/mapper/src/Casters/FloatCaster.php +++ b/packages/mapper/src/Casters/FloatCaster.php @@ -4,10 +4,24 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final readonly class FloatCaster implements Caster +#[Priority(Priority::NORMAL)] +final readonly class FloatCaster implements Caster, DynamicCaster { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['float', 'double'], strict: true); + } + public function cast(mixed $input): float { return floatval($input); diff --git a/packages/mapper/src/Casters/IntegerCaster.php b/packages/mapper/src/Casters/IntegerCaster.php index 68fce2667a..954284b645 100644 --- a/packages/mapper/src/Casters/IntegerCaster.php +++ b/packages/mapper/src/Casters/IntegerCaster.php @@ -4,10 +4,24 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final readonly class IntegerCaster implements Caster +#[Priority(Priority::NORMAL)] +final readonly class IntegerCaster implements Caster, DynamicCaster { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['int', 'integer'], strict: true); + } + public function cast(mixed $input): int { return intval($input); diff --git a/packages/mapper/src/Casters/JsonToArrayCaster.php b/packages/mapper/src/Casters/JsonToArrayCaster.php index 235e13f6f3..d948c244fd 100644 --- a/packages/mapper/src/Casters/JsonToArrayCaster.php +++ b/packages/mapper/src/Casters/JsonToArrayCaster.php @@ -4,11 +4,25 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\DynamicCaster; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Support\Json; -final class JsonToArrayCaster implements Caster +#[Priority(Priority::NORMAL)] +final class JsonToArrayCaster implements Caster, DynamicCaster { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->getName() === 'array'; + } + public function cast(mixed $input): array { if (is_array($input)) { diff --git a/packages/mapper/src/Casters/NativeDateTimeCaster.php b/packages/mapper/src/Casters/NativeDateTimeCaster.php index f68f1b5137..6505cbc413 100644 --- a/packages/mapper/src/Casters/NativeDateTimeCaster.php +++ b/packages/mapper/src/Casters/NativeDateTimeCaster.php @@ -8,18 +8,33 @@ use DateTimeImmutable; use DateTimeInterface; use InvalidArgumentException; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\HasDateTimeFormat; -final readonly class NativeDateTimeCaster implements Caster +#[Priority(Priority::HIGHEST)] +final readonly class NativeDateTimeCaster implements Caster, DynamicCaster, ConfigurableCaster { public function __construct( private string $format = 'Y-m-d H:i:s', private bool $immutable = true, ) {} - public static function fromProperty(PropertyReflector $property): NativeDateTimeCaster + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(DateTimeInterface::class); + } + + public static function configure(PropertyReflector $property, Context $context): NativeDateTimeCaster { $format = $property->getAttribute(HasDateTimeFormat::class)->format ?? 'Y-m-d H:i:s'; diff --git a/packages/mapper/src/Casters/ObjectCaster.php b/packages/mapper/src/Casters/ObjectCaster.php index b9bab1021a..91622086ab 100644 --- a/packages/mapper/src/Casters/ObjectCaster.php +++ b/packages/mapper/src/Casters/ObjectCaster.php @@ -4,21 +4,43 @@ namespace Tempest\Mapper\Casters; +use Tempest\Core\Priority; use Tempest\Mapper\Caster; +use Tempest\Mapper\ConfigurableCaster; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicCaster; use Tempest\Mapper\Mappers\ArrayToObjectMapper; +use Tempest\Reflection\PropertyReflector; use Tempest\Reflection\TypeReflector; -use function Tempest\map; +use function Tempest\Mapper\map; -final readonly class ObjectCaster implements Caster +#[Priority(Priority::HIGH)] +final readonly class ObjectCaster implements Caster, DynamicCaster, ConfigurableCaster { public function __construct( private TypeReflector $type, ) {} + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->isClass(); + } + + public static function configure(PropertyReflector $property, Context $context): static + { + return new self($property->getType()); + } + public function cast(mixed $input): mixed { // TODO: difference with ArrayToObjectCaster? This can probably be removed after we've added support for #984 - return map($input)->with(ArrayToObjectMapper::class)->to($this->type->getName()); + return map($input) + ->with(ArrayToObjectMapper::class) + ->to($this->type->getName()); } } diff --git a/packages/mapper/src/ConfigurableCaster.php b/packages/mapper/src/ConfigurableCaster.php new file mode 100644 index 0000000000..529ffa7ae2 --- /dev/null +++ b/packages/mapper/src/ConfigurableCaster.php @@ -0,0 +1,16 @@ +mappers[] = $mapperClass; + + return $this; + } } diff --git a/packages/mapper/src/MapperDiscovery.php b/packages/mapper/src/MapperDiscovery.php index 91ec317599..ea4e609c49 100644 --- a/packages/mapper/src/MapperDiscovery.php +++ b/packages/mapper/src/MapperDiscovery.php @@ -23,13 +23,13 @@ public function discover(DiscoveryLocation $location, ClassReflector $class): vo return; } - $this->discoveryItems->add($location, $class->getName()); + $this->discoveryItems->add($location, [$class->getName()]); } public function apply(): void { - foreach ($this->discoveryItems as $className) { - $this->config->mappers[] = $className; + foreach ($this->discoveryItems as [$className]) { + $this->config->addMapper($className); } } } diff --git a/packages/mapper/src/Mappers/ArrayToObjectMapper.php b/packages/mapper/src/Mappers/ArrayToObjectMapper.php index af753dbf09..7694cfee9d 100644 --- a/packages/mapper/src/Mappers/ArrayToObjectMapper.php +++ b/packages/mapper/src/Mappers/ArrayToObjectMapper.php @@ -5,20 +5,26 @@ namespace Tempest\Mapper\Mappers; use Tempest\Mapper\CasterFactory; +use Tempest\Mapper\Context; use Tempest\Mapper\Exceptions\MappingValuesWereMissing; use Tempest\Mapper\MapFrom; use Tempest\Mapper\Mapper; use Tempest\Mapper\Strict; use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; +use Tempest\Support\Arr; +use Tempest\Support\Memoization\HasMemoization; use Throwable; use function Tempest\Support\arr; -final readonly class ArrayToObjectMapper implements Mapper +final class ArrayToObjectMapper implements Mapper { + use HasMemoization; + public function __construct( - private CasterFactory $casterFactory, + private readonly CasterFactory $casterFactory, + private readonly Context $context, ) {} public function canMap(mixed $from, mixed $to): bool @@ -36,15 +42,15 @@ public function canMap(mixed $from, mixed $to): bool public function map(mixed $from, mixed $to): object { - $class = new ClassReflector($to); - $object = $this->resolveObject($to); - $from = arr($from)->undot()->toArray(); - $isStrictClass = $class->hasAttribute(Strict::class); + $targetClass = new ClassReflector($to); + $targetObject = $this->resolveObject($to); + $from = Arr\wrap($from) |> Arr\undot(...); + $isStrictClass = $targetClass->hasAttribute(Strict::class); $missingValues = []; $unsetProperties = []; - foreach ($class->getPublicProperties() as $property) { + foreach ($targetClass->getPublicProperties() as $property) { if ($property->isVirtual()) { continue; } @@ -59,28 +65,31 @@ public function map(mixed $from, mixed $to): object missingValues: $missingValues, unsetProperties: $unsetProperties, ); + continue; } - $value = $this->resolveValue($property, $from[$propertyName]); - $property->setValue($object, $value); + $property->setValue( + object: $targetObject, + value: $this->resolveValue($property, $from[$propertyName]), + ); } if ($missingValues !== []) { throw new MappingValuesWereMissing($to, $missingValues); } - $this->setParentRelations($object, $class); + $this->setParentRelations($targetObject, $targetClass); foreach ($unsetProperties as $property) { if ($property->isVirtual()) { continue; } - $property->unset($object); + $property->unset($targetObject); } - return $object; + return $targetObject; } private function resolvePropertyName(PropertyReflector $property, array $from): string @@ -152,7 +161,11 @@ private function setChildParentRelation(object $parent, mixed $child, ClassRefle public function resolveValue(PropertyReflector $property, mixed $value): mixed { - $caster = $this->casterFactory->forProperty($property); + $caster = $this->memoize((string) $property, function () use ($property, $value) { + return $this->casterFactory + ->in($this->context) + ->forProperty($property); + }); if ($property->isNullable() && $value === null) { return null; diff --git a/packages/mapper/src/Mappers/JsonFileToObjectMapper.php b/packages/mapper/src/Mappers/JsonFileToObjectMapper.php index d68f214d34..d0e59078b5 100644 --- a/packages/mapper/src/Mappers/JsonFileToObjectMapper.php +++ b/packages/mapper/src/Mappers/JsonFileToObjectMapper.php @@ -4,14 +4,19 @@ namespace Tempest\Mapper\Mappers; +use Tempest\Mapper\Context; use Tempest\Mapper\Mapper; use Tempest\Support\Filesystem; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\path; final readonly class JsonFileToObjectMapper implements Mapper { + public function __construct( + private Context $context, + ) {} + public function canMap(mixed $from, mixed $to): bool { if (! is_string($from)) { @@ -25,6 +30,9 @@ public function canMap(mixed $from, mixed $to): bool public function map(mixed $from, mixed $to): array { - return map(Filesystem\read_json($from))->collection()->to($to); + return map(Filesystem\read_json($from)) + ->in($this->context) + ->collection() + ->to($to); } } diff --git a/packages/mapper/src/Mappers/JsonToObjectMapper.php b/packages/mapper/src/Mappers/JsonToObjectMapper.php index 370bf6048c..1e20d8ba25 100644 --- a/packages/mapper/src/Mappers/JsonToObjectMapper.php +++ b/packages/mapper/src/Mappers/JsonToObjectMapper.php @@ -4,15 +4,20 @@ namespace Tempest\Mapper\Mappers; +use Tempest\Mapper\Context; use Tempest\Mapper\Mapper; use Tempest\Reflection\ClassReflector; use Tempest\Support\Json; use Throwable; -use function Tempest\map; +use function Tempest\Mapper\map; final readonly class JsonToObjectMapper implements Mapper { + public function __construct( + private Context $context, + ) {} + public function canMap(mixed $from, mixed $to): bool { if (! is_string($from)) { @@ -34,6 +39,12 @@ public function canMap(mixed $from, mixed $to): bool public function map(mixed $from, mixed $to): object { - return map(map($from)->toArray())->to($to); + $array = map($from) + ->in($this->context) + ->toArray(); + + return map($array) + ->in($this->context) + ->to($to); } } diff --git a/packages/mapper/src/Mappers/ObjectToArrayMapper.php b/packages/mapper/src/Mappers/ObjectToArrayMapper.php index 2012b2e99b..ef68537f14 100644 --- a/packages/mapper/src/Mappers/ObjectToArrayMapper.php +++ b/packages/mapper/src/Mappers/ObjectToArrayMapper.php @@ -5,18 +5,20 @@ namespace Tempest\Mapper\Mappers; use JsonSerializable; +use Tempest\Mapper\Context; use Tempest\Mapper\Mapper; use Tempest\Mapper\MapTo; use Tempest\Mapper\SerializerFactory; use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; -use function Tempest\map; +use function Tempest\Mapper\map; final readonly class ObjectToArrayMapper implements Mapper { public function __construct( private SerializerFactory $serializerFactory, + private Context $context, ) {} public function canMap(mixed $from, mixed $to): bool @@ -65,7 +67,7 @@ private function resolvePropertyValue(PropertyReflector $property, object $objec return $propertyValue; } - if ($propertyValue !== null && ($serializer = $this->serializerFactory->forProperty($property)) !== null) { + if ($propertyValue !== null && ($serializer = $this->serializerFactory->in($this->context)->forProperty($property)) !== null) { return $serializer->serialize($propertyValue); } diff --git a/packages/mapper/src/Mappers/ObjectToJsonMapper.php b/packages/mapper/src/Mappers/ObjectToJsonMapper.php index 008c8039ba..d785d0b367 100644 --- a/packages/mapper/src/Mappers/ObjectToJsonMapper.php +++ b/packages/mapper/src/Mappers/ObjectToJsonMapper.php @@ -4,12 +4,17 @@ namespace Tempest\Mapper\Mappers; +use Tempest\Mapper\Context; use Tempest\Mapper\Mapper; -use function Tempest\map; +use function Tempest\Mapper\map; final readonly class ObjectToJsonMapper implements Mapper { + public function __construct( + private Context $context, + ) {} + public function canMap(mixed $from, mixed $to): bool { return false; @@ -17,6 +22,12 @@ public function canMap(mixed $from, mixed $to): bool public function map(mixed $from, mixed $to): string { - return map(map($from)->toArray())->toJson(); + $array = map($from) + ->in($this->context) + ->toArray(); + + return map($array) + ->in($this->context) + ->toJson(); } } diff --git a/packages/mapper/src/MappingContext.php b/packages/mapper/src/MappingContext.php new file mode 100644 index 0000000000..1215c1f33f --- /dev/null +++ b/packages/mapper/src/MappingContext.php @@ -0,0 +1,40 @@ +name; + } + + return new self($context); + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/packages/mapper/src/ObjectFactory.php b/packages/mapper/src/ObjectFactory.php index eec40d67a7..22207e64d9 100644 --- a/packages/mapper/src/ObjectFactory.php +++ b/packages/mapper/src/ObjectFactory.php @@ -6,6 +6,7 @@ use Closure; use Tempest\Container\Container; +use Tempest\Container\Singleton; use Tempest\Mapper\Exceptions\DataCouldNotBeMapped; use Tempest\Mapper\Exceptions\MapperWasMissing; use Tempest\Mapper\Mappers\ArrayToJsonMapper; @@ -15,8 +16,10 @@ use Tempest\Reflection\FunctionReflector; use Tempest\Support\Arr; use Tempest\Support\Json; +use UnitEnum; /** @template ClassType */ +#[Singleton] final class ObjectFactory { private mixed $from; @@ -27,12 +30,26 @@ final class ObjectFactory private bool $isCollection = false; + private Context|UnitEnum|string|null $context = null; + + /** @var \Tempest\Mapper\Mapper[] */ + private array $mappers; + public function __construct( private readonly MapperConfig $config, private readonly Container $container, - ) {} + ) { + $this->mappers = $this->resolveMappers(); + } /** + * Sets the target class for mapping operations. + * + * ### Example + * ```php + * $factory->forClass(Author::class)->from(['name' => 'Jon Doe']); + * ``` + * * @template T of object * @param T|class-string $objectOrClass * @return self @@ -44,6 +61,14 @@ public function forClass(mixed $objectOrClass): self return $this; } + /** + * Sets the source data for mapping. + * + * ### Example + * ```php + * $factory->withData(['name' => 'Jon Doe'])->to(Author::class); + * ``` + */ public function withData(mixed $data): self { $this->from = $data; @@ -52,6 +77,18 @@ public function withData(mixed $data): self } /** + * Marks the mapping operation to process an array of objects instead of a single object. + * + * ### Example + * ```php + * make(Author::class) + * ->collection() + * ->from([ + * ['name' => 'Jon Doe'], + * ['name' => 'Jane Smith'], + * ]); + * ``` + * * @return self */ public function collection(): self @@ -62,6 +99,39 @@ public function collection(): self } /** + * Sets the context for mapping, allowing context-specific mappers to be used. + * + * ### Example + * ```php + * make(Author::class) + * ->in(Context::API) + * ->from(['name' => 'Jon Doe']); + * ``` + * + * @return self + */ + public function in(Context|UnitEnum|string|null $context): self + { + $clone = clone($this, [ + 'context' => $context, + ]); + + $clone->mappers = $clone->resolveMappers(); + + return $clone; + } + + /** + * Maps the given data to the target class. + * + * ### Example + * ```php + * $author = make(Author::class)->from([ + * 'first_name' => 'Jon', + * 'last_name' => 'Doe', + * ]); + * ``` + * * @return ClassType */ public function from(mixed $data): mixed @@ -74,6 +144,19 @@ public function from(mixed $data): mixed } /** + * Specifies custom mappers to use for the mapping operation. + * + * ### Example + * ```php + * map(['name' => 'Jon Doe']) + * ->with(CustomMapper::class) + * ->to(Author::class); + * + * map($data) + * ->with(fn (SomeMapper $mapper) => $mapper->map($data)) + * ->do(); + * ``` + * * @template MapperType of \Tempest\Mapper\Mapper * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> ...$mappers * @return self @@ -86,6 +169,16 @@ public function with(Closure|string ...$mappers): self } /** + * Maps the source data to the specified target class. + * + * ### Example + * ```php + * $author = map([ + * 'first_name' => 'Jon', + * 'last_name' => 'Doe', + * ])->to(Author::class); + * ``` + * * @template T of object * @param T|class-string|string $to * @return T|T[]|mixed @@ -99,6 +192,17 @@ public function to(mixed $to): mixed ); } + /** + * Executes the mapping using explicitly specified mappers. + * + * ### Example + * ```php + * $result = map($data) + * ->with(ObjectToArrayMapper::class) + * ->with(ArrayToJsonMapper::class) + * ->do(); + * ``` + */ public function do(): mixed { if ($this->with === []) { @@ -118,6 +222,15 @@ public function do(): mixed return $result; } + /** + * Converts the source data to an array. + * + * ### Example + * ```php + * $array = map($author)->toArray(); + * $arrays = map($authors)->collection()->toArray(); + * ``` + */ public function toArray(): array { if (is_object($this->from)) { @@ -142,6 +255,15 @@ public function toArray(): array throw new DataCouldNotBeMapped($this->from, 'array'); } + /** + * Converts the source data to a JSON string. + * + * ### Example + * ```php + * $json = map($author)->toJson(); + * $json = map(['name' => 'Jon Doe'])->toJson(); + * ``` + */ public function toJson(): string { if (is_object($this->from)) { @@ -156,6 +278,13 @@ public function toJson(): string } /** + * Maps data from one format to another. + * + * ### Example + * ```php + * $author = $factory->map(['name' => 'Jon Doe'], to: Author::class); + * ``` + * * @template T of object * @param T|class-string|string $to * @return T|mixed @@ -169,21 +298,15 @@ public function map(mixed $from, mixed $to): mixed ); } - private function mapObject( - mixed $from, - mixed $to, - bool $isCollection, - ): mixed { + private function mapObject(mixed $from, mixed $to, bool $isCollection): mixed + { // Map collections if ($isCollection && is_array($from)) { - return array_map( - fn (mixed $item) => $this->mapObject( - from: $item, - to: $to, - isCollection: false, - ), - $from, - ); + return array_map(fn (mixed $item) => $this->mapObject( + from: $item, + to: $to, + isCollection: false, + ), $from); } // Map using explicitly defined mappers @@ -201,15 +324,11 @@ private function mapObject( return $result; } - // Map using an inferred mapper - $mappers = $this->config->mappers; - - foreach ($mappers as $mapperClass) { - /** @var Mapper $mapper */ - $mapper = $this->container->get($mapperClass); + $context = MappingContext::from($this->context); - if ($mapper->canMap(from: $from, to: $to)) { - return $mapper->map(from: $from, to: $to); + foreach ($this->mappers as $mapper) { + if ($mapper->canMap($from, $to)) { + return $mapper->map($from, $to); } } @@ -220,11 +339,10 @@ private function mapObject( * @template MapperType of \Tempest\Mapper\Mapper * @param Closure(MapperType $mapper, mixed $from): mixed|class-string<\Tempest\Mapper\Mapper> $mapper */ - private function mapWith( - mixed $mapper, - mixed $from, - mixed $to, - ): mixed { + private function mapWith(mixed $mapper, mixed $from, mixed $to): mixed + { + $context = MappingContext::from($this->context); + if ($mapper instanceof Closure) { $function = new FunctionReflector($mapper); @@ -233,15 +351,38 @@ private function mapWith( ]; foreach ($function->getParameters() as $parameter) { - $data[$parameter->getName()] ??= $this->container->get($parameter->getType()->getName()); + if ($parameter->getType()->matches(Context::class)) { + $data[$parameter->getName()] ??= $context; + continue; + } + + $data[$parameter->getName()] ??= $this->container->get($parameter->getType()->getName(), context: $context); } return $mapper(...$data); } - $mapper = $this->container->get($mapper); + $mapper = $this->container->get($mapper, context: $context); /** @var Mapper $mapper */ return $mapper->map($from, $to); } + + /** + * We cache mapper instances within the factory so that we prevent mappers being resolved on every mapping call. + * Whenever a mapping context changes, we'll have to re-resolve the mapper classes with the new context. + */ + private function resolveMappers(): array + { + $mappers = []; + + $context = MappingContext::from($this->context); + + foreach ($this->config->mappers as $mapperClass) { + /** @var Mapper $mapper */ + $mappers[] = $this->container->get($mapperClass, context: $context); + } + + return $mappers; + } } diff --git a/packages/mapper/src/SerializeAs.php b/packages/mapper/src/SerializeAs.php index 4ee4a71b04..96324867ea 100644 --- a/packages/mapper/src/SerializeAs.php +++ b/packages/mapper/src/SerializeAs.php @@ -6,7 +6,6 @@ /** * Defines the name to use when serializing this class, instead of its fully qualified class name. - * Using this attribute removes the need to specify a `DtoCaster` and `DtoSerializer`. */ #[Attribute(Attribute::TARGET_CLASS)] final readonly class SerializeAs diff --git a/packages/mapper/src/Serializer.php b/packages/mapper/src/Serializer.php index 530a707ba8..1e18af8dd8 100644 --- a/packages/mapper/src/Serializer.php +++ b/packages/mapper/src/Serializer.php @@ -6,5 +6,8 @@ interface Serializer { - public function serialize(mixed $input): array|string; + /** + * Serializes the given input into a string, array, or integer. + */ + public function serialize(mixed $input): array|string|int; } diff --git a/packages/mapper/src/SerializerDiscovery.php b/packages/mapper/src/SerializerDiscovery.php new file mode 100644 index 0000000000..a7f6bb6f36 --- /dev/null +++ b/packages/mapper/src/SerializerDiscovery.php @@ -0,0 +1,45 @@ +implements(DynamicSerializer::class)) { + return; + } + + $this->discoveryItems->add($location, [ + $class->getAttribute(Context::class)?->name, + $class->getAttribute(Priority::class)?->priority, + $class->getName(), + ]); + } + + public function apply(): void + { + foreach ($this->discoveryItems as [$context, $priority, $serializerClass]) { + $this->serializerFactory->addSerializer( + serializerClass: $serializerClass, + priority: $priority ?? Priority::NORMAL, + context: $context, + ); + } + } +} diff --git a/packages/mapper/src/SerializerFactory.php b/packages/mapper/src/SerializerFactory.php index c232f3470e..336d3f65a9 100644 --- a/packages/mapper/src/SerializerFactory.php +++ b/packages/mapper/src/SerializerFactory.php @@ -5,105 +5,93 @@ namespace Tempest\Mapper; use Closure; -use Tempest\Mapper\Serializers\DtoSerializer; +use Tempest\Container\Container; +use Tempest\Container\Singleton; use Tempest\Reflection\ClassReflector; use Tempest\Reflection\PropertyReflector; use Tempest\Reflection\TypeReflector; -use TypeError; - -use function Tempest\get; +use Tempest\Support\Memoization\HasMemoization; +use UnitEnum; +#[Singleton] final class SerializerFactory { + use HasMemoization; + /** - * @var array{string|Closure, class-string<\Tempest\Mapper\Serializer>|Closure}[] + * @var array,int}[]> */ - private array $serializers = []; + private(set) array $serializers = []; + + private(set) Context|UnitEnum|string|null $context = null; + + public function __construct( + private readonly Container $container, + ) {} /** * @param class-string<\Tempest\Mapper\Serializer> $serializerClass */ - public function addSerializer(string|Closure $for, string|Closure $serializerClass): self + public function addSerializer(string $serializerClass, int $priority = 0, Context|UnitEnum|string|null $context = null): self { - $this->serializers = [[$for, $serializerClass], ...$this->serializers]; - - return $this; - } - - private function serializerMatches(Closure|string $for, TypeReflector|string $input): bool - { - if (is_callable($for)) { - try { - return $for($input); - } catch (TypeError) { - return false; - } - } + $context = MappingContext::from($context); - if ($for === $input) { - return true; - } + $this->serializers[$context->name] ??= []; + $this->serializers[$context->name][] = [$serializerClass, $priority]; - if ($input instanceof TypeReflector) { - return $input->getName() === $for || $input->matches($for); - } + usort($this->serializers[$context->name], static fn (array $a, array $b) => $a[1] <=> $b[1]); - return false; + return $this; } - private function resolveSerializer(Closure|string $serializerClass, PropertyReflector|TypeReflector|string $input): ?Serializer + /** + * Sets the context that should be passed to serializers. + */ + public function in(Context|UnitEnum|string $context): self { - if (is_string($serializerClass)) { - return get($serializerClass); - } + $serializer = clone $this; + $serializer->context = $context; - try { - return $serializerClass($input); - } catch (TypeError) { - return null; - } + return $serializer; } public function forProperty(PropertyReflector $property): ?Serializer { - $type = $property->getType(); - - // Get SerializerWith from the property - $serializeWith = $property->getAttribute(SerializeWith::class); + $context = MappingContext::from($this->context); - // Get SerializerWith from the property's type if there's no property-defined SerializerWith - if ($serializeWith === null && $type->isClass()) { - $serializeWith = $type->asClass()->getAttribute(SerializeWith::class, recursive: true); + return $this->memoize('[' . $context->name . '] ' . $property->getName(), function () use ($property, $context) { + $context = MappingContext::from($this->context); + $type = $property->getType(); + $serializeWith = $property->getAttribute(SerializeWith::class); - if ($serializeWith === null && $type->asClass()->getAttribute(SerializeAs::class)) { - $serializeWith = new SerializeWith(DtoSerializer::class); + if ($serializeWith === null && ($type->isClass() || $type->isInterface())) { + $serializeWith = $type->asClass()->getAttribute(SerializeWith::class, recursive: true); } - } - - // Return the serializer if defined with SerializerWith - if ($serializeWith !== null) { - // Resolve the serializer from the container - return get($serializeWith->className); - } - if ($serializerAttribute = $property->getAttribute(ProvidesSerializer::class)) { - return get($serializerAttribute->serializer); - } + if ($serializeWith !== null) { + return $this->container->get($serializeWith->className, context: $context); + } - // Resolve serializer from manual additions - foreach ($this->serializers as [$for, $serializerClass]) { - if (! $this->serializerMatches($for, $type)) { - continue; + if ($serializerAttribute = $property->getAttribute(ProvidesSerializer::class)) { + return $this->container->get($serializerAttribute->serializer, context: $context); } - $serializer = $this->resolveSerializer($serializerClass, $property); + foreach ($this->resolveSerializers() as [$serializerClass]) { + if (is_a($serializerClass, DynamicSerializer::class, allow_string: true)) { + if (! $serializerClass::accepts($property)) { + continue; + } + } - if ($serializer !== null) { - return $serializer; + $serializer = $this->resolveSerializer($serializerClass, $property); + + if ($serializer !== null) { + return $serializer; + } } - } - return null; + return null; + }); } public function forValue(mixed $value): ?Serializer @@ -115,13 +103,14 @@ public function forValue(mixed $value): ?Serializer if (is_object($value)) { $input = new ClassReflector($value)->getType(); } else { - $input = gettype($value); + $input = new TypeReflector(gettype($value)); } - // Resolve serializer from manual additions - foreach ($this->serializers as [$for, $serializerClass]) { - if (! $this->serializerMatches($for, $input)) { - continue; + foreach ($this->resolveSerializers() as [$serializerClass]) { + if (is_a($serializerClass, DynamicSerializer::class, allow_string: true)) { + if (! $serializerClass::accepts($input)) { + continue; + } } $serializer = $this->resolveSerializer($serializerClass, $input); @@ -133,4 +122,29 @@ public function forValue(mixed $value): ?Serializer return null; } + + /** + * @param Closure|class-string $serializerClass + */ + private function resolveSerializer(string $serializerClass, PropertyReflector|TypeReflector|string $input): ?Serializer + { + $context = MappingContext::from($this->context); + + if (is_a($serializerClass, ConfigurableSerializer::class, allow_string: true)) { + return $serializerClass::configure($input, $context); + } + + return $this->container->get($serializerClass, context: $context); + } + + /** + * @return array{class-string|Closure,int}[] + */ + private function resolveSerializers(): array + { + return [ + ...($this->serializers[MappingContext::from($this->context)->name] ?? []), + ...($this->serializers[MappingContext::default()->name] ?? []), + ]; + } } diff --git a/packages/mapper/src/SerializerFactoryInitializer.php b/packages/mapper/src/SerializerFactoryInitializer.php deleted file mode 100644 index 9da21e4811..0000000000 --- a/packages/mapper/src/SerializerFactoryInitializer.php +++ /dev/null @@ -1,61 +0,0 @@ -addSerializer('bool', BooleanSerializer::class) - ->addSerializer('boolean', BooleanSerializer::class) - ->addSerializer('float', FloatSerializer::class) - ->addSerializer('double', FloatSerializer::class) - ->addSerializer('int', IntegerSerializer::class) - ->addSerializer('integer', IntegerSerializer::class) - ->addSerializer('string', StringSerializer::class) - ->addSerializer('array', ArrayToJsonSerializer::class) - ->addSerializer(DateTimeInterface::class, DateTimeSerializer::fromReflector(...)) - ->addSerializer(NativeDateTimeImmutable::class, NativeDateTimeSerializer::fromReflector(...)) - ->addSerializer(NativeDateTimeInterface::class, NativeDateTimeSerializer::fromReflector(...)) - ->addSerializer(NativeDateTime::class, NativeDateTimeSerializer::fromReflector(...)) - ->addSerializer(Serializable::class, SerializableSerializer::class) - ->addSerializer(JsonSerializable::class, SerializableSerializer::class) - ->addSerializer(Stringable::class, StringSerializer::class) - ->addSerializer(UnitEnum::class, EnumSerializer::class) - ->addSerializer(BackedEnum::class, EnumSerializer::class) - ->addSerializer(DateTime::class, DateTimeSerializer::fromReflector(...)) - ->addSerializer( - fn (PropertyReflector $property) => $property->getIterableType() !== null, - ArrayOfObjectsSerializer::class, - ); - } -} diff --git a/packages/mapper/src/Serializers/ArrayOfObjectsSerializer.php b/packages/mapper/src/Serializers/ArrayOfObjectsSerializer.php index 5d6c60cd0e..0d3a71c7c3 100644 --- a/packages/mapper/src/Serializers/ArrayOfObjectsSerializer.php +++ b/packages/mapper/src/Serializers/ArrayOfObjectsSerializer.php @@ -4,14 +4,28 @@ namespace Tempest\Mapper\Serializers; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Mappers\ObjectToArrayMapper; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -use function Tempest\map; +use function Tempest\Mapper\map; -final class ArrayOfObjectsSerializer implements Serializer +#[Priority(Priority::HIGHEST)] +final class ArrayOfObjectsSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + if ($input instanceof TypeReflector) { + return false; + } + + return $input->getIterableType() !== null; + } + public function serialize(mixed $input): array { if (! is_array($input)) { diff --git a/packages/mapper/src/Serializers/ArrayToJsonSerializer.php b/packages/mapper/src/Serializers/ArrayToJsonSerializer.php index c8d9d42042..9fc20a17f7 100644 --- a/packages/mapper/src/Serializers/ArrayToJsonSerializer.php +++ b/packages/mapper/src/Serializers/ArrayToJsonSerializer.php @@ -4,13 +4,27 @@ namespace Tempest\Mapper\Serializers; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use Tempest\Support\Arr\ArrayInterface; use Tempest\Support\Json; -final class ArrayToJsonSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class ArrayToJsonSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->getName() === 'array'; + } + public function serialize(mixed $input): string { if ($input instanceof ArrayInterface) { diff --git a/packages/mapper/src/Serializers/BooleanSerializer.php b/packages/mapper/src/Serializers/BooleanSerializer.php index ee65d6b8a1..580a1bed2a 100644 --- a/packages/mapper/src/Serializers/BooleanSerializer.php +++ b/packages/mapper/src/Serializers/BooleanSerializer.php @@ -4,11 +4,25 @@ namespace Tempest\Mapper\Serializers; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class BooleanSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class BooleanSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['bool', 'boolean'], strict: true); + } + public function serialize(mixed $input): string { if (! is_bool($input)) { diff --git a/packages/mapper/src/Serializers/DateTimeSerializer.php b/packages/mapper/src/Serializers/DateTimeSerializer.php index 2169e026ac..85dd2b01d1 100644 --- a/packages/mapper/src/Serializers/DateTimeSerializer.php +++ b/packages/mapper/src/Serializers/DateTimeSerializer.php @@ -5,25 +5,39 @@ namespace Tempest\Mapper\Serializers; use DateTimeInterface as NativeDateTimeInterface; +use Tempest\Core\Priority; use Tempest\DateTime\DateTime; use Tempest\DateTime\DateTimeInterface; use Tempest\DateTime\FormatPattern; +use Tempest\Mapper\ConfigurableSerializer; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; use Tempest\Reflection\PropertyReflector; use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\HasDateTimeFormat; -final readonly class DateTimeSerializer implements Serializer +#[Priority(Priority::HIGHEST)] +final readonly class DateTimeSerializer implements Serializer, DynamicSerializer, ConfigurableSerializer { public function __construct( private FormatPattern|string $format = FormatPattern::SQL_DATE_TIME, ) {} - public static function fromReflector(PropertyReflector|TypeReflector $reflector): self + public static function accepts(PropertyReflector|TypeReflector $input): bool { - if ($reflector instanceof PropertyReflector) { - $format = $reflector->getAttribute(HasDateTimeFormat::class)->format ?? FormatPattern::SQL_DATE_TIME; + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(DateTime::class) || $type->matches(DateTimeInterface::class); + } + + public static function configure(PropertyReflector|TypeReflector|string $input, Context $context): Serializer + { + if ($input instanceof PropertyReflector) { + $format = $input->getAttribute(HasDateTimeFormat::class)->format ?? FormatPattern::SQL_DATE_TIME; } else { $format = FormatPattern::SQL_DATE_TIME; } diff --git a/packages/mapper/src/Serializers/EnumSerializer.php b/packages/mapper/src/Serializers/EnumSerializer.php index 8356f4d27a..bbae4d4dea 100644 --- a/packages/mapper/src/Serializers/EnumSerializer.php +++ b/packages/mapper/src/Serializers/EnumSerializer.php @@ -5,12 +5,26 @@ namespace Tempest\Mapper\Serializers; use BackedEnum; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; use UnitEnum; -final class EnumSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class EnumSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(UnitEnum::class); + } + public function serialize(mixed $input): string { if ($input instanceof BackedEnum) { diff --git a/packages/mapper/src/Serializers/FloatSerializer.php b/packages/mapper/src/Serializers/FloatSerializer.php index 57d6747180..491185bf25 100644 --- a/packages/mapper/src/Serializers/FloatSerializer.php +++ b/packages/mapper/src/Serializers/FloatSerializer.php @@ -4,11 +4,25 @@ namespace Tempest\Mapper\Serializers; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class FloatSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class FloatSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['double', 'float'], strict: true); + } + public function serialize(mixed $input): string { if (! is_float($input)) { diff --git a/packages/mapper/src/Serializers/IntegerSerializer.php b/packages/mapper/src/Serializers/IntegerSerializer.php index d6859da82f..4bd516c400 100644 --- a/packages/mapper/src/Serializers/IntegerSerializer.php +++ b/packages/mapper/src/Serializers/IntegerSerializer.php @@ -4,11 +4,25 @@ namespace Tempest\Mapper\Serializers; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class IntegerSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class IntegerSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return in_array($type->getName(), ['int', 'integer'], strict: true); + } + public function serialize(mixed $input): string { if (! is_int($input)) { diff --git a/packages/mapper/src/Serializers/NativeDateTimeSerializer.php b/packages/mapper/src/Serializers/NativeDateTimeSerializer.php index 44f834dc81..9acffdd2ba 100644 --- a/packages/mapper/src/Serializers/NativeDateTimeSerializer.php +++ b/packages/mapper/src/Serializers/NativeDateTimeSerializer.php @@ -4,23 +4,40 @@ namespace Tempest\Mapper\Serializers; +use DateTime as NativeDateTime; +use DateTimeImmutable as NativeDateTimeImmutable; use DateTimeInterface; +use DateTimeInterface as NativeDateTimeInterface; +use Tempest\Core\Priority; +use Tempest\Mapper\ConfigurableSerializer; +use Tempest\Mapper\Context; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; use Tempest\Reflection\PropertyReflector; use Tempest\Reflection\TypeReflector; use Tempest\Validation\Rules\HasDateTimeFormat; -final readonly class NativeDateTimeSerializer implements Serializer +#[Priority(Priority::HIGHEST)] +final readonly class NativeDateTimeSerializer implements Serializer, DynamicSerializer, ConfigurableSerializer { public function __construct( private string $format = 'Y-m-d H:i:s', ) {} - public static function fromReflector(PropertyReflector|TypeReflector $property): self + public static function accepts(PropertyReflector|TypeReflector $input): bool { - if ($property instanceof PropertyReflector) { - $format = $property->getAttribute(HasDateTimeFormat::class)->format ?? 'Y-m-d H:i:s'; + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(NativeDateTimeInterface::class) || $type->matches(NativeDateTimeImmutable::class) || $type->matches(NativeDateTime::class); + } + + public static function configure(PropertyReflector|TypeReflector|string $input, Context $context): Serializer + { + if ($input instanceof PropertyReflector) { + $format = $input->getAttribute(HasDateTimeFormat::class)->format ?? 'Y-m-d H:i:s'; } else { $format = 'Y-m-d H:i:s'; } diff --git a/packages/mapper/src/Serializers/SerializableSerializer.php b/packages/mapper/src/Serializers/SerializableSerializer.php index 9767d75b2e..f1aa3bf8b1 100644 --- a/packages/mapper/src/Serializers/SerializableSerializer.php +++ b/packages/mapper/src/Serializers/SerializableSerializer.php @@ -6,11 +6,25 @@ use JsonSerializable; use Serializable; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class SerializableSerializer implements Serializer +#[Priority(Priority::LOW)] +final class SerializableSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->matches(Serializable::class) || $type->matches(JsonSerializable::class); + } + public function serialize(mixed $input): array|string { if ($input instanceof JsonSerializable) { diff --git a/packages/mapper/src/Serializers/StringSerializer.php b/packages/mapper/src/Serializers/StringSerializer.php index a8f2e47ce2..8479467d15 100644 --- a/packages/mapper/src/Serializers/StringSerializer.php +++ b/packages/mapper/src/Serializers/StringSerializer.php @@ -5,11 +5,25 @@ namespace Tempest\Mapper\Serializers; use Stringable; +use Tempest\Core\Priority; +use Tempest\Mapper\DynamicSerializer; use Tempest\Mapper\Exceptions\ValueCouldNotBeSerialized; use Tempest\Mapper\Serializer; +use Tempest\Reflection\PropertyReflector; +use Tempest\Reflection\TypeReflector; -final class StringSerializer implements Serializer +#[Priority(Priority::NORMAL)] +final class StringSerializer implements Serializer, DynamicSerializer { + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->getName() === 'string' || $type->matches(Stringable::class); + } + public function serialize(mixed $input): string { if (! is_string($input) && ! $input instanceof Stringable) { diff --git a/packages/mapper/src/functions.php b/packages/mapper/src/functions.php index f001e3b391..59aeb2f297 100644 --- a/packages/mapper/src/functions.php +++ b/packages/mapper/src/functions.php @@ -2,46 +2,43 @@ declare(strict_types=1); -namespace Tempest { - use Tempest\Mapper\ObjectFactory; +namespace Tempest\Mapper; - /** - * Creates a factory which allows instantiating `$objectOrClass` with the data specified by the {@see \Tempest\Mapper\ObjectFactory::from()} method. - * - * ### Example - * ```php - * make(Author::class)->from([ - * 'first_name' => 'Jon', - * 'last_name' => 'Doe', - * ]) - * ``` - * - * @template T of object - * @param T|class-string $objectOrClass - * @return ObjectFactory - */ - function make(object|string $objectOrClass): ObjectFactory - { - $factory = get(ObjectFactory::class); +use Tempest\Container; +use Tempest\Mapper\ObjectFactory; - return $factory->forClass($objectOrClass); - } - - /** - * Creates a factory which allows instantiating the object or class specified by {@see \Tempest\Mapper\ObjectFactory::to()} the given `$data`. - * - * ### Example - * ```php - * map([ - * 'first_name' => 'Jon', - * 'last_name' => 'Doe', - * ])->to($author); - * ``` - */ - function map(mixed $data): ObjectFactory - { - $factory = get(ObjectFactory::class); +/** + * Creates a factory which allows instantiating `$objectOrClass` with the data specified by the {@see \Tempest\Mapper\ObjectFactory::from()} method. + * + * ### Example + * ```php + * make(Author::class)->from([ + * 'first_name' => 'Jon', + * 'last_name' => 'Doe', + * ]) + * ``` + * + * @template T of object + * @param T|class-string $objectOrClass + * @return ObjectFactory + */ +function make(object|string $objectOrClass): ObjectFactory +{ + return Container\get(ObjectFactory::class)->forClass($objectOrClass); +} - return $factory->withData($data); - } +/** + * Creates a factory which allows instantiating the object or class specified by {@see \Tempest\Mapper\ObjectFactory::to()} the given `$data`. + * + * ### Example + * ```php + * map([ + * 'first_name' => 'Jon', + * 'last_name' => 'Doe', + * ])->to($author); + * ``` + */ +function map(mixed $data): ObjectFactory +{ + return Container\get(ObjectFactory::class)->withData($data); } diff --git a/packages/process/composer.json b/packages/process/composer.json index 97e073f3cc..da0cef9442 100644 --- a/packages/process/composer.json +++ b/packages/process/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "symfony/process": "^7.3", "tempest/container": "dev-main", "tempest/support": "dev-main", diff --git a/packages/process/src/Testing/ProcessExecutionWasForbidden.php b/packages/process/src/Testing/ProcessExecutionWasForbidden.php index cfb26db9d9..e9f13abf4f 100644 --- a/packages/process/src/Testing/ProcessExecutionWasForbidden.php +++ b/packages/process/src/Testing/ProcessExecutionWasForbidden.php @@ -3,13 +3,13 @@ namespace Tempest\Process\Testing; use Exception; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; use Tempest\Process\Exceptions\ProcessException; use Tempest\Process\PendingProcess; use function Tempest\Support\arr; -final class ProcessExecutionWasForbidden extends Exception implements ProcessException, HasContext +final class ProcessExecutionWasForbidden extends Exception implements ProcessException, ProvidesContext { private function __construct( string $message, diff --git a/packages/reflection/composer.json b/packages/reflection/composer.json index 896595215b..2e5525bb8e 100644 --- a/packages/reflection/composer.json +++ b/packages/reflection/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4" + "php": "^8.5" }, "autoload": { "psr-4": { diff --git a/packages/reflection/src/PropertyReflector.php b/packages/reflection/src/PropertyReflector.php index 455d8c898b..3eda1ce131 100644 --- a/packages/reflection/src/PropertyReflector.php +++ b/packages/reflection/src/PropertyReflector.php @@ -157,4 +157,9 @@ public function hasDefaultValue(): bool return $hasDefaultValue || $hasPromotedDefaultValue; } + + public function __toString(): string + { + return $this->getClass()->getName() . '::' . $this->getName(); + } } diff --git a/packages/reflection/src/TypeReflector.php b/packages/reflection/src/TypeReflector.php index a5e7bc19ff..acfcb03177 100644 --- a/packages/reflection/src/TypeReflector.php +++ b/packages/reflection/src/TypeReflector.php @@ -122,7 +122,7 @@ public function accepts(mixed $input): bool public function matches(string $className): bool { - return is_a($this->cleanDefinition, $className, true); + return is_a($this->cleanDefinition, $className, allow_string: true); } public function getName(): string @@ -184,15 +184,7 @@ public function isIterable(): bool return true; } - return in_array( - $this->cleanDefinition, - [ - 'array', - 'iterable', - Generator::class, - ], - strict: true, - ); + return in_array($this->cleanDefinition, ['array', 'iterable', Generator::class], strict: true); } public function isStringable(): bool @@ -201,13 +193,7 @@ public function isStringable(): bool return true; } - return in_array( - $this->cleanDefinition, - [ - 'string', - ], - strict: true, - ); + return in_array($this->cleanDefinition, ['string'], strict: true); } public function isNullable(): bool @@ -215,6 +201,16 @@ public function isNullable(): bool return $this->isNullable; } + public function isUnion(): bool + { + return str_contains($this->definition, '|'); + } + + public function isIntersection(): bool + { + return str_contains($this->definition, '&'); + } + /** @return self[] */ public function split(): array { @@ -270,7 +266,14 @@ private function resolveDefinition(PHPReflector|PHPReflectionType|string $reflec private function resolveIsNullable(PHPReflectionType|PHPReflector|string $reflector): bool { if (is_string($reflector)) { - return str_contains($this->definition, '?') || str_contains($this->definition, 'null'); + if (str_contains($this->definition, '?')) { + return true; + } + + return array_any( + array: preg_split('/[&|]/', $this->definition), + callback: static fn (string $type) => $type === 'null', + ); } if ($reflector instanceof PHPReflectionParameter || $reflector instanceof PHPReflectionProperty) { diff --git a/packages/reflection/src/functions.php b/packages/reflection/src/functions.php index e663ed9e8f..20f07a96eb 100644 --- a/packages/reflection/src/functions.php +++ b/packages/reflection/src/functions.php @@ -2,29 +2,29 @@ declare(strict_types=1); -namespace Tempest { - use ReflectionClass as PHPReflectionClass; - use ReflectionProperty as PHPReflectionProperty; - use Tempest\Reflection\ClassReflector; - use Tempest\Reflection\PropertyReflector; +namespace Tempest\Reflection; - /** - * Creates a new {@see Reflector} instance based on the given `$classOrProperty`. - */ - function reflect(mixed $classOrProperty, ?string $propertyName = null): ClassReflector|PropertyReflector - { - if ($classOrProperty instanceof PHPReflectionClass) { - return new ClassReflector($classOrProperty); - } +use ReflectionClass as PHPReflectionClass; +use ReflectionProperty as PHPReflectionProperty; +use Tempest\Reflection\ClassReflector; +use Tempest\Reflection\PropertyReflector; - if ($classOrProperty instanceof PHPReflectionProperty) { - return new PropertyReflector($classOrProperty); - } +/** + * Creates a new {@see Reflector} instance based on the given `$classOrProperty`. + */ +function reflect(mixed $classOrProperty, ?string $propertyName = null): ClassReflector|PropertyReflector +{ + if ($classOrProperty instanceof PHPReflectionClass) { + return new ClassReflector($classOrProperty); + } - if ($propertyName !== null) { - return new PropertyReflector(new PHPReflectionProperty($classOrProperty, $propertyName)); - } + if ($classOrProperty instanceof PHPReflectionProperty) { + return new PropertyReflector($classOrProperty); + } - return new ClassReflector($classOrProperty); + if ($propertyName !== null) { + return new PropertyReflector(new PHPReflectionProperty($classOrProperty, $propertyName)); } + + return new ClassReflector($classOrProperty); } diff --git a/packages/reflection/tests/Fixtures/AnnulledInvoice.php b/packages/reflection/tests/Fixtures/AnnulledInvoice.php new file mode 100644 index 0000000000..f5d5c7f8fc --- /dev/null +++ b/packages/reflection/tests/Fixtures/AnnulledInvoice.php @@ -0,0 +1,9 @@ +assertFalse(new TypeReflector(ImmutableString::class)->isRelation()); $this->assertFalse(new TypeReflector(DateTimeImmutable::class)->isRelation()); } + + public function test_is_union(): void + { + $this->assertTrue(new TypeReflector('string|int')->isUnion()); + $this->assertTrue(new TypeReflector(A::class . '|' . B::class)->isUnion()); + $this->assertFalse(new TypeReflector('?string')->isUnion()); + $this->assertFalse(new TypeReflector('string')->isUnion()); + $this->assertFalse(new TypeReflector(A::class)->isUnion()); + $this->assertFalse(new TypeReflector('string&Stringable')->isUnion()); + } + + public function test_is_intersection(): void + { + $this->assertTrue(new TypeReflector('string&Stringable')->isIntersection()); + $this->assertTrue(new TypeReflector(A::class . '&' . B::class)->isIntersection()); + $this->assertFalse(new TypeReflector('?string')->isIntersection()); + $this->assertFalse(new TypeReflector('string')->isIntersection()); + $this->assertFalse(new TypeReflector(A::class)->isIntersection()); + $this->assertFalse(new TypeReflector('string|int')->isIntersection()); + } } diff --git a/packages/reflection/tests/TypeReflectorTest.php b/packages/reflection/tests/TypeReflectorTest.php index 623aa19be6..4946c4472f 100644 --- a/packages/reflection/tests/TypeReflectorTest.php +++ b/packages/reflection/tests/TypeReflectorTest.php @@ -4,7 +4,10 @@ use PHPUnit\Framework\TestCase; use Tempest\Reflection\ClassReflector; +use Tempest\Reflection\Tests\Fixtures\AnnulledInvoice; +use Tempest\Reflection\Tests\Fixtures\NullableClass; use Tempest\Reflection\Tests\Fixtures\TestClassA; +use Tempest\Reflection\TypeReflector; final class TypeReflectorTest extends TestCase { @@ -90,4 +93,18 @@ public function test_is_enum(): void ->isUnitEnum(), ); } + + public function test_is_nullable(): void + { + $this->assertTrue(new TypeReflector('?string')->isNullable()); + $this->assertTrue(new TypeReflector('string|null')->isNullable()); + $this->assertTrue(new TypeReflector('null')->isNullable()); + $this->assertFalse(new TypeReflector('string')->isNullable()); + } + + public function test_class_name_containing_null_is_not_nullable(): void + { + $this->assertFalse(new TypeReflector(AnnulledInvoice::class)->isNullable()); + $this->assertFalse(new TypeReflector(NullableClass::class)->isNullable()); + } } diff --git a/packages/router/composer.json b/packages/router/composer.json index cab9eb741b..91f5aa1708 100644 --- a/packages/router/composer.json +++ b/packages/router/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/http": "dev-main", "tempest/view": "dev-main", "tempest/highlight": "^2.11.4", diff --git a/packages/router/src/Commands/MakeControllerCommand.php b/packages/router/src/Commands/MakeControllerCommand.php index 973f4770de..5052d4aede 100644 --- a/packages/router/src/Commands/MakeControllerCommand.php +++ b/packages/router/src/Commands/MakeControllerCommand.php @@ -8,8 +8,8 @@ use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; use Tempest\Discovery\SkipDiscovery; -use Tempest\Generation\ClassManipulator; -use Tempest\Generation\DataObjects\StubFile; +use Tempest\Generation\Php\ClassManipulator; +use Tempest\Generation\Php\DataObjects\StubFile; use Tempest\Router\Stubs\ControllerStub; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { diff --git a/packages/router/src/ExceptionRendererDiscovery.php b/packages/router/src/ExceptionRendererDiscovery.php new file mode 100644 index 0000000000..c63f3fc58a --- /dev/null +++ b/packages/router/src/ExceptionRendererDiscovery.php @@ -0,0 +1,39 @@ +implements(ExceptionRenderer::class)) { + return; + } + + $priority = $class->getAttribute(Priority::class)->priority ?? Priority::NORMAL; + + $this->discoveryItems->add($location, [$class->getName(), $priority]); + } + + public function apply(): void + { + foreach ($this->discoveryItems as [$className, $priority]) { + $this->routeConfig->addExceptionRenderer($className, $priority); + } + } +} diff --git a/packages/router/src/Exceptions/ConvertsToResponse.php b/packages/router/src/Exceptions/ConvertsToResponse.php index d8389bca3e..adf912c1c7 100644 --- a/packages/router/src/Exceptions/ConvertsToResponse.php +++ b/packages/router/src/Exceptions/ConvertsToResponse.php @@ -14,5 +14,5 @@ interface ConvertsToResponse /** * Gets a response to be sent to the client. */ - public function toResponse(): Response; + public function convertToResponse(): Response; } diff --git a/packages/router/src/Exceptions/DevelopmentException.php b/packages/router/src/Exceptions/DevelopmentException.php new file mode 100644 index 0000000000..c1d3608756 --- /dev/null +++ b/packages/router/src/Exceptions/DevelopmentException.php @@ -0,0 +1,109 @@ +status = Status::INTERNAL_SERVER_ERROR; + + if (! Filesystem\exists(__DIR__ . '/local/dist/main.js')) { + $this->body = 'The development exception interface is not built.'; + return; + } + + $stacktrace = Stacktrace::fromThrowable($throwable, rootPath: root_path()); + + if ($throwable instanceof ViewCompilationFailed) { + $stacktrace = $this->enhanceStacktraceForViewCompilation($throwable, $stacktrace); + } + + $this->body = new GenericView( + path: __DIR__ . '/local/exception.view.php', + data: [ + 'script' => Filesystem\read_file(__DIR__ . '/local/dist/main.js'), + 'css' => Filesystem\read_file(__DIR__ . '/local/dist/style.css'), + 'hydration' => map([ + 'stacktrace' => json_encode($stacktrace), + 'context' => $throwable instanceof ProvidesContext ? $throwable->context() : [], + 'rootPath' => root_path(), + 'request' => [ + 'uri' => $request->uri, + 'method' => $request->method, + 'headers' => $request->headers->toArray(), + 'body' => $request->raw, + ], + 'response' => [ + 'status' => $response->status->value, + ], + 'resources' => [ + 'memoryPeakUsage' => memory_get_peak_usage(real_usage: true), + 'executionTimeMs' => (hrtime(as_number: true) - TEMPEST_START) / 1_000_000, + ], + 'versions' => [ + 'php' => PHP_VERSION, + 'tempest' => Kernel::VERSION, + ], + ])->toJson(), + ], + ); + } + + private function enhanceStacktraceForViewCompilation(ViewCompilationFailed $exception, Stacktrace $stacktrace): Stacktrace + { + $previous = $exception->getPrevious(); + + if (! $previous) { + return $stacktrace; + } + + $lines = explode("\n", $exception->content); + $errorLine = $previous->getLine(); + $contextLines = 5; + $startLine = max(1, $errorLine - $contextLines); + $endLine = min(count($lines), $errorLine + $contextLines); + $snippetLines = []; + + for ($i = $startLine; $i <= $endLine; $i++) { + $snippetLines[$i] = $lines[$i - 1]; + } + + return $stacktrace->prependFrame(new Frame( + line: $errorLine, + class: TempestViewRenderer::class, + function: 'renderCompiled', + type: '->', + isVendor: false, + snippet: new CodeSnippet( + lines: $snippetLines, + highlightedLine: $errorLine, + ), + absoluteFile: $exception->path, + relativeFile: to_relative_path(root_path(), $exception->path), + arguments: [], + index: 1, + )); + } +} diff --git a/packages/router/src/Exceptions/ExceptionRenderer.php b/packages/router/src/Exceptions/ExceptionRenderer.php new file mode 100644 index 0000000000..5219680f15 --- /dev/null +++ b/packages/router/src/Exceptions/ExceptionRenderer.php @@ -0,0 +1,25 @@ +accepts(ContentType::HTML, ContentType::XHTML); + } + + public function render(Throwable $throwable): Response + { + $response = match (true) { + $throwable instanceof ValidationFailed => $this->renderValidationFailedResponse($throwable), + $throwable instanceof AccessWasDenied => $this->renderErrorResponse(Status::FORBIDDEN, message: $throwable->accessDecision->message), + $throwable instanceof HttpRequestFailed => $this->renderHttpRequestFailed($throwable), + $throwable instanceof ConvertsToResponse => $throwable->convertToResponse(), + default => $this->renderErrorResponse(Status::INTERNAL_SERVER_ERROR), + }; + + if ($this->shouldRenderDevelopmentException($throwable)) { + return new DevelopmentException( + throwable: $throwable, + response: $response, + request: $this->request, + ); + } + + return $response; + } + + private function renderHttpRequestFailed(HttpRequestFailed $exception): Response + { + if ($exception->getMessage() !== '') { + return $this->renderErrorResponse($exception->status, message: $exception->getMessage()); + } + + if ($exception->cause && is_string($exception->cause->body)) { + return $this->renderErrorResponse($exception->status, message: $exception->cause->body); + } + + if ($exception->cause && $exception->cause->body) { + return $exception->cause; + } + + return $this->renderErrorResponse($exception->status); + } + + private function renderErrorResponse(Status $status, ?string $message = null): Response + { + return new GenericResponse( + status: $status, + body: new GenericView(__DIR__ . '/production/error.view.php', [ + 'css' => $this->getStyleSheet(), + 'status' => $status->value, + 'title' => $status->description(), + 'message' => $message ?? $this->translator->translate("http_status_error.{$status->value}"), + ]), + ); + } + + private function getStyleSheet(): string + { + return Filesystem\read_file(__DIR__ . '/production/style.css'); + } + + private function shouldRenderDevelopmentException(Throwable $throwable): bool + { + if (! $this->environment->isLocal()) { + return false; + } + + if ($throwable instanceof ValidationFailed) { + return false; + } + + if (! $throwable instanceof HttpRequestFailed) { + return true; + } + + if ($throwable->status === Status::NOT_FOUND) { + return false; + } + + return true; + } + + private function renderValidationFailedResponse(ValidationFailed $exception): Response + { + $status = Status::UNPROCESSABLE_CONTENT; + $headers = []; + + if ($referer = $this->request->headers->get('referer')) { + $headers['Location'] = $referer; + $status = Status::FOUND; + } + + if ($this->container->has(Session::class)) { + $this->container->get(FormSession::class)->setErrors($exception->failingRules); + $this->container->get(FormSession::class)->setOriginalValues($this->filterSensitiveFields($this->request, $exception->targetClass)); + $this->container->get(SessionManager::class)->save($this->container->get(FormSession::class)->session); + } + + $errors = Arr\map($exception->failingRules, fn (array $failingRulesForField, string $field) => Arr\map( + array: $failingRulesForField, + map: fn (FailingRule $rule) => $this->validator->getErrorMessage($rule, $field), + )); + + $headers['x-validation'] = Json\encode($errors); + + return new GenericResponse( + status: $status, + headers: $headers, + ); + } + + /** + * @param class-string|null $targetClass + */ + private function filterSensitiveFields(Request $request, ?string $targetClass): array + { + $body = $request->body; + + if ($targetClass === null) { + return $body; + } + + $reflector = new ClassReflector($targetClass); + + foreach ($reflector->getPublicProperties() as $property) { + if ($property->hasAttribute(SensitiveField::class)) { + unset($body[$property->getName()]); + } + } + + return $body; + } +} diff --git a/packages/router/src/Exceptions/HttpExceptionHandler.php b/packages/router/src/Exceptions/HttpExceptionHandler.php index fe093c0b01..da55626515 100644 --- a/packages/router/src/Exceptions/HttpExceptionHandler.php +++ b/packages/router/src/Exceptions/HttpExceptionHandler.php @@ -2,73 +2,54 @@ namespace Tempest\Router\Exceptions; -use Tempest\Auth\Exceptions\AccessWasDenied; use Tempest\Container\Container; -use Tempest\Core\AppConfig; use Tempest\Core\ExceptionHandler; -use Tempest\Core\ExceptionReporter; +use Tempest\Core\Exceptions\ExceptionProcessor; use Tempest\Core\Kernel; use Tempest\Http\GenericResponse; -use Tempest\Http\HttpRequestFailed; +use Tempest\Http\Request; use Tempest\Http\Response; -use Tempest\Http\Session\CsrfTokenDidNotMatch; use Tempest\Http\Status; use Tempest\Router\ResponseSender; -use Tempest\Support\Filesystem; -use Tempest\View\GenericView; +use Tempest\Router\RouteConfig; +use Tempest\Support\Arr; use Throwable; final readonly class HttpExceptionHandler implements ExceptionHandler { public function __construct( - private AppConfig $appConfig, - private Kernel $kernel, private ResponseSender $responseSender, + private Kernel $kernel, private Container $container, - private ExceptionReporter $exceptionReporter, + private ExceptionProcessor $exceptionProcessor, + private RouteConfig $routeConfig, ) {} public function handle(Throwable $throwable): void { - try { - $this->exceptionReporter->report($throwable); + $request = $this->container->get(Request::class); - $response = match (true) { - $throwable instanceof ConvertsToResponse => $throwable->toResponse(), - $throwable instanceof AccessWasDenied => $this->renderErrorResponse(Status::FORBIDDEN), - $throwable instanceof HttpRequestFailed => $this->renderErrorResponse($throwable->status, $throwable), - $throwable instanceof CsrfTokenDidNotMatch => $this->renderErrorResponse(Status::UNPROCESSABLE_CONTENT), - default => $this->renderErrorResponse(Status::INTERNAL_SERVER_ERROR), - }; - - $this->responseSender->send($response); + try { + $this->exceptionProcessor->process($throwable); + $this->responseSender->send($this->renderResponse($request, $throwable)); } finally { $this->kernel->shutdown(); } } - private function renderErrorResponse(Status $status, ?HttpRequestFailed $exception = null): Response + public function renderResponse(Request $request, Throwable $throwable): Response { - return new GenericResponse( - status: $status, - body: new GenericView(__DIR__ . '/HttpErrorResponse/error.view.php', [ - 'css' => $this->getStyleSheet(), - 'status' => $status->value, - 'title' => $status->description(), - 'message' => $exception?->getMessage() ?: match ($status) { - Status::INTERNAL_SERVER_ERROR => 'An unexpected server error occurred', - Status::NOT_FOUND => 'This page could not be found on the server', - Status::FORBIDDEN => 'You do not have permission to access this page', - Status::UNAUTHORIZED => 'You must be authenticated in to access this page', - Status::UNPROCESSABLE_CONTENT => 'The request could not be processed due to invalid data', - default => null, - }, - ]), - ); - } + ksort($this->routeConfig->exceptionRenderers); - private function getStyleSheet(): string - { - return Filesystem\read_file(__DIR__ . '/HttpErrorResponse/style.css'); + foreach (Arr\flatten($this->routeConfig->exceptionRenderers) as $rendererClass) { + /** @var ExceptionRenderer $renderer */ + $renderer = $this->container->get($rendererClass); + + if ($renderer->canRender($throwable, $request)) { + return $renderer->render($throwable); + } + } + + return new GenericResponse(status: Status::NOT_ACCEPTABLE); } } diff --git a/packages/router/src/Exceptions/JsonExceptionRenderer.php b/packages/router/src/Exceptions/JsonExceptionRenderer.php new file mode 100644 index 0000000000..9a3c247be2 --- /dev/null +++ b/packages/router/src/Exceptions/JsonExceptionRenderer.php @@ -0,0 +1,109 @@ +accepts(ContentType::JSON); + } + + public function render(Throwable $throwable): Response + { + return match (true) { + $throwable instanceof ValidationFailed => $this->renderValidationFailedResponse($throwable), + $throwable instanceof AccessWasDenied => $this->renderErrorResponse(Status::FORBIDDEN, message: $throwable->accessDecision->message), + $throwable instanceof HttpRequestFailed => $this->renderHttpRequestFailed($throwable), + $throwable instanceof ConvertsToResponse => $throwable->convertToResponse(), + default => $this->renderErrorResponse(Status::INTERNAL_SERVER_ERROR, throwable: $throwable), + }; + } + + private function renderHttpRequestFailed(HttpRequestFailed $exception): Response + { + if ($exception->getMessage() !== '') { + return $this->renderErrorResponse($exception->status, message: $exception->getMessage()); + } + + if ($exception->cause && is_string($exception->cause->body)) { + return $this->renderErrorResponse($exception->status, message: $exception->cause->body); + } + + if ($exception->cause && $exception->cause->body) { + return $exception->cause; + } + + return $this->renderErrorResponse($exception->status, throwable: $exception); + } + + private function renderValidationFailedResponse(ValidationFailed $exception): Response + { + $errors = Arr\map($exception->failingRules, fn (array $failingRulesForField, string $field) => Arr\map( + array: $failingRulesForField, + map: fn (FailingRule $rule) => $this->validator->getErrorMessage($rule, $field), + )); + + return new Json( + body: [ + 'message' => Arr\first($errors)[0], + 'errors' => $errors, + ], + status: Status::UNPROCESSABLE_CONTENT, + headers: ['x-validation' => encode($errors)], + ); + } + + private function renderErrorResponse(Status $status, ?string $message = null, ?Throwable $throwable = null): Response + { + if ($status === Status::NOT_FOUND) { + return new NotFound(); + } + + $body = [ + 'message' => $message ?? $status->description(), + ]; + + if ($this->environment->isLocal() && $throwable !== null) { + $body['debug'] = array_filter([ + 'message' => $throwable->getMessage(), + 'exception' => get_class($throwable), + 'file' => $throwable->getFile(), + 'line' => $throwable->getLine(), + 'trace' => Arr\map( + array: $throwable->getTrace(), + map: fn (array $trace) => Arr\remove_keys($trace, 'args'), + ), + ]); + } + + return new Json( + body: $body, + status: $status, + ); + } +} diff --git a/packages/router/src/Exceptions/local/.gitignore b/packages/router/src/Exceptions/local/.gitignore new file mode 100644 index 0000000000..4330955aa7 --- /dev/null +++ b/packages/router/src/Exceptions/local/.gitignore @@ -0,0 +1,4 @@ +node_modules +auto-imports.d.ts +components.d.ts +!dist diff --git a/packages/router/src/Exceptions/local/.vscode/settings.json b/packages/router/src/Exceptions/local/.vscode/settings.json new file mode 100644 index 0000000000..d1227e6353 --- /dev/null +++ b/packages/router/src/Exceptions/local/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "files.associations": { + "*.css": "tailwindcss" + }, + "editor.quickSuggestions": { + "strings": "on" + }, + "tailwindCSS.classAttributes": ["class", "ui"], + "tailwindCSS.experimental.classRegex": [ + ["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ] +} diff --git a/packages/router/src/Exceptions/local/bun.lock b/packages/router/src/Exceptions/local/bun.lock new file mode 100644 index 0000000000..ce071a8da5 --- /dev/null +++ b/packages/router/src/Exceptions/local/bun.lock @@ -0,0 +1,1287 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "tempest-exception-ui", + "devDependencies": { + "@nuxt/ui": "^4.3.0", + "@shikijs/langs-precompiled": "^3.21.0", + "@tailwindcss/vite": "^4.1.18", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "@vueuse/components": "^14.1.0", + "@vueuse/core": "^14.1.0", + "arktype": "^2.1.29", + "shiki": "^3.21.0", + "sql-formatter": "^15.7.0", + "tailwindcss": "^4.1.18", + "temporal-polyfill": "^0.3.0", + "typescript": "~5.9.3", + "vite": "8.0.0-beta.2", + "vite-plugin-singlefile": "^2.3.0", + "vue": "^3.5.27", + "vue-router": "^4.6.4", + "vue-tsc": "^3.2.2", + }, + }, + }, + "trustedDependencies": [ + "@tailwindcss/oxide", + ], + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], + + "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], + + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], + + "@capsizecss/unpack": ["@capsizecss/unpack@3.0.1", "", { "dependencies": { "fontkit": "^2.0.2" } }, "sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg=="], + + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + + "@floating-ui/vue": ["@floating-ui/vue@1.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.0.0", "@floating-ui/utils": "^0.2.9", "vue-demi": ">=0.13.0" } }, "sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A=="], + + "@iconify/collections": ["@iconify/collections@1.0.629", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-1iT8HyMKpOvml6jxZDaW2dkdgzls4Ik7I/tn79hHqbPGWkNpIQsJSB3Dto+vAyboXLtsRvIKIwtSvfgrHR0HRw=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], + + "@iconify/vue": ["@iconify/vue@5.0.0", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3" } }, "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg=="], + + "@internationalized/date": ["@internationalized/date@3.10.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA=="], + + "@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], + + "@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.1.1", "", { "dependencies": { "@nuxt/kit": "^4.2.1", "execa": "^8.0.1" }, "peerDependencies": { "vite": ">=6.0" } }, "sha512-sjiKFeDCOy1SyqezSgyV4rYNfQewC64k/GhOsuJgRF+wR2qr6KTVhO6u2B+csKs74KrMrnJprQBgud7ejvOXAQ=="], + + "@nuxt/fonts": ["@nuxt/fonts@0.12.1", "", { "dependencies": { "@nuxt/devtools-kit": "^3.0.1", "@nuxt/kit": "^4.2.1", "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.25.12", "fontaine": "^0.7.0", "fontless": "^0.1.0", "h3": "^1.15.4", "jiti": "^2.6.1", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "node-fetch-native": "^1.6.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.1", "unifont": "^0.6.0", "unplugin": "^2.3.10", "unstorage": "^1.17.2" } }, "sha512-ALajI/HE+uqqL/PWkWwaSUm1IdpyGPbP3mYGy2U1l26/o4lUZBxjFaduMxaZ85jS5yQeJfCu2eEHANYFjAoujQ=="], + + "@nuxt/icon": ["@nuxt/icon@2.1.1", "", { "dependencies": { "@iconify/collections": "^1.0.628", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0", "@iconify/vue": "^5.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "local-pkg": "^1.1.2", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15" } }, "sha512-KX991xA64ttUQYXnLFafOw8EYxmmGRtnd2z1P9PjMOeSxxLXxUL1v9fKH2njqtPkamiOI0fvthxfJpJ4uH71sw=="], + + "@nuxt/kit": ["@nuxt/kit@4.2.2", "", { "dependencies": { "c12": "^3.3.2", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^2.1.2", "scule": "^1.3.0", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ufo": "^1.6.1", "unctx": "^2.4.1", "untyped": "^2.0.0" } }, "sha512-ZAgYBrPz/yhVgDznBNdQj2vhmOp31haJbO0I0iah/P9atw+OHH7NJLUZ3PK+LOz/0fblKTN1XJVSi8YQ1TQ0KA=="], + + "@nuxt/schema": ["@nuxt/schema@4.2.2", "", { "dependencies": { "@vue/shared": "^3.5.25", "defu": "^6.1.4", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "std-env": "^3.10.0" } }, "sha512-lW/1MNpO01r5eR/VoeanQio8Lg4QpDklMOHa4mBHhhPNlBO1qiRtVYzjcnNdun3hujGauRaO9khGjv93Z5TZZA=="], + + "@nuxt/ui": ["@nuxt/ui@4.3.0", "", { "dependencies": { "@iconify/vue": "^5.0.0", "@internationalized/date": "^3.10.1", "@internationalized/number": "^3.6.5", "@nuxt/fonts": "^0.12.1", "@nuxt/icon": "^2.1.1", "@nuxt/kit": "^4.2.2", "@nuxt/schema": "^4.2.2", "@nuxtjs/color-mode": "^3.5.2", "@standard-schema/spec": "^1.1.0", "@tailwindcss/postcss": "^4.1.18", "@tailwindcss/vite": "^4.1.18", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.13", "@tiptap/core": "3.13.0", "@tiptap/extension-bubble-menu": "3.13.0", "@tiptap/extension-drag-handle-vue-3": "3.13.0", "@tiptap/extension-floating-menu": "3.13.0", "@tiptap/extension-horizontal-rule": "3.13.0", "@tiptap/extension-image": "3.13.0", "@tiptap/extension-mention": "3.13.0", "@tiptap/extension-placeholder": "3.13.0", "@tiptap/markdown": "3.13.0", "@tiptap/pm": "3.13.0", "@tiptap/starter-kit": "3.13.0", "@tiptap/suggestion": "3.13.0", "@tiptap/vue-3": "3.13.0", "@unhead/vue": "^2.0.19", "@vueuse/core": "^14.1.0", "@vueuse/integrations": "^14.1.0", "colortranslator": "^5.0.0", "consola": "^3.4.2", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.6.0", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-class-names": "^8.6.0", "embla-carousel-fade": "^8.6.0", "embla-carousel-vue": "^8.6.0", "embla-carousel-wheel-gestures": "^8.1.0", "fuse.js": "^7.1.0", "hookable": "^5.5.3", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.0", "motion-v": "^1.7.3", "ohash": "^2.0.11", "pathe": "^2.0.3", "reka-ui": "2.6.1", "scule": "^1.3.0", "tailwind-merge": "^3.4.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.1.18", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-auto-import": "^20.3.0", "unplugin-vue-components": "^30.0.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "^3.1.5" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7", "@nuxt/content": "^3.0.0", "joi": "^18.0.0", "superstruct": "^2.0.0", "typescript": "^5.6.3", "valibot": "^1.0.0", "vue-router": "^4.5.0", "yup": "^1.7.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@inertiajs/vue3", "@nuxt/content", "joi", "superstruct", "valibot", "vue-router", "yup", "zod"], "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-zhOIba3roiqNwV/hXXkKBlv9RA01/Gd2Okydpgps2zM4KGx6RM+ED5JGUOSd41bmTeBRO7v7Lg4w3Vyj9hQPiA=="], + + "@nuxtjs/color-mode": ["@nuxtjs/color-mode@3.5.2", "", { "dependencies": { "@nuxt/kit": "^3.13.2", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "semver": "^7.6.3" } }, "sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA=="], + + "@oxc-project/runtime": ["@oxc-project/runtime@0.102.0", "", {}, "sha512-vEDGxVIeeO+u5XCHD5+iSzWwC3DgRpEaf3lPZETC+6GnoRKHaxbxV6XqpbOhiY423RVkAbBEtfetfrjJjPWByA=="], + + "@oxc-project/types": ["@oxc-project/types@0.102.0", "", {}, "sha512-8Skrw405g+/UJPKWJ1twIk3BIH2nXdiVlVNtYT23AXVwpsd79es4K+KYt06Fbnkc5BaTvk/COT2JuCLYdwnCdA=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@remirror/core-constants": ["@remirror/core-constants@3.0.0", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.54", "", { "os": "android", "cpu": "arm64" }, "sha512-zZRx/ur3Fai3fxiEmVp48+6GCBR48PRWJR1X3TTMn9yiq2bBHlYPgBaQtDOYWXv5H3J5dXujeTyGnuoY+kdGCg=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.54", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zMyFEJmbIs91x22HAA/eUvmZHgjX8tGsD3TJ+WC9aY4bCdl3w84H9vMZmChSHAF1dYvGNH4KQDI2IubeZaCYtg=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.54", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ex7QttdaVnEpmE/zroUT5Qm10e2+Vjd9q0LX9eXm59SitxDODMpC8GI1Rct5RrLf4GLU4DzdXBj6DGzuR+6g6w=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.54", "", { "os": "freebsd", "cpu": "x64" }, "sha512-E1XO10ryM/Vxw3Q1wvs9s2mSpVBfbHtzkbJcdu26qh17ZmVwNWLiIoqEcbkXm028YwkReG4Gd2gCZ3NxgTQ28Q=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.54", "", { "os": "linux", "cpu": "arm" }, "sha512-oS73Uks8jczQR9pg0Bj718vap/x71exyJ5yuxu4X5V4MhwRQnky7ANSPm6ARUfraxOqt49IBfcMeGnw2rTSqdA=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.54", "", { "os": "linux", "cpu": "arm64" }, "sha512-pY8N2X5C+/ZQcy0eRdfOzOP//OFngP1TaIqDjFwfBPws2UNavKS8SpxhPEgUaYIaT0keVBd/TB+eVy9z+CIOtw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.54", "", { "os": "linux", "cpu": "arm64" }, "sha512-cgTooAFm2MUmFriB7IYaWBNyqrGlRPKG+yaK2rGFl2rcdOcO24urY4p3eyB0ogqsRLvJbIxwjjYiWiIP7Eo1Cw=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.54", "", { "os": "linux", "cpu": "x64" }, "sha512-nGyLT1Qau0W+kEL44V2jhHmvfS3wyJW08E4WEu2E6NuIy+uChKN1X0aoxzFIDi2owDsYaZYez/98/f268EupIQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.54", "", { "os": "linux", "cpu": "x64" }, "sha512-KH374P0TUjDXssROT/orvzaWrzGOptD13PTrltgKwbDprJTMknoLiYsOD6Ttz92O2VuAcCtFuJ1xbyFM2Uo/Xg=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.54", "", { "os": "none", "cpu": "arm64" }, "sha512-oMAVO4wbfAbhpBxPsSp8R7ntL2DchpNfO+tGhN8/sI9jsbYwOv78uIW1fTwOBslhjTVFltGJ+l23mubNQcYNaQ=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.54", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.0" }, "cpu": "none" }, "sha512-MYY/FmY+HehHiQkNx04W5oLy/Fqd1hXYqZmmorSDXvAHnxMbSgmdFicKsSYOg/sVGHBMEP1tTn6kV5sWrS45rA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.54", "", { "os": "win32", "cpu": "arm64" }, "sha512-66o3uKxUmcYskT9exskxs3OVduXf5x0ndlMkYOjSpBgqzhLtkub136yDvZkNT1OkNDET0odSwcU7aWdpnwzAyg=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.54", "", { "os": "win32", "cpu": "x64" }, "sha512-FbbbrboChLBXfeEsOfaypBGqzbdJ/CcSA2BPLCggojnIHy58Jo+AXV7HATY8opZk7194rRbokIT8AfPJtZAWtg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], + + "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], + + "@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], + + "@shikijs/langs-precompiled": ["@shikijs/langs-precompiled@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "oniguruma-to-es": "^4.3.4" } }, "sha512-nG3QpmFHToJuBk3DmVgKgxryZGlYZdn8nve1kJmyHAteDu0mUGuzO7gLcEafNUXpMzGdxpor0IdeOac7JVO2Iw=="], + + "@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="], + + "@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="], + + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.13", "", {}, "sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA=="], + + "@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="], + + "@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.13", "", { "dependencies": { "@tanstack/virtual-core": "3.13.13" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-Cf2xIEE8nWAfsX0N5nihkPYMeQRT+pHt4NEkuP8rNCn6lVnLDiV8rC8IeIxbKmQC0yPnj4SIBLwXYVf86xxKTQ=="], + + "@tiptap/core": ["@tiptap/core@3.13.0", "", { "peerDependencies": { "@tiptap/pm": "^3.13.0" } }, "sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ=="], + + "@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-I7aOqcVLHBgCeRtMaMHA+ILSS8Sli46fjFq8477stOpQ79TPiBd6e4SDuFCAu58M94mVLMvlPKF2Eh5IvbIMyQ=="], + + "@tiptap/extension-bold": ["@tiptap/extension-bold@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-T4ma6VLoHm9JupglidD3CfZXm89A3HMv99gLplXNizvy1mlr4R3uC3aBqKw6lAP+NoqCqbIgjwc4YYsqZClNwA=="], + + "@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.13.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg=="], + + "@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.14.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.14.0" } }, "sha512-luqPX4u52hiOAHJ95mYsNE+x+9dZxsM461Xny9d/eTXLjAcnwS7MghjrnpljvyYsSXNiwQtxUyEr4uEZZJ5gIQ=="], + + "@tiptap/extension-code": ["@tiptap/extension-code@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-Sx9yLorzS+oqNmXID4jt0G5tDnsEgU0HtEXPLD3KNt/ltVxWJU0AXwCsp1/Dg0HIDL868vWpJ2jC1t/4oaf9kA=="], + + "@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0" } }, "sha512-hRSdIhhm3Q9JBMQdKaifRVFnAa4sG+M7l1QcTKR3VSYVy2/oR0U+aiOifi5OvMRBUwhaR71Ro+cMT9FH9s26Kg=="], + + "@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0", "@tiptap/y-tiptap": "^3.0.0", "yjs": "^13" } }, "sha512-6DgquRiAw/Mf8Y5KqQ+O9muZAmAWU9RaK5tZHrd8+OLkiGBxH891cZ2WA5jNaCl4T9hSJrpcMNJ3lxmBbxLGPg=="], + + "@tiptap/extension-document": ["@tiptap/extension-document@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-O3D7/GPB3XrWGy0y/b4LMHiY0eTd+dyIbSdiFtmUnbC/E9lqQLw43GiqvD9Gm6AyKhBA+Z45dKMbaOe1c6eTwQ=="], + + "@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.14.0", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" }, "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/extension-collaboration": "^3.14.0", "@tiptap/extension-node-range": "^3.14.0", "@tiptap/pm": "^3.14.0", "@tiptap/y-tiptap": "^3.0.0" } }, "sha512-Q2NOSxxqExBbPFOBtEAJskVMdPArceX7VjWS82TSVnrZnzkzFmixki6JxgFdY+xdslsNNjgDrJRL21DBNchhHw=="], + + "@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.13.0", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "^3.13.0", "@tiptap/pm": "^3.13.0", "@tiptap/vue-3": "^3.13.0", "vue": "^3.0.0" } }, "sha512-kj0FpTEFo+KU7HUjrh245QY9HFhTL3y7PCuhNemRHcg9YdkFn07Up6LXthVxXGEFmnQfjR0L4aWFo7xPpUwj7g=="], + + "@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.14.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.14.0" } }, "sha512-IwHyiZKLjV9WSBlQFS+afMjucIML8wFAKkG8UKCu+CVOe/Qd1ImDGyv6rzPlCmefJkDHIUWS+c2STapJlUD1VQ=="], + + "@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.13.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA=="], + + "@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.14.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.14.0" } }, "sha512-hMg2U59+c9FreYtTvzxx5GWKejdZLRITMLEu4OTfrgQok6uF4qkzGEEqmYqPiHk08TBqAg18Y5bbpyqTsuit9A=="], + + "@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-XKxr8usQp+kFevhDK6Ccmnq1CIkLmPClhKwbt7AClGLKLBtEVAS1qUgcmKudkw8cD8Q2/69twI37LXa23sfuLA=="], + + "@tiptap/extension-heading": ["@tiptap/extension-heading@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-4xpahSo3b1dN2nwA0XKXLQVz9nZ/vE443a/Y5QLWeXiu3v9wkcMs/5kQ5ysFeDZRBTfVUWBqhngI7zhvDUx2zQ=="], + + "@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw=="], + + "@tiptap/extension-image": ["@tiptap/extension-image@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw=="], + + "@tiptap/extension-italic": ["@tiptap/extension-italic@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-Arl5EaG4wdyipwvKjsI7Krlk3OkmqvLfF0YfGwsd5AVDxTiYuiDGgz7RF8J2kttbBeiUTqwME5xpkryQK3F+fg=="], + + "@tiptap/extension-link": ["@tiptap/extension-link@3.14.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0" } }, "sha512-xaeJIktD42rJ4t9fbQpKe+yYNZ+YFIK96cp1Kdm0hZHv/8MPMNRiF85TRY+9U1aoyh5uRcspgCj7EKQb2Hs7qg=="], + + "@tiptap/extension-list": ["@tiptap/extension-list@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0" } }, "sha512-rsjFH0Vd/4UbDsjwMLay7oz72VVu1r35t8ofAzy5587jn5JAjflaZs05XbRRMD2imUTK41dyajVSh8CqSnDEJw=="], + + "@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.14.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.14.0" } }, "sha512-19Dcp8HCFdhINmRy0KQLFfz9ZEuVwFWGAAjYG7BvMvkd9k4sJ5vCv5fej59G99rhsc+tCmik77w+SLksOcxwKQ=="], + + "@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.14.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.14.0" } }, "sha512-1oPbvNnQjeOxkHZcUbWPx/IY9o4fT3QGk/9A9cIjFrJRD2AHzbYfPDHNHINtg7Bj0jWz74cHvAHcaxP+M27jkA=="], + + "@tiptap/extension-mention": ["@tiptap/extension-mention@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0", "@tiptap/suggestion": "^3.13.0" } }, "sha512-JcZ9ItaaifurERewyydfj/s52MGcWsCxk5hYdkSohzwa8Ohw4yyghHWCuEl/kvLK+9KhjIDDr1jvAmfZ89I7Fg=="], + + "@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0" } }, "sha512-Um49mpIWLvTc5U84CT5pRUBG9hkcwRj19+c9/9/O4DJ/A3T5RdqGK87jhfNMADiDlZCLAQcMJ//aYNlCj1vIfA=="], + + "@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.14.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.14.0" } }, "sha512-/fXjVL4JajkJQoc213iiput0bCXC4ztUPUpvNuI62VcgFKHcTvX4eYxED1VflotCx0OdkyY9yYD8PtvyO5lkmA=="], + + "@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-NFxk2yNo3Cvh9g8evea+yTLNV48se7MbMcVizTnVhobqtBKv793qsb5FM5Hu30Y72FQPNfH+LRoap4XZyBPfVw=="], + + "@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.13.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.13.0" } }, "sha512-Au4ktRBraQktX9gjSzGWyJV6kPof7+kOhzE8ej+rOMjIrHbx3DCHy1CJWftSO9BbqIyonjsFmm4nE+vjzZ3Z5Q=="], + + "@tiptap/extension-strike": ["@tiptap/extension-strike@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-R8BbAhnWpisBml6okMKl98hY4tJjedTTgyTkx8tPabIJ92nS9IURKEk3foWB9uHxdTOBUqTvVT+2ScDf9r6QHg=="], + + "@tiptap/extension-text": ["@tiptap/extension-text@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-XlpnD87LQ7lLcDcBenHgzxv3uivQzPdVHM16CY4lXR4aKDIp2mxjPZr4twHT+cOnRQHc8VYpRgkEo6LLX6VylA=="], + + "@tiptap/extension-underline": ["@tiptap/extension-underline@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0" } }, "sha512-zmnWlsi2g/tMlThHby0Je9O+v24j4d+qcXF3nuzLUUaDsGCEtOyC9RzwITft59ViK+Nc2PD2W/J14rsB0j+qoQ=="], + + "@tiptap/extensions": ["@tiptap/extensions@3.14.0", "", { "peerDependencies": { "@tiptap/core": "^3.14.0", "@tiptap/pm": "^3.14.0" } }, "sha512-qQBVKqzU4ZVjRn8W0UbdfE4LaaIgcIWHOMrNnJ+PutrRzQ6ZzhmD/kRONvRWBfG9z3DU7pSKGwVYSR2hztsGuQ=="], + + "@tiptap/markdown": ["@tiptap/markdown@3.13.0", "", { "dependencies": { "marked": "^15.0.12" }, "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-BI1GZxDFBrEeYbngbKh/si48tRSXO6HVGg7KzlfOwdncSD982/loG2KUnFIjoVGjmMzXNDWbI6O/eqfLVQPB5Q=="], + + "@tiptap/pm": ["@tiptap/pm@3.13.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ=="], + + "@tiptap/starter-kit": ["@tiptap/starter-kit@3.13.0", "", { "dependencies": { "@tiptap/core": "^3.13.0", "@tiptap/extension-blockquote": "^3.13.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-bullet-list": "^3.13.0", "@tiptap/extension-code": "^3.13.0", "@tiptap/extension-code-block": "^3.13.0", "@tiptap/extension-document": "^3.13.0", "@tiptap/extension-dropcursor": "^3.13.0", "@tiptap/extension-gapcursor": "^3.13.0", "@tiptap/extension-hard-break": "^3.13.0", "@tiptap/extension-heading": "^3.13.0", "@tiptap/extension-horizontal-rule": "^3.13.0", "@tiptap/extension-italic": "^3.13.0", "@tiptap/extension-link": "^3.13.0", "@tiptap/extension-list": "^3.13.0", "@tiptap/extension-list-item": "^3.13.0", "@tiptap/extension-list-keymap": "^3.13.0", "@tiptap/extension-ordered-list": "^3.13.0", "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-strike": "^3.13.0", "@tiptap/extension-text": "^3.13.0", "@tiptap/extension-underline": "^3.13.0", "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg=="], + + "@tiptap/suggestion": ["@tiptap/suggestion@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-IXNvyLITpPiuXHn/q1ntztPYJZMFjPAokKj+OQz3MFNYlzAX3I409KD/EwwCubisRIAFiNX0ZjIIXxxZ3AhFTw=="], + + "@tiptap/vue-3": ["@tiptap/vue-3@3.13.0", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.13.0", "@tiptap/extension-floating-menu": "^3.13.0" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0", "vue": "^3.0.0" } }, "sha512-vl9l2oEARKyUNpViqwSPCL0+dlyIomrPTdHOtDJb6ldo/umWKvjqgLhAtgA7MQ9NwVQa1k5rKICWU6ZH+jLBOw=="], + + "@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.1", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-F3hj5X77ckmyIywbCQpKgyX3xKra2/acJPWaV5R9wqp0cUPBmm62FYbkQ6HaqxH1VhCkUhhAZcDSQjbjj7tnWw=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@unhead/vue": ["@unhead/vue@2.0.19", "", { "dependencies": { "hookable": "^5.5.3", "unhead": "2.0.19" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-7BYjHfOaoZ9+ARJkT10Q2TjnTUqDXmMpfakIAsD/hXiuff1oqWg1xeXT5+MomhNcC15HbiABpbbBmITLSHxdKg=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.53" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w=="], + + "@volar/language-core": ["@volar/language-core@2.4.27", "", { "dependencies": { "@volar/source-map": "2.4.27" } }, "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.27", "", {}, "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg=="], + + "@volar/typescript": ["@volar/typescript@2.4.27", "", { "dependencies": { "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.27", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.27", "", { "dependencies": { "@vue/compiler-core": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.27", "@vue/compiler-dom": "3.5.27", "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw=="], + + "@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], + + "@vue/language-core": ["@vue/language-core@3.2.2", "", { "dependencies": { "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.27", "", { "dependencies": { "@vue/shared": "3.5.27" } }, "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/runtime-core": "3.5.27", "@vue/shared": "3.5.27", "csstype": "^3.2.3" } }, "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.27", "", { "dependencies": { "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "vue": "3.5.27" } }, "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA=="], + + "@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="], + + "@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="], + + "@vueuse/components": ["@vueuse/components@14.1.0", "", { "dependencies": { "@vueuse/core": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-SDRJUAv3H7/PMh+KkYpq0d5KMzpKOfqx4qcV4xyN4mZOLPw8NkiWu+yDcfXwI8h1uCqhRNz2cdeaLa+IuaehFw=="], + + "@vueuse/core": ["@vueuse/core@14.1.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw=="], + + "@vueuse/integrations": ["@vueuse/integrations@14.1.0", "", { "dependencies": { "@vueuse/core": "14.1.0", "@vueuse/shared": "14.1.0" }, "peerDependencies": { "async-validator": "^4", "axios": "^1", "change-case": "^5", "drauu": "^0.4", "focus-trap": "^7", "fuse.js": "^7", "idb-keyval": "^6", "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", "universal-cookie": "^7 || ^8", "vue": "^3.5.0" }, "optionalPeers": ["async-validator", "axios", "change-case", "drauu", "focus-trap", "fuse.js", "idb-keyval", "jwt-decode", "nprogress", "qrcode", "sortablejs", "universal-cookie"] }, "sha512-eNQPdisnO9SvdydTIXnTE7c29yOsJBD/xkwEyQLdhDC/LKbqrFpXHb3uS//7NcIrQO3fWVuvMGp8dbK6mNEMCA=="], + + "@vueuse/metadata": ["@vueuse/metadata@14.1.0", "", {}, "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA=="], + + "@vueuse/shared": ["@vueuse/shared@14.1.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "alien-signals": ["alien-signals@3.1.1", "", {}, "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "arkregex": ["arkregex@0.0.5", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw=="], + + "arktype": ["arktype@2.1.29", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="], + + "c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + + "colortranslator": ["colortranslator@5.0.0", "", {}, "sha512-Z3UPUKasUVDFCDYAjP2fmlVRf1jFHJv1izAmPjiOa0OCIw1W7iC8PZ2GsoDa8uZv+mKyWopxxStT9q05+27h7w=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], + + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], + + "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], + + "discontinuous-range": ["discontinuous-range@1.0.0", "", {}, "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], + + "embla-carousel-auto-height": ["embla-carousel-auto-height@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-/HrJQOEM6aol/oF33gd2QlINcXy3e19fJWvHDuHWp2bpyTa+2dm9tVVJak30m2Qy6QyQ6Fc8DkImtv7pxWOJUQ=="], + + "embla-carousel-auto-scroll": ["embla-carousel-auto-scroll@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-WT9fWhNXFpbQ6kP+aS07oF5IHYLZ1Dx4DkwgCY8Hv2ZyYd2KMCPfMV1q/cA3wFGuLO7GMgKiySLX90/pQkcOdQ=="], + + "embla-carousel-autoplay": ["embla-carousel-autoplay@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA=="], + + "embla-carousel-class-names": ["embla-carousel-class-names@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-l1hm1+7GxQ+zwdU2sea/LhD946on7XO2qk3Xq2XWSwBaWfdgchXdK567yzLtYSHn4sWYdiX+x4nnaj+saKnJkw=="], + + "embla-carousel-fade": ["embla-carousel-fade@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-qaYsx5mwCz72ZrjlsXgs1nKejSrW+UhkbOMwLgfRT7w2LtdEB03nPRI06GHuHv5ac2USvbEiX2/nAHctcDwvpg=="], + + "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], + + "embla-carousel-vue": ["embla-carousel-vue@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "vue": "^3.2.37" } }, "sha512-v8UO5UsyLocZnu/LbfQA7Dn2QHuZKurJY93VUmZYP//QRWoCWOsionmvLLAlibkET3pGPs7++03VhJKbWD7vhQ=="], + + "embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "^2.2.5" }, "peerDependencies": { "embla-carousel": "^8.0.0 || ~8.0.0-rc03" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + + "entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], + + "errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="], + + "esbuild": ["esbuild@0.25.12", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "fontaine": ["fontaine@0.7.0", "", { "dependencies": { "@capsizecss/unpack": "^3.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-vlaWLyoJrOnCBqycmFo/CA8ZmPzuyJHYmgu261KYKByZ4YLz9sTyHZ4qoHgWSYiDsZXhiLo2XndVMz0WOAyZ8Q=="], + + "fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="], + + "fontless": ["fontless@0.1.0", "", { "dependencies": { "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.25.12", "fontaine": "0.7.0", "jiti": "^2.6.1", "lightningcss": "^1.30.2", "magic-string": "^0.30.21", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1", "unifont": "^0.6.0", "unstorage": "^1.17.1" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-KyvRd732HuVd/XP9iEFTb1w8Q01TPSA5GaCJV9HYmPiEs/ZZg/on2YdrQmlKfi9gDGpmN5Bn27Ze/CHqk0vE+w=="], + + "framer-motion": ["framer-motion@12.23.12", "", { "dependencies": { "motion-dom": "^12.23.12", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="], + + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], + + "knitwork": ["knitwork@1.3.0", "", {}, "sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw=="], + + "lib0": ["lib0@0.2.115", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0gentesthtml": "bin/gentesthtml.js", "0serve": "bin/0serve.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "linkifyjs": ["linkifyjs@4.3.2", "", {}, "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA=="], + + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "moo": ["moo@0.5.2", "", {}, "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="], + + "motion-dom": ["motion-dom@12.23.12", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw=="], + + "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="], + + "motion-v": ["motion-v@1.7.4", "", { "dependencies": { "framer-motion": "12.23.12", "hey-listen": "^1.0.8", "motion-dom": "12.23.12" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-YNDUAsany04wfI7YtHxQK3kxzNvh+OdFUk9GpA3+hMt7j6P+5WrVAAgr8kmPPoVza9EsJiAVhqoN3YYFN0Twrw=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "nearley": ["nearley@2.20.1", "", { "dependencies": { "commander": "^2.19.0", "moo": "^0.5.0", "railroad-diagrams": "^1.0.0", "randexp": "0.4.6" }, "bin": { "nearleyc": "bin/nearleyc.js", "nearley-test": "bin/nearley-test.js", "nearley-unparse": "bin/nearley-unparse.js", "nearley-railroad": "bin/nearley-railroad.js" } }, "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "nypm": ["nypm@0.6.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^2.0.0", "tinyexec": "^0.3.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg=="], + + "ofetch": ["ofetch@1.4.1", "", { "dependencies": { "destr": "^2.0.3", "node-fetch-native": "^1.6.4", "ufo": "^1.5.4" } }, "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + + "orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="], + + "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "prosemirror-changeset": ["prosemirror-changeset@2.3.1", "", { "dependencies": { "prosemirror-transform": "^1.0.0" } }, "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ=="], + + "prosemirror-collab": ["prosemirror-collab@1.3.1", "", { "dependencies": { "prosemirror-state": "^1.0.0" } }, "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ=="], + + "prosemirror-commands": ["prosemirror-commands@1.7.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.10.2" } }, "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w=="], + + "prosemirror-dropcursor": ["prosemirror-dropcursor@1.8.2", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw=="], + + "prosemirror-gapcursor": ["prosemirror-gapcursor@1.4.0", "", { "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-view": "^1.0.0" } }, "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ=="], + + "prosemirror-history": ["prosemirror-history@1.5.0", "", { "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.31.0", "rope-sequence": "^1.3.0" } }, "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg=="], + + "prosemirror-inputrules": ["prosemirror-inputrules@1.5.1", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" } }, "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw=="], + + "prosemirror-keymap": ["prosemirror-keymap@1.2.3", "", { "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" } }, "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw=="], + + "prosemirror-markdown": ["prosemirror-markdown@1.13.2", "", { "dependencies": { "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", "prosemirror-model": "^1.25.0" } }, "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g=="], + + "prosemirror-menu": ["prosemirror-menu@1.2.5", "", { "dependencies": { "crelt": "^1.0.0", "prosemirror-commands": "^1.0.0", "prosemirror-history": "^1.0.0", "prosemirror-state": "^1.0.0" } }, "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ=="], + + "prosemirror-model": ["prosemirror-model@1.25.4", "", { "dependencies": { "orderedmap": "^2.0.0" } }, "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA=="], + + "prosemirror-schema-basic": ["prosemirror-schema-basic@1.2.4", "", { "dependencies": { "prosemirror-model": "^1.25.0" } }, "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ=="], + + "prosemirror-schema-list": ["prosemirror-schema-list@1.5.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.7.3" } }, "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q=="], + + "prosemirror-state": ["prosemirror-state@1.4.4", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.27.0" } }, "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw=="], + + "prosemirror-tables": ["prosemirror-tables@1.8.3", "", { "dependencies": { "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-transform": "^1.10.5", "prosemirror-view": "^1.41.4" } }, "sha512-wbqCR/RlRPRe41a4LFtmhKElzBEfBTdtAYWNIGHM6X2e24NN/MTNUKyXjjphfAfdQce37Kh/5yf765mLPYDe7Q=="], + + "prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="], + + "prosemirror-transform": ["prosemirror-transform@1.10.5", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw=="], + + "prosemirror-view": ["prosemirror-view@1.41.4", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + + "railroad-diagrams": ["railroad-diagrams@1.0.0", "", {}, "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A=="], + + "randexp": ["randexp@0.4.6", "", { "dependencies": { "discontinuous-range": "1.0.0", "ret": "~0.1.10" } }, "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ=="], + + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + + "reka-ui": ["reka-ui@2.6.1", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^12.5.0", "@vueuse/shared": "^12.5.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-XK7cJDQoNuGXfCNzBBo/81Yg/OgjPwvbabnlzXG2VsdSgNsT6iIkuPBPr+C0Shs+3bb0x0lbPvgQAhMSCKm5Ww=="], + + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], + + "ret": ["ret@0.1.15", "", {}, "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="], + + "rolldown": ["rolldown@1.0.0-beta.54", "", { "dependencies": { "@oxc-project/types": "=0.102.0", "@rolldown/pluginutils": "1.0.0-beta.54" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.54", "@rolldown/binding-darwin-arm64": "1.0.0-beta.54", "@rolldown/binding-darwin-x64": "1.0.0-beta.54", "@rolldown/binding-freebsd-x64": "1.0.0-beta.54", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.54", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.54", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.54", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.54", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.54", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.54", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.54", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.54", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.54" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-3lIvjCWgjPL3gmiATUdV1NeVBGJZy6FdtwgLPol25tAkn46Q/MsVGfCSNswXwFOxGrxglPaN20IeALSIFuFyEg=="], + + "rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], + + "rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "sql-formatter": ["sql-formatter@15.7.0", "", { "dependencies": { "argparse": "^2.0.1", "nearley": "^2.20.1" }, "bin": { "sql-formatter": "bin/sql-formatter-cli.cjs" } }, "sha512-o2yiy7fYXK1HvzA8P6wwj8QSuwG3e/XcpWht/jIxkQX99c0SVPw0OXdLSV9fHASPiYB09HLA0uq8hokGydi/QA=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], + + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], + + "tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + + "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], + + "temporal-polyfill": ["temporal-polyfill@0.3.0", "", { "dependencies": { "temporal-spec": "0.3.0" } }, "sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g=="], + + "temporal-spec": ["temporal-spec@0.3.0", "", {}, "sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-level-regexp": ["type-level-regexp@0.1.17", "", {}, "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + + "unctx": ["unctx@2.4.1", "", { "dependencies": { "acorn": "^8.14.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.17", "unplugin": "^2.1.0" } }, "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg=="], + + "unhead": ["unhead@2.0.19", "", { "dependencies": { "hookable": "^5.5.3" } }, "sha512-gEEjkV11Aj+rBnY6wnRfsFtF2RxKOLaPN4i+Gx3UhBxnszvV6ApSNZbGk7WKyy/lErQ6ekPN63qdFL7sa1leow=="], + + "unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="], + + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + + "unifont": ["unifont@0.6.0", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA=="], + + "unimport": ["unimport@5.6.0", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" } }, "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A=="], + + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], + + "unplugin-auto-import": ["unplugin-auto-import@20.3.0", "", { "dependencies": { "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "picomatch": "^4.0.3", "unimport": "^5.5.0", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^4.0.0", "@vueuse/core": "*" }, "optionalPeers": ["@nuxt/kit", "@vueuse/core"] }, "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ=="], + + "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], + + "unplugin-vue-components": ["unplugin-vue-components@30.0.0", "", { "dependencies": { "chokidar": "^4.0.3", "debug": "^4.4.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.19", "mlly": "^1.8.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@babel/parser": "^7.15.8", "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "2 || 3" }, "optionalPeers": ["@babel/parser", "@nuxt/kit"] }, "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw=="], + + "unstorage": ["unstorage@1.17.3", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q=="], + + "untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="], + + "vaul-vue": ["vaul-vue@0.4.1", "", { "dependencies": { "@vueuse/core": "^10.8.0", "reka-ui": "^2.0.0", "vue": "^3.4.5" } }, "sha512-A6jOWOZX5yvyo1qMn7IveoWN91mJI5L3BUKsIwkg6qrTGgHs1Sb1JF/vyLJgnbN1rH4OOOxFbtqL9A46bOyGUQ=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], + + "vite": ["vite@8.0.0-beta.2", "", { "dependencies": { "@oxc-project/runtime": "0.102.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.54", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-PIkpGhNy7r5r6Sepwo07BDWf8vr6O4CXVBm+vg7aIpswvL0VNGTjok1qiNRypcqT9dhFQJggtPoubZwXM7yeAQ=="], + + "vite-plugin-singlefile": ["vite-plugin-singlefile@2.3.0", "", { "dependencies": { "micromatch": "^4.0.8" }, "peerDependencies": { "rollup": "^4.44.1", "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" } }, "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A=="], + + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + + "vue": ["vue@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27", "@vue/runtime-dom": "3.5.27", "@vue/server-renderer": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw=="], + + "vue-component-type-helpers": ["vue-component-type-helpers@3.1.8", "", {}, "sha512-oaowlmEM6BaYY+8o+9D9cuzxpWQWHqHTMKakMxXu0E+UCIOMTljyIPO15jcnaCwJtZu/zWDotK7mOIHvWD9mcw=="], + + "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + + "vue-router": ["vue-router@4.6.4", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg=="], + + "vue-tsc": ["vue-tsc@3.2.2", "", { "dependencies": { "@volar/typescript": "2.4.27", "@vue/language-core": "3.2.2" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "wheel-gestures": ["wheel-gestures@2.2.48", "", {}, "sha512-f+Gy33Oa5Z14XY9679Zze+7VFhbsQfBFXodnU2x589l4kxGM9L5Y8zETTmcMR5pWOPQyRv4Z0lNax6xCO0NSlA=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], + + "yjs": ["yjs@13.6.28", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@nuxt/schema/@vue/shared": ["@vue/shared@3.5.25", "", {}, "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg=="], + + "@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.17.4", "", { "dependencies": { "c12": "^3.0.4", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.5", "ignore": "^7.0.4", "jiti": "^2.4.2", "klona": "^2.0.6", "knitwork": "^1.2.0", "mlly": "^1.7.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.1.0", "scule": "^1.3.0", "semver": "^7.7.2", "std-env": "^3.9.0", "tinyglobby": "^0.2.13", "ufo": "^1.6.1", "unctx": "^2.4.1", "unimport": "^5.0.1", "untyped": "^2.0.0" } }, "sha512-l+hY8sy2XFfg3PigZj+PTu6+KIJzmbACTRimn1ew/gtCz+F38f6KTF4sMRTN5CUxiB8TRENgEonASmkAWfpO9Q=="], + + "@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "@nuxtjs/color-mode/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "@nuxtjs/color-mode/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "giget/node-fetch-native": ["node-fetch-native@1.6.6", "", {}, "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ=="], + + "magic-regexp/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "magic-regexp/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "magic-regexp/mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + + "magic-regexp/unplugin": ["unplugin@2.3.4", "", { "dependencies": { "acorn": "^8.14.1", "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" } }, "sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg=="], + + "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "nypm/pkg-types": ["pkg-types@2.1.0", "", { "dependencies": { "confbox": "^0.2.1", "exsolve": "^1.0.1", "pathe": "^2.0.3" } }, "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A=="], + + "nypm/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "ofetch/node-fetch-native": ["node-fetch-native@1.6.6", "", {}, "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ=="], + + "reka-ui/@vueuse/core": ["@vueuse/core@12.8.2", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" } }, "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ=="], + + "reka-ui/@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="], + + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.54", "", {}, "sha512-AHgcZ+w7RIRZ65ihSQL8YuoKcpD9Scew4sEeP1BBUT9QdTo6KjwHrZZXjID6nL10fhKessCH6OPany2QKwAwTQ=="], + + "unctx/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "unctx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "unctx/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "unctx/unplugin": ["unplugin@2.3.4", "", { "dependencies": { "acorn": "^8.14.1", "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" } }, "sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg=="], + + "unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "unstorage/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + + "untyped/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "untyped/knitwork": ["knitwork@1.2.0", "", {}, "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg=="], + + "vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="], + + "vaul-vue/reka-ui": ["reka-ui@2.2.1", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^12.5.0", "@vueuse/shared": "^12.5.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-oLHiyBn6gTIQGnTnv8G5LQuFp9j8HuUNl0qdnW3XPhFb/07hrxzFpjo2kt/jxOZive+n/XWDbOjSj2h9Hih3qA=="], + + "vaul-vue/vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="], + + "@nuxtjs/color-mode/@nuxt/kit/c12": ["c12@3.0.4", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.5.0", "exsolve": "^1.0.5", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.1.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg=="], + + "@nuxtjs/color-mode/@nuxt/kit/exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="], + + "@nuxtjs/color-mode/@nuxt/kit/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="], + + "@nuxtjs/color-mode/@nuxt/kit/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "@nuxtjs/color-mode/@nuxt/kit/knitwork": ["knitwork@1.2.0", "", {}, "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg=="], + + "@nuxtjs/color-mode/@nuxt/kit/mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + + "@nuxtjs/color-mode/@nuxt/kit/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "@nuxtjs/color-mode/@nuxt/kit/pkg-types": ["pkg-types@2.1.0", "", { "dependencies": { "confbox": "^0.2.1", "exsolve": "^1.0.1", "pathe": "^2.0.3" } }, "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A=="], + + "@nuxtjs/color-mode/@nuxt/kit/std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="], + + "@nuxtjs/color-mode/@nuxt/kit/tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport": ["unimport@5.0.1", "", { "dependencies": { "acorn": "^8.14.1", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", "mlly": "^1.7.4", "pathe": "^2.0.3", "picomatch": "^4.0.2", "pkg-types": "^2.1.0", "scule": "^1.3.0", "strip-literal": "^3.0.0", "tinyglobby": "^0.2.13", "unplugin": "^2.3.2", "unplugin-utils": "^0.2.4" } }, "sha512-1YWzPj6wYhtwHE+9LxRlyqP4DiRrhGfJxdtH475im8ktyZXO3jHj/3PZ97zDdvkYoovFdi0K4SKl3a7l92v3sQ=="], + + "@nuxtjs/color-mode/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "@nuxtjs/color-mode/pkg-types/mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + + "@nuxtjs/color-mode/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "magic-regexp/estree-walker/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "magic-regexp/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "magic-regexp/mlly/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "magic-regexp/mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "magic-regexp/unplugin/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "magic-regexp/unplugin/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "mlly/pkg-types/mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], + + "nypm/pkg-types/exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="], + + "reka-ui/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="], + + "reka-ui/@vueuse/core/vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="], + + "reka-ui/@vueuse/shared/vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="], + + "unctx/estree-walker/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "unctx/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "unctx/unplugin/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "unimport/estree-walker/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "vaul-vue/@vueuse/core/@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="], + + "vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="], + + "vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="], + + "vaul-vue/reka-ui/@internationalized/date": ["@internationalized/date@3.8.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA=="], + + "vaul-vue/reka-ui/@internationalized/number": ["@internationalized/number@3.6.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg=="], + + "vaul-vue/reka-ui/@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.9", "", { "dependencies": { "@tanstack/virtual-core": "3.13.9" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-HsvHaOo+o52cVcPhomKDZ3CMpTF/B2qg+BhPHIQJwzn4VIqDyt/rRVqtIomG6jE83IFsE2vlr6cmx7h3dHA0SA=="], + + "vaul-vue/reka-ui/@vueuse/core": ["@vueuse/core@12.8.2", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" } }, "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ=="], + + "vaul-vue/reka-ui/@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="], + + "vaul-vue/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="], + + "vaul-vue/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="], + + "vaul-vue/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="], + + "vaul-vue/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="], + + "vaul-vue/vue/@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="], + + "@nuxtjs/color-mode/@nuxt/kit/c12/dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], + + "@nuxtjs/color-mode/@nuxt/kit/c12/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + + "@nuxtjs/color-mode/@nuxt/kit/mlly/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "@nuxtjs/color-mode/@nuxt/kit/mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "@nuxtjs/color-mode/@nuxt/kit/tinyglobby/fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="], + + "@nuxtjs/color-mode/@nuxt/kit/tinyglobby/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/unplugin": ["unplugin@2.3.4", "", { "dependencies": { "acorn": "^8.14.1", "picomatch": "^4.0.2", "webpack-virtual-modules": "^0.6.2" } }, "sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/unplugin-utils": ["unplugin-utils@0.2.4", "", { "dependencies": { "pathe": "^2.0.2", "picomatch": "^4.0.2" } }, "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA=="], + + "@nuxtjs/color-mode/pkg-types/mlly/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "magic-regexp/mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "mlly/pkg-types/mlly/acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="], + + "reka-ui/@vueuse/core/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="], + + "reka-ui/@vueuse/core/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="], + + "reka-ui/@vueuse/core/vue/@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="], + + "reka-ui/@vueuse/shared/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="], + + "reka-ui/@vueuse/shared/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="], + + "reka-ui/@vueuse/shared/vue/@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="], + + "vaul-vue/reka-ui/@tanstack/vue-virtual/@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.9", "", {}, "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ=="], + + "vaul-vue/reka-ui/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "vaul-vue/vue/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "vaul-vue/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "vaul-vue/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "vaul-vue/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "vaul-vue/vue/@vue/compiler-sfc/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + + "vaul-vue/vue/@vue/runtime-dom/@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="], + + "vaul-vue/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="], + + "vaul-vue/vue/@vue/runtime-dom/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "vaul-vue/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "@nuxtjs/color-mode/@nuxt/kit/mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/estree-walker/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/local-pkg/quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="], + + "@nuxtjs/color-mode/@nuxt/kit/unimport/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + + "reka-ui/@vueuse/core/vue/@vue/runtime-dom/@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="], + + "reka-ui/@vueuse/core/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="], + + "reka-ui/@vueuse/core/vue/@vue/runtime-dom/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "reka-ui/@vueuse/core/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + + "reka-ui/@vueuse/shared/vue/@vue/runtime-dom/@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="], + + "reka-ui/@vueuse/shared/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="], + + "reka-ui/@vueuse/shared/vue/@vue/runtime-dom/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "reka-ui/@vueuse/shared/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "vaul-vue/vue/@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "vaul-vue/vue/@vue/compiler-sfc/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "vaul-vue/vue/@vue/compiler-sfc/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "vaul-vue/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "vaul-vue/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-sfc/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "vaul-vue/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "reka-ui/@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "reka-ui/@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core/@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + } +} diff --git a/packages/router/src/Exceptions/local/dist/main.js b/packages/router/src/Exceptions/local/dist/main.js new file mode 100644 index 0000000000..927961a077 --- /dev/null +++ b/packages/router/src/Exceptions/local/dist/main.js @@ -0,0 +1,176 @@ +var e=Object.defineProperty,t=(e,t)=>()=>(e&&(t=e(e=0)),t),n=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n&&e(r,Symbol.toStringTag,{value:`Module`}),r};function r(e,t={},n){for(let i in e){let a=e[i],o=n?`${n}:${i}`:i;typeof a==`object`&&a?r(a,t,o):typeof a==`function`&&(t[o]=a)}return t}var i={run:e=>e()},a=console.createTask===void 0?()=>i:console.createTask;function o(e,t){let n=a(t.shift());return e.reduce((e,r)=>e.then(()=>n.run(()=>r(...t))),Promise.resolve())}function s(e,t){let n=a(t.shift());return Promise.all(e.map(e=>n.run(()=>e(...t))))}function c(e,t){for(let n of[...e])n(t)}var l=class{constructor(){this._hooks={},this._before=void 0,this._after=void 0,this._deprecatedMessages=void 0,this._deprecatedHooks={},this.hook=this.hook.bind(this),this.callHook=this.callHook.bind(this),this.callHookWith=this.callHookWith.bind(this)}hook(e,t,n={}){if(!e||typeof t!=`function`)return()=>{};let r=e,i;for(;this._deprecatedHooks[e];)i=this._deprecatedHooks[e],e=i.to;if(i&&!n.allowDeprecated){let e=i.message;e||=`${r} hook has been deprecated`+(i.to?`, please use ${i.to}`:``),this._deprecatedMessages||=new Set,this._deprecatedMessages.has(e)||(console.warn(e),this._deprecatedMessages.add(e))}if(!t.name)try{Object.defineProperty(t,`name`,{get:()=>`_`+e.replace(/\W+/g,`_`)+`_hook_cb`,configurable:!0})}catch{}return this._hooks[e]=this._hooks[e]||[],this._hooks[e].push(t),()=>{t&&=(this.removeHook(e,t),void 0)}}hookOnce(e,t){let n,r=(...e)=>(typeof n==`function`&&n(),n=void 0,r=void 0,t(...e));return n=this.hook(e,r),n}removeHook(e,t){if(this._hooks[e]){let n=this._hooks[e].indexOf(t);n!==-1&&this._hooks[e].splice(n,1),this._hooks[e].length===0&&delete this._hooks[e]}}deprecateHook(e,t){this._deprecatedHooks[e]=typeof t==`string`?{to:t}:t;let n=this._hooks[e]||[];delete this._hooks[e];for(let t of n)this.hook(e,t)}deprecateHooks(e){for(let t in Object.assign(this._deprecatedHooks,e),e)this.deprecateHook(t,e[t])}addHooks(e){let t=r(e),n=Object.keys(t).map(e=>this.hook(e,t[e]));return()=>{for(let e of n.splice(0,n.length))e()}}removeHooks(e){let t=r(e);for(let e in t)this.removeHook(e,t[e])}removeAllHooks(){for(let e in this._hooks)delete this._hooks[e]}callHook(e,...t){return t.unshift(e),this.callHookWith(o,e,...t)}callHookParallel(e,...t){return t.unshift(e),this.callHookWith(s,e,...t)}callHookWith(e,t,...n){let r=this._before||this._after?{name:t,args:n,context:{}}:void 0;this._before&&c(this._before,r);let i=e(t in this._hooks?[...this._hooks[t]]:[],n);return i instanceof Promise?i.finally(()=>{this._after&&r&&c(this._after,r)}):(this._after&&r&&c(this._after,r),i)}beforeEach(e){return this._before=this._before||[],this._before.push(e),()=>{if(this._before!==void 0){let t=this._before.indexOf(e);t!==-1&&this._before.splice(t,1)}}}afterEach(e){return this._after=this._after||[],this._after.push(e),()=>{if(this._after!==void 0){let t=this._after.indexOf(e);t!==-1&&this._after.splice(t,1)}}}};function u(){return new l}var d=new Set([`link`,`style`,`script`,`noscript`]),f=new Set([`title`,`titleTemplate`,`script`,`style`,`noscript`]),p=new Set([`base`,`meta`,`link`,`style`,`script`,`noscript`]),m=new Set([`title`,`base`,`htmlAttrs`,`bodyAttrs`,`meta`,`link`,`style`,`script`,`noscript`]),h=new Set([`base`,`title`,`titleTemplate`,`bodyAttrs`,`htmlAttrs`,`templateParams`]),g=new Set([`key`,`tagPosition`,`tagPriority`,`tagDuplicateStrategy`,`innerHTML`,`textContent`,`processTemplateParams`]),_=new Set([`templateParams`,`htmlAttrs`,`bodyAttrs`]),v=new Set([`theme-color`,`google-site-verification`,`og`,`article`,`book`,`profile`,`twitter`,`author`]),y=[`name`,`property`,`http-equiv`],b=new Set([`viewport`,`description`,`keywords`,`robots`]);function x(e){let t=e.split(`:`);return t.length?v.has(t[1]):!1}function ee(e){let{props:t,tag:n}=e;if(h.has(n))return n;if(n===`link`&&t.rel===`canonical`)return`canonical`;if(t.charset)return`charset`;if(e.tag===`meta`){for(let r of y)if(t[r]!==void 0){let i=t[r],a=i.includes(`:`),o=b.has(i);return`${n}:${i}${!(a||o)&&e.key?`:key:${e.key}`:``}`}}if(e.key)return`${n}:key:${e.key}`;if(t.id)return`${n}:id:${t.id}`;if(f.has(n)){let t=e.textContent||e.innerHTML;if(t)return`${n}:content:${t}`}}function S(e){return e._h||e._d||e.textContent||e.innerHTML||`${e.tag}:${Object.entries(e.props).map(([e,t])=>`${e}:${String(t)}`).join(`,`)}`}function C(e,t,n){typeof e==`function`&&(!n||n!==`titleTemplate`&&!(n[0]===`o`&&n[1]===`n`))&&(e=e());let r;if(t&&(r=t(n,e)),Array.isArray(r))return r.map(e=>C(e,t));if(r?.constructor===Object){let e={};for(let n of Object.keys(r))e[n]=C(r[n],t,n);return e}return r}function w(e,t){let n=e===`style`?new Map:new Set;function r(t){let r=t.trim();if(r)if(e===`style`){let[e,...t]=r.split(`:`).map(e=>e.trim());e&&t.length&&n.set(e,t.join(`:`))}else r.split(` `).filter(Boolean).forEach(e=>n.add(e))}return typeof t==`string`?e===`style`?t.split(`;`).forEach(r):r(t):Array.isArray(t)?t.forEach(e=>r(e)):t&&typeof t==`object`&&Object.entries(t).forEach(([t,i])=>{i&&i!==`false`&&(e===`style`?n.set(t.trim(),i):r(t))}),n}function te(e,t){return e.props=e.props||{},t?e.tag===`templateParams`?(e.props=t,e):(Object.entries(t).forEach(([n,r])=>{if(r===null){e.props[n]=null;return}if(n===`class`||n===`style`){e.props[n]=w(n,r);return}if(g.has(n)){if([`textContent`,`innerHTML`].includes(n)&&typeof r==`object`){let i=t.type;if(t.type||(i=`application/json`),!i?.endsWith(`json`)&&i!==`speculationrules`)return;t.type=i,e.props.type=i,e[n]=JSON.stringify(r)}else e[n]=r;return}let i=String(r),a=n.startsWith(`data-`);i===`true`||i===``?e.props[n]=a?i:!0:!r&&a&&i===`false`?e.props[n]=`false`:r!==void 0&&(e.props[n]=r)}),e):e}function ne(e,t){let n=te({tag:e,props:{}},typeof t==`object`&&typeof t!=`function`?t:{[e===`script`||e===`noscript`||e===`style`?`innerHTML`:`textContent`]:t});return n.key&&d.has(n.tag)&&(n.props[`data-hid`]=n._h=n.key),n.tag===`script`&&typeof n.innerHTML==`object`&&(n.innerHTML=JSON.stringify(n.innerHTML),n.props.type=n.props.type||`application/json`),Array.isArray(n.props.content)?n.props.content.map(e=>({...n,props:{...n.props,content:e}})):n}function re(e,t){if(!e)return[];typeof e==`function`&&(e=e());let n=(e,n)=>{for(let r=0;r{if(t!==void 0)for(let n of Array.isArray(t)?t:[t])r.push(ne(e,n))}),r.flat()}var T=(e,t)=>e._w===t._w?e._p-t._p:e._w-t._w,ie={base:-10,title:10},ae={critical:-8,high:-1,low:2},oe={meta:{"content-security-policy":-30,charset:-20,viewport:-15},link:{preconnect:20,stylesheet:60,preload:70,modulepreload:70,prefetch:90,"dns-prefetch":90,prerender:90},script:{async:30,defer:80,sync:50},style:{imported:40,sync:60}},se=/@import/,E=e=>e===``||e===!0;function ce(e,t){if(typeof t.tagPriority==`number`)return t.tagPriority;let n=100,r=ae[t.tagPriority]||0,i=e.resolvedOptions.disableCapoSorting?{link:{},script:{},style:{}}:oe;if(t.tag in ie)n=ie[t.tag];else if(t.tag===`meta`){let e=t.props[`http-equiv`]===`content-security-policy`?`content-security-policy`:t.props.charset?`charset`:t.props.name===`viewport`?`viewport`:null;e&&(n=oe.meta[e])}else t.tag===`link`&&t.props.rel?n=i.link[t.props.rel]:t.tag===`script`?E(t.props.async)?n=i.script.async:t.props.src&&!E(t.props.defer)&&!E(t.props.async)&&t.props.type!==`module`&&!t.props.type?.endsWith(`json`)?n=i.script.sync:E(t.props.defer)&&t.props.src&&!E(t.props.async)&&(n=i.script.defer):t.tag===`style`&&(n=t.innerHTML&&se.test(t.innerHTML)?i.style.imported:i.style.sync);return(n||100)+r}function le(e,t){let n=typeof t==`function`?t(e):t,r=n.key||String(e.plugins.size+1);e.plugins.get(r)||(e.plugins.set(r,n),e.hooks.addHooks(n.hooks||{}))}function ue(e={}){let t=u();t.addHooks(e.hooks||{});let n=!e.document,r=new Map,i=new Map,a=new Set,o={_entryCount:1,plugins:i,dirty:!1,resolvedOptions:e,hooks:t,ssr:n,entries:r,headEntries(){return[...r.values()]},use:e=>le(o,e),push(e,i){let s={...i||{}};delete s.head;let c=s._index??o._entryCount++,l={_i:c,input:e,options:s},u={_poll(e=!1){o.dirty=!0,!e&&a.add(c),t.callHook(`entries:updated`,o)},dispose(){r.delete(c)&&o.invalidate()},patch(e){(!s.mode||s.mode===`server`&&n||s.mode===`client`&&!n)&&(l.input=e,r.set(c,l),u._poll())}};return u.patch(e),u},async resolveTags(){let n={tagMap:new Map,tags:[],entries:[...o.entries.values()]};for(await t.callHook(`entries:resolve`,n);a.size;){let n=a.values().next().value;a.delete(n);let i=r.get(n);if(i){let n={tags:re(i.input,e.propResolvers||[]).map(e=>Object.assign(e,i.options)),entry:i};await t.callHook(`entries:normalize`,n),i._tags=n.tags.map((e,t)=>(e._w=ce(o,e),e._p=(i._i<<10)+t,e._d=ee(e),e))}}let i=!1;n.entries.flatMap(e=>(e._tags||[]).map(e=>({...e,props:{...e.props}}))).sort(T).reduce((e,t)=>{let n=String(t._d||t._p);if(!e.has(n))return e.set(n,t);let r=e.get(n);if((t?.tagDuplicateStrategy||(_.has(t.tag)?`merge`:null)||(t.key&&t.key===r.key?`merge`:null))===`merge`){let i={...r.props};Object.entries(t.props).forEach(([e,t])=>i[e]=e===`style`?new Map([...r.props.style||new Map,...t]):e===`class`?new Set([...r.props.class||new Set,...t]):t),e.set(n,{...t,props:i})}else t._p>>10==r._p>>10&&t.tag===`meta`&&x(n)?(e.set(n,Object.assign([...Array.isArray(r)?r:[r],t],t)),i=!0):(t._w===r._w?t._p>r._p:t?._wle(o,e)),o.hooks.callHook(`init`,o),e.init?.forEach(e=>e&&o.push(e)),o}async function D(e,t={}){let n=t.document||e.resolvedOptions.document;if(!n||!e.dirty)return;let r={shouldRender:!0,tags:[]};if(await e.hooks.callHook(`dom:beforeRender`,r),r.shouldRender)return e._domUpdatePromise||=new Promise(async t=>{let r=new Map,i=new Promise(t=>{e.resolveTags().then(e=>{t(e.map(e=>{let t=r.get(e._d)||0,n={tag:e,id:(t?`${e._d}:${t}`:e._d)||S(e),shouldRender:!0};return e._d&&x(e._d)&&r.set(e._d,t+1),n}))})}),a=e._dom;if(!a){a={title:n.title,elMap:new Map().set(`htmlAttrs`,n.documentElement).set(`bodyAttrs`,n.body)};for(let e of[`body`,`head`]){let t=n[e]?.children;for(let e of t){let t=e.tagName.toLowerCase();if(!p.has(t))continue;let n=te({tag:t,props:{}},{innerHTML:e.innerHTML,...e.getAttributeNames().reduce((t,n)=>(t[n]=e.getAttribute(n),t),{})||{}});if(n.key=e.getAttribute(`data-hid`)||void 0,n._d=ee(n)||S(n),a.elMap.has(n._d)){let t=1,r=n._d;for(;a.elMap.has(r);)r=`${n._d}:${t++}`;a.elMap.set(r,e)}else a.elMap.set(n._d,e)}}}a.pendingSideEffects={...a.sideEffects},a.sideEffects={};function o(e,t,n){let r=`${e}:${t}`;a.sideEffects[r]=n,delete a.pendingSideEffects[r]}function s({id:e,$el:t,tag:r}){let i=r.tag.endsWith(`Attrs`);for(let s in a.elMap.set(e,t),i||(r.textContent&&r.textContent!==t.textContent&&(t.textContent=r.textContent),r.innerHTML&&r.innerHTML!==t.innerHTML&&(t.innerHTML=r.innerHTML),o(e,`el`,()=>{t?.remove(),a.elMap.delete(e)})),r.props){if(!Object.prototype.hasOwnProperty.call(r.props,s))continue;let a=r.props[s];if(s.startsWith(`on`)&&typeof a==`function`){let e=t?.dataset;if(e&&e[`${s}fired`]){let e=s.slice(0,-5);a.call(t,new Event(e.substring(2)))}t.getAttribute(`data-${s}`)!==``&&((r.tag===`bodyAttrs`?n.defaultView:t).addEventListener(s.substring(2),a.bind(t)),t.setAttribute(`data-${s}`,``));continue}let c=`attr:${s}`;if(s===`class`){if(!a)continue;for(let n of a)i&&o(e,`${c}:${n}`,()=>t.classList.remove(n)),!t.classList.contains(n)&&t.classList.add(n)}else if(s===`style`){if(!a)continue;for(let[n,r]of a)o(e,`${c}:${n}`,()=>{t.style.removeProperty(n)}),t.style.setProperty(n,r)}else a!==!1&&a!==null&&(t.getAttribute(s)!==a&&t.setAttribute(s,a===!0?``:String(a)),i&&o(e,c,()=>t.removeAttribute(s)))}}let c=[],l={bodyClose:void 0,bodyOpen:void 0,head:void 0},u=await i;for(let e of u){let{tag:t,shouldRender:r,id:i}=e;if(r){if(t.tag===`title`){n.title=t.textContent,o(`title`,``,()=>n.title=a.title);continue}e.$el=e.$el||a.elMap.get(i),e.$el?s(e):p.has(t.tag)&&c.push(e)}}for(let e of c){let t=e.tag.tagPosition||`head`;e.$el=n.createElement(e.tag.tag),s(e),l[t]=l[t]||n.createDocumentFragment(),l[t].appendChild(e.$el)}for(let t of u)await e.hooks.callHook(`dom:renderTag`,t,n,o);for(let e in l.head&&n.head.appendChild(l.head),l.bodyOpen&&n.body.insertBefore(l.bodyOpen,n.body.firstChild),l.bodyClose&&n.body.appendChild(l.bodyClose),a.pendingSideEffects)a.pendingSideEffects[e]();e._dom=a,await e.hooks.callHook(`dom:rendered`,{renders:u}),t()}).finally(()=>{e._domUpdatePromise=void 0,e.dirty=!1}),e._domUpdatePromise}function de(e={}){let t=e.domOptions?.render||D;e.document=e.document||(typeof window<`u`?document:void 0);let n=e.document?.head.querySelector(`script[id="unhead:payload"]`)?.innerHTML||!1;return ue({...e,plugins:[...e.plugins||[],{key:`client`,hooks:{"entries:updated":t}}],init:[n?JSON.parse(n):!1,...e.init||[]]})}function fe(e,t){let n=0;return()=>{let r=++n;t(()=>{n===r&&e()})}}function pe(e){let t=Object.create(null);for(let n of e.split(`,`))t[n]=1;return e=>e in t}var me={},he=[],ge=()=>{},_e=()=>!1,ve=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),ye=e=>e.startsWith(`onUpdate:`),be=Object.assign,xe=(e,t)=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)},Se=Object.prototype.hasOwnProperty,Ce=(e,t)=>Se.call(e,t),O=Array.isArray,we=e=>Ne(e)===`[object Map]`,Te=e=>Ne(e)===`[object Set]`,Ee=e=>Ne(e)===`[object Date]`,De=e=>Ne(e)===`[object RegExp]`,k=e=>typeof e==`function`,Oe=e=>typeof e==`string`,ke=e=>typeof e==`symbol`,Ae=e=>typeof e==`object`&&!!e,je=e=>(Ae(e)||k(e))&&k(e.then)&&k(e.catch),Me=Object.prototype.toString,Ne=e=>Me.call(e),Pe=e=>Ne(e).slice(8,-1),Fe=e=>Ne(e)===`[object Object]`,Ie=e=>Oe(e)&&e!==`NaN`&&e[0]!==`-`&&``+parseInt(e,10)===e,Le=pe(`,key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted`),Re=e=>{let t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},ze=/-\w/g,Be=Re(e=>e.replace(ze,e=>e.slice(1).toUpperCase())),Ve=/\B([A-Z])/g,He=Re(e=>e.replace(Ve,`-$1`).toLowerCase()),Ue=Re(e=>e.charAt(0).toUpperCase()+e.slice(1)),We=Re(e=>e?`on${Ue(e)}`:``),Ge=(e,t)=>!Object.is(e,t),Ke=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},Je=e=>{let t=parseFloat(e);return isNaN(t)?e:t},Ye=e=>{let t=Oe(e)?Number(e):NaN;return isNaN(t)?e:t},Xe,Ze=()=>Xe||=typeof globalThis<`u`?globalThis:typeof self<`u`?self:typeof window<`u`?window:typeof global<`u`?global:{},Qe=pe(`Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol`);function $e(e){if(O(e)){let t={};for(let n=0;n{if(e){let n=e.split(tt);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function A(e){let t=``;if(Oe(e))t=e;else if(O(e))for(let n=0;nlt(e,t))}var dt=e=>!!(e&&e.__v_isRef===!0),ft=e=>Oe(e)?e:e==null?``:O(e)||Ae(e)&&(e.toString===Me||!k(e.toString))?dt(e)?ft(e.value):JSON.stringify(e,pt,2):String(e),pt=(e,t)=>dt(t)?pt(e,t.value):we(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((e,[t,n],r)=>(e[mt(t,r)+` =>`]=n,e),{})}:Te(t)?{[`Set(${t.size})`]:[...t.values()].map(e=>mt(e))}:ke(t)?mt(t):Ae(t)&&!O(t)&&!Fe(t)?String(t):t,mt=(e,t=``)=>ke(e)?`Symbol(${e.description??t})`:e;function ht(e){return e==null?`initial`:typeof e==`string`?e===``?` `:e:String(e)}var gt,_t=class{constructor(e=!1){this.detached=e,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=gt,!e&>&&(this.index=(gt.scopes||=[]).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let e,t;if(this.scopes)for(e=0,t=this.scopes.length;e0&&--this._on===0&&(gt=this.prevScope,this.prevScope=void 0)}stop(e){if(this._active){this._active=!1;let t,n;for(t=0,n=this.effects.length;t0)return;if(Et){let e=Et;for(Et=void 0;e;){let t=e.next;e.next=void 0,e.flags&=-9,e=t}}let e;for(;Tt;){let t=Tt;for(Tt=void 0;t;){let n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(t){e||=t}t=n}}if(e)throw e}function At(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function jt(e){let t,n=e.depsTail,r=n;for(;r;){let e=r.prevDep;r.version===-1?(r===n&&(n=e),Pt(r),Ft(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=e}e.deps=t,e.depsTail=n}function Mt(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Nt(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Nt(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Ut)||(e.globalVersion=Ut,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Mt(e))))return;e.flags|=2;let t=e.dep,n=xt,r=Rt;xt=e,Rt=!0;try{At(e);let n=e.fn(e._value);(t.version===0||Ge(n,e._value))&&(e.flags|=128,e._value=n,t.version++)}catch(e){throw t.version++,e}finally{xt=n,Rt=r,jt(e),e.flags&=-3}}function Pt(e,t=!1){let{dep:n,prevSub:r,nextSub:i}=e;if(r&&(r.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let e=n.computed.deps;e;e=e.nextDep)Pt(e,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function Ft(e){let{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}function It(e,t){e.effect instanceof Ct&&(e=e.effect.fn);let n=new Ct(e);t&&be(n,t);try{n.run()}catch(e){throw n.stop(),e}let r=n.run.bind(n);return r.effect=n,r}function Lt(e){e.effect.stop()}var Rt=!0,zt=[];function Bt(){zt.push(Rt),Rt=!1}function Vt(){let e=zt.pop();Rt=e===void 0?!0:e}function Ht(e){let{cleanup:t}=e;if(e.cleanup=void 0,t){let e=xt;xt=void 0;try{t()}finally{xt=e}}}var Ut=0,Wt=class{constructor(e,t){this.sub=e,this.dep=t,this.version=t.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}},Gt=class{constructor(e){this.computed=e,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(e){if(!xt||!Rt||xt===this.computed)return;let t=this.activeLink;if(t===void 0||t.sub!==xt)t=this.activeLink=new Wt(xt,this),xt.deps?(t.prevDep=xt.depsTail,xt.depsTail.nextDep=t,xt.depsTail=t):xt.deps=xt.depsTail=t,Kt(t);else if(t.version===-1&&(t.version=this.version,t.nextDep)){let e=t.nextDep;e.prevDep=t.prevDep,t.prevDep&&(t.prevDep.nextDep=e),t.prevDep=xt.depsTail,t.nextDep=void 0,xt.depsTail.nextDep=t,xt.depsTail=t,xt.deps===t&&(xt.deps=e)}return t}trigger(e){this.version++,Ut++,this.notify(e)}notify(e){Ot();try{for(let e=this.subs;e;e=e.prevSub)e.sub.notify()&&e.sub.dep.notify()}finally{kt()}}};function Kt(e){if(e.dep.sc++,e.sub.flags&4){let t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let e=t.deps;e;e=e.nextDep)Kt(e)}let n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}var qt=new WeakMap,Jt=Symbol(``),Yt=Symbol(``),Xt=Symbol(``);function Zt(e,t,n){if(Rt&&xt){let t=qt.get(e);t||qt.set(e,t=new Map);let r=t.get(n);r||(t.set(n,r=new Gt),r.map=t,r.key=n),r.track()}}function Qt(e,t,n,r,i,a){let o=qt.get(e);if(!o){Ut++;return}let s=e=>{e&&e.trigger()};if(Ot(),t===`clear`)o.forEach(s);else{let i=O(e),a=i&&Ie(n);if(i&&n===`length`){let e=Number(r);o.forEach((t,n)=>{(n===`length`||n===Xt||!ke(n)&&n>=e)&&s(t)})}else switch((n!==void 0||o.has(void 0))&&s(o.get(n)),a&&s(o.get(Xt)),t){case`add`:i?a&&s(o.get(`length`)):(s(o.get(Jt)),we(e)&&s(o.get(Yt)));break;case`delete`:i||(s(o.get(Jt)),we(e)&&s(o.get(Yt)));break;case`set`:we(e)&&s(o.get(Jt));break}}kt()}function $t(e,t){let n=qt.get(e);return n&&n.get(t)}function en(e){let t=Kn(e);return t===e?t:(Zt(t,`iterate`,Xt),Wn(e)?t:t.map(Jn))}function tn(e){return Zt(e=Kn(e),`iterate`,Xt),e}function nn(e,t){return Un(e)?Yn(Hn(e)?Jn(t):t):Jn(t)}var rn={__proto__:null,[Symbol.iterator](){return an(this,Symbol.iterator,e=>nn(this,e))},concat(...e){return en(this).concat(...e.map(e=>O(e)?en(e):e))},entries(){return an(this,`entries`,e=>(e[1]=nn(this,e[1]),e))},every(e,t){return sn(this,`every`,e,t,void 0,arguments)},filter(e,t){return sn(this,`filter`,e,t,e=>e.map(e=>nn(this,e)),arguments)},find(e,t){return sn(this,`find`,e,t,e=>nn(this,e),arguments)},findIndex(e,t){return sn(this,`findIndex`,e,t,void 0,arguments)},findLast(e,t){return sn(this,`findLast`,e,t,e=>nn(this,e),arguments)},findLastIndex(e,t){return sn(this,`findLastIndex`,e,t,void 0,arguments)},forEach(e,t){return sn(this,`forEach`,e,t,void 0,arguments)},includes(...e){return ln(this,`includes`,e)},indexOf(...e){return ln(this,`indexOf`,e)},join(e){return en(this).join(e)},lastIndexOf(...e){return ln(this,`lastIndexOf`,e)},map(e,t){return sn(this,`map`,e,t,void 0,arguments)},pop(){return un(this,`pop`)},push(...e){return un(this,`push`,e)},reduce(e,...t){return cn(this,`reduce`,e,t)},reduceRight(e,...t){return cn(this,`reduceRight`,e,t)},shift(){return un(this,`shift`)},some(e,t){return sn(this,`some`,e,t,void 0,arguments)},splice(...e){return un(this,`splice`,e)},toReversed(){return en(this).toReversed()},toSorted(e){return en(this).toSorted(e)},toSpliced(...e){return en(this).toSpliced(...e)},unshift(...e){return un(this,`unshift`,e)},values(){return an(this,`values`,e=>nn(this,e))}};function an(e,t,n){let r=tn(e),i=r[t]();return r!==e&&!Wn(e)&&(i._next=i.next,i.next=()=>{let e=i._next();return e.done||(e.value=n(e.value)),e}),i}var on=Array.prototype;function sn(e,t,n,r,i,a){let o=tn(e),s=o!==e&&!Wn(e),c=o[t];if(c!==on[t]){let t=c.apply(e,a);return s?Jn(t):t}let l=n;o!==e&&(s?l=function(t,r){return n.call(this,nn(e,t),r,e)}:n.length>2&&(l=function(t,r){return n.call(this,t,r,e)}));let u=c.call(o,l,r);return s&&i?i(u):u}function cn(e,t,n,r){let i=tn(e),a=n;return i!==e&&(Wn(e)?n.length>3&&(a=function(t,r,i){return n.call(this,t,r,i,e)}):a=function(t,r,i){return n.call(this,t,nn(e,r),i,e)}),i[t](a,...r)}function ln(e,t,n){let r=Kn(e);Zt(r,`iterate`,Xt);let i=r[t](...n);return(i===-1||i===!1)&&Gn(n[0])?(n[0]=Kn(n[0]),r[t](...n)):i}function un(e,t,n=[]){Bt(),Ot();let r=Kn(e)[t].apply(e,n);return kt(),Vt(),r}var dn=pe(`__proto__,__v_isRef,__isVue`),fn=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!==`arguments`&&e!==`caller`).map(e=>Symbol[e]).filter(ke));function pn(e){ke(e)||(e=String(e));let t=Kn(this);return Zt(t,`has`,e),t.hasOwnProperty(e)}var mn=class{constructor(e=!1,t=!1){this._isReadonly=e,this._isShallow=t}get(e,t,n){if(t===`__v_skip`)return e.__v_skip;let r=this._isReadonly,i=this._isShallow;if(t===`__v_isReactive`)return!r;if(t===`__v_isReadonly`)return r;if(t===`__v_isShallow`)return i;if(t===`__v_raw`)return n===(r?i?Pn:Nn:i?Mn:jn).get(e)||Object.getPrototypeOf(e)===Object.getPrototypeOf(n)?e:void 0;let a=O(e);if(!r){let e;if(a&&(e=rn[t]))return e;if(t===`hasOwnProperty`)return pn}let o=Reflect.get(e,t,Xn(e)?e:n);if((ke(t)?fn.has(t):dn(t))||(r||Zt(e,`get`,t),i))return o;if(Xn(o)){let e=a&&Ie(t)?o:o.value;return r&&Ae(e)?zn(e):e}return Ae(o)?r?zn(o):Ln(o):o}},hn=class extends mn{constructor(e=!1){super(!1,e)}set(e,t,n,r){let i=e[t],a=O(e)&&Ie(t);if(!this._isShallow){let e=Un(i);if(!Wn(n)&&!Un(n)&&(i=Kn(i),n=Kn(n)),!a&&Xn(i)&&!Xn(n))return e||(i.value=n),!0}let o=a?Number(t)e,Sn=e=>Reflect.getPrototypeOf(e);function Cn(e,t,n){return function(...r){let i=this.__v_raw,a=Kn(i),o=we(a),s=e===`entries`||e===Symbol.iterator&&o,c=e===`keys`&&o,l=i[e](...r),u=n?xn:t?Yn:Jn;return!t&&Zt(a,`iterate`,c?Yt:Jt),be(Object.create(l),{next(){let{value:e,done:t}=l.next();return t?{value:e,done:t}:{value:s?[u(e[0]),u(e[1])]:u(e),done:t}}})}}function wn(e){return function(...t){return e===`delete`?!1:e===`clear`?void 0:this}}function Tn(e,t){let n={get(n){let r=this.__v_raw,i=Kn(r),a=Kn(n);e||(Ge(n,a)&&Zt(i,`get`,n),Zt(i,`get`,a));let{has:o}=Sn(i),s=t?xn:e?Yn:Jn;if(o.call(i,n))return s(r.get(n));if(o.call(i,a))return s(r.get(a));r!==i&&r.get(n)},get size(){let t=this.__v_raw;return!e&&Zt(Kn(t),`iterate`,Jt),t.size},has(t){let n=this.__v_raw,r=Kn(n),i=Kn(t);return e||(Ge(t,i)&&Zt(r,`has`,t),Zt(r,`has`,i)),t===i?n.has(t):n.has(t)||n.has(i)},forEach(n,r){let i=this,a=i.__v_raw,o=Kn(a),s=t?xn:e?Yn:Jn;return!e&&Zt(o,`iterate`,Jt),a.forEach((e,t)=>n.call(r,s(e),s(t),i))}};return be(n,e?{add:wn(`add`),set:wn(`set`),delete:wn(`delete`),clear:wn(`clear`)}:{add(e){!t&&!Wn(e)&&!Un(e)&&(e=Kn(e));let n=Kn(this);return Sn(n).has.call(n,e)||(n.add(e),Qt(n,`add`,e,e)),this},set(e,n){!t&&!Wn(n)&&!Un(n)&&(n=Kn(n));let r=Kn(this),{has:i,get:a}=Sn(r),o=i.call(r,e);o||=(e=Kn(e),i.call(r,e));let s=a.call(r,e);return r.set(e,n),o?Ge(n,s)&&Qt(r,`set`,e,n,s):Qt(r,`add`,e,n),this},delete(e){let t=Kn(this),{has:n,get:r}=Sn(t),i=n.call(t,e);i||=(e=Kn(e),n.call(t,e));let a=r?r.call(t,e):void 0,o=t.delete(e);return i&&Qt(t,`delete`,e,void 0,a),o},clear(){let e=Kn(this),t=e.size!==0,n=e.clear();return t&&Qt(e,`clear`,void 0,void 0,void 0),n}}),[`keys`,`values`,`entries`,Symbol.iterator].forEach(r=>{n[r]=Cn(r,e,t)}),n}function En(e,t){let n=Tn(e,t);return(t,r,i)=>r===`__v_isReactive`?!e:r===`__v_isReadonly`?e:r===`__v_raw`?t:Reflect.get(Ce(n,r)&&r in t?n:t,r,i)}var Dn={get:En(!1,!1)},On={get:En(!1,!0)},kn={get:En(!0,!1)},An={get:En(!0,!0)},jn=new WeakMap,Mn=new WeakMap,Nn=new WeakMap,Pn=new WeakMap;function Fn(e){switch(e){case`Object`:case`Array`:return 1;case`Map`:case`Set`:case`WeakMap`:case`WeakSet`:return 2;default:return 0}}function In(e){return e.__v_skip||!Object.isExtensible(e)?0:Fn(Pe(e))}function Ln(e){return Un(e)?e:Vn(e,!1,_n,Dn,jn)}function Rn(e){return Vn(e,!1,yn,On,Mn)}function zn(e){return Vn(e,!0,vn,kn,Nn)}function Bn(e){return Vn(e,!0,bn,An,Pn)}function Vn(e,t,n,r,i){if(!Ae(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;let a=In(e);if(a===0)return e;let o=i.get(e);if(o)return o;let s=new Proxy(e,a===2?r:n);return i.set(e,s),s}function Hn(e){return Un(e)?Hn(e.__v_raw):!!(e&&e.__v_isReactive)}function Un(e){return!!(e&&e.__v_isReadonly)}function Wn(e){return!!(e&&e.__v_isShallow)}function Gn(e){return e?!!e.__v_raw:!1}function Kn(e){let t=e&&e.__v_raw;return t?Kn(t):e}function qn(e){return!Ce(e,`__v_skip`)&&Object.isExtensible(e)&&qe(e,`__v_skip`,!0),e}var Jn=e=>Ae(e)?Ln(e):e,Yn=e=>Ae(e)?zn(e):e;function Xn(e){return e?e.__v_isRef===!0:!1}function j(e){return Qn(e,!1)}function Zn(e){return Qn(e,!0)}function Qn(e,t){return Xn(e)?e:new $n(e,t)}var $n=class{constructor(e,t){this.dep=new Gt,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=t?e:Kn(e),this._value=t?e:Jn(e),this.__v_isShallow=t}get value(){return this.dep.track(),this._value}set value(e){let t=this._rawValue,n=this.__v_isShallow||Wn(e)||Un(e);e=n?e:Kn(e),Ge(e,t)&&(this._rawValue=e,this._value=n?e:Jn(e),this.dep.trigger())}};function er(e){e.dep&&e.dep.trigger()}function M(e){return Xn(e)?e.value:e}function tr(e){return k(e)?e():M(e)}var nr={get:(e,t,n)=>t===`__v_raw`?e:M(Reflect.get(e,t,n)),set:(e,t,n,r)=>{let i=e[t];return Xn(i)&&!Xn(n)?(i.value=n,!0):Reflect.set(e,t,n,r)}};function rr(e){return Hn(e)?e:new Proxy(e,nr)}var ir=class{constructor(e){this.__v_isRef=!0,this._value=void 0;let t=this.dep=new Gt,{get:n,set:r}=e(t.track.bind(t),t.trigger.bind(t));this._get=n,this._set=r}get value(){return this._value=this._get()}set value(e){this._set(e)}};function ar(e){return new ir(e)}function or(e){let t=O(e)?Array(e.length):{};for(let n in e)t[n]=ur(e,n);return t}var sr=class{constructor(e,t,n){this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0,this._value=void 0,this._raw=Kn(e);let r=!0,i=e;if(!O(e)||!Ie(String(t)))do r=!Gn(i)||Wn(i);while(r&&(i=i.__v_raw));this._shallow=r}get value(){let e=this._object[this._key];return this._shallow&&(e=M(e)),this._value=e===void 0?this._defaultValue:e}set value(e){if(this._shallow&&Xn(this._raw[this._key])){let t=this._object[this._key];if(Xn(t)){t.value=e;return}}this._object[this._key]=e}get dep(){return $t(this._raw,this._key)}},cr=class{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}};function lr(e,t,n){return Xn(e)?e:k(e)?new cr(e):Ae(e)&&arguments.length>1?ur(e,t,n):j(e)}function ur(e,t,n){return new sr(e,t,n)}var dr=class{constructor(e,t,n){this.fn=e,this.setter=t,this._value=void 0,this.dep=new Gt(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Ut-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!t,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&xt!==this)return Dt(this,!0),!0}get value(){let e=this.dep.track();return Nt(this),e&&(e.version=this.dep.version),this._value}set value(e){this.setter&&this.setter(e)}};function fr(e,t,n=!1){let r,i;return k(e)?r=e:(r=e.get,i=e.set),new dr(r,i,n)}var pr={GET:`get`,HAS:`has`,ITERATE:`iterate`},mr={SET:`set`,ADD:`add`,DELETE:`delete`,CLEAR:`clear`},hr={},gr=new WeakMap,_r=void 0;function vr(){return _r}function yr(e,t=!1,n=_r){if(n){let t=gr.get(n);t||gr.set(n,t=[]),t.push(e)}}function br(e,t,n=me){let{immediate:r,deep:i,once:a,scheduler:o,augmentJob:s,call:c}=n,l=e=>i?e:Wn(e)||i===!1||i===0?xr(e,1):xr(e),u,d,f,p,m=!1,h=!1;if(Xn(e)?(d=()=>e.value,m=Wn(e)):Hn(e)?(d=()=>l(e),m=!0):O(e)?(h=!0,m=e.some(e=>Hn(e)||Wn(e)),d=()=>e.map(e=>{if(Xn(e))return e.value;if(Hn(e))return l(e);if(k(e))return c?c(e,2):e()})):d=k(e)?t?c?()=>c(e,2):e:()=>{if(f){Bt();try{f()}finally{Vt()}}let t=_r;_r=u;try{return c?c(e,3,[p]):e(p)}finally{_r=t}}:ge,t&&i){let e=d,t=i===!0?1/0:i;d=()=>xr(e(),t)}let g=yt(),_=()=>{u.stop(),g&&g.active&&xe(g.effects,u)};if(a&&t){let e=t;t=(...t)=>{e(...t),_()}}let v=h?Array(e.length).fill(hr):hr,y=e=>{if(!(!(u.flags&1)||!u.dirty&&!e))if(t){let e=u.run();if(i||m||(h?e.some((e,t)=>Ge(e,v[t])):Ge(e,v))){f&&f();let n=_r;_r=u;try{let n=[e,v===hr?void 0:h&&v[0]===hr?[]:v,p];v=e,c?c(t,3,n):t(...n)}finally{_r=n}}}else u.run()};return s&&s(y),u=new Ct(d),u.scheduler=o?()=>o(y,!1):y,p=e=>yr(e,!1,u),f=u.onStop=()=>{let e=gr.get(u);if(e){if(c)c(e,4);else for(let t of e)t();gr.delete(u)}},t?r?y(!0):v=u.run():o?o(y.bind(null,!0),!0):u.run(),_.pause=u.pause.bind(u),_.resume=u.resume.bind(u),_.stop=_,_}function xr(e,t=1/0,n){if(t<=0||!Ae(e)||e.__v_skip||(n||=new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,Xn(e))xr(e.value,t,n);else if(O(e))for(let r=0;r{xr(e,t,n)});else if(Fe(e)){for(let r in e)xr(e[r],t,n);for(let r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&xr(e[r],t,n)}return e}var Sr=[];function Cr(e){Sr.push(e)}function wr(){Sr.pop()}function Tr(e,t){}var Er={SETUP_FUNCTION:0,0:`SETUP_FUNCTION`,RENDER_FUNCTION:1,1:`RENDER_FUNCTION`,NATIVE_EVENT_HANDLER:5,5:`NATIVE_EVENT_HANDLER`,COMPONENT_EVENT_HANDLER:6,6:`COMPONENT_EVENT_HANDLER`,VNODE_HOOK:7,7:`VNODE_HOOK`,DIRECTIVE_HOOK:8,8:`DIRECTIVE_HOOK`,TRANSITION_HOOK:9,9:`TRANSITION_HOOK`,APP_ERROR_HANDLER:10,10:`APP_ERROR_HANDLER`,APP_WARN_HANDLER:11,11:`APP_WARN_HANDLER`,FUNCTION_REF:12,12:`FUNCTION_REF`,ASYNC_COMPONENT_LOADER:13,13:`ASYNC_COMPONENT_LOADER`,SCHEDULER:14,14:`SCHEDULER`,COMPONENT_UPDATE:15,15:`COMPONENT_UPDATE`,APP_UNMOUNT_CLEANUP:16,16:`APP_UNMOUNT_CLEANUP`},Dr={sp:`serverPrefetch hook`,bc:`beforeCreate hook`,c:`created hook`,bm:`beforeMount hook`,m:`mounted hook`,bu:`beforeUpdate hook`,u:`updated`,bum:`beforeUnmount hook`,um:`unmounted hook`,a:`activated hook`,da:`deactivated hook`,ec:`errorCaptured hook`,rtc:`renderTracked hook`,rtg:`renderTriggered hook`,0:`setup function`,1:`render function`,2:`watcher getter`,3:`watcher callback`,4:`watcher cleanup function`,5:`native event handler`,6:`component event handler`,7:`vnode hook`,8:`directive hook`,9:`transition hook`,10:`app errorHandler`,11:`app warnHandler`,12:`ref function`,13:`async component loader`,14:`scheduler flush`,15:`component update`,16:`app unmount cleanup function`};function Or(e,t,n,r){try{return r?e(...r):e()}catch(e){Ar(e,t,n)}}function kr(e,t,n,r){if(k(e)){let i=Or(e,t,n,r);return i&&je(i)&&i.catch(e=>{Ar(e,t,n)}),i}if(O(e)){let i=[];for(let a=0;a>>1,i=Mr[r],a=Kr(i);a=Kr(n)?Mr.push(e):Mr.splice(Br(t),0,e),e.flags|=1,Hr()}}function Hr(){Rr||=Lr.then(qr)}function Ur(e){O(e)?Pr.push(...e):Fr&&e.id===-1?Fr.splice(Ir+1,0,e):e.flags&1||(Pr.push(e),e.flags|=1),Hr()}function Wr(e,t,n=Nr+1){for(;nKr(e)-Kr(t));if(Pr.length=0,Fr){Fr.push(...e);return}for(Fr=e,Ir=0;Ire.id==null?e.flags&2?-1:1/0:e.id;function qr(e){try{for(Nr=0;NrJr.emit(e,...t)),Yr=[]):typeof window<`u`&&window.HTMLElement&&!(window.navigator?.userAgent)?.includes(`jsdom`)?((t.__VUE_DEVTOOLS_HOOK_REPLAY__=t.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push(e=>{Xr(e,t)}),setTimeout(()=>{Jr||(t.__VUE_DEVTOOLS_HOOK_REPLAY__=null,Yr=[])},3e3)):Yr=[]}var Zr=null,Qr=null;function $r(e){let t=Zr;return Zr=e,Qr=e&&e.type.__scopeId||null,t}function ei(e){Qr=e}function ti(){Qr=null}var ni=e=>N;function N(e,t=Zr,n){if(!t||e._n)return e;let r=(...n)=>{r._d&&Ks(-1);let i=$r(t),a;try{a=e(...n)}finally{$r(i),r._d&&Ks(1)}return a};return r._n=!0,r._c=!0,r._d=!0,r}function ri(e,t){if(Zr===null)return e;let n=kc(Zr),r=e.dirs||=[];for(let e=0;e1)return n&&k(t)?t.call(r&&r.proxy):t}}function si(){return!!(fc()||Io)}var ci=Symbol.for(`v-scx`),li=()=>oi(ci);function ui(e,t){return mi(e,null,t)}function di(e,t){return mi(e,null,{flush:`post`})}function fi(e,t){return mi(e,null,{flush:`sync`})}function pi(e,t,n){return mi(e,t,n)}function mi(e,t,n=me){let{immediate:r,deep:i,flush:a,once:o}=n,s=be({},n),c=t&&r||!t&&a!==`post`,l;if(vc){if(a===`sync`){let e=li();l=e.__watcherHandles||=[]}else if(!c){let e=()=>{};return e.stop=ge,e.resume=ge,e.pause=ge,e}}let u=dc;s.call=(e,t,n)=>kr(e,u,t,n);let d=!1;a===`post`?s.scheduler=e=>{ms(e,u&&u.suspense)}:a!==`sync`&&(d=!0,s.scheduler=(e,t)=>{t?e():Vr(e)}),s.augmentJob=e=>{t&&(e.flags|=4),d&&(e.flags|=2,u&&(e.id=u.uid,e.i=u))};let f=br(e,t,s);return vc&&(l?l.push(f):c&&f()),f}function hi(e,t,n){let r=this.proxy,i=Oe(e)?e.includes(`.`)?gi(r,e):()=>r[e]:e.bind(r,r),a;k(t)?a=t:(a=t.handler,n=t);let o=hc(this),s=mi(i,a.bind(r),n);return o(),s}function gi(e,t){let n=t.split(`.`);return()=>{let t=e;for(let e=0;ee.__isTeleport,yi=e=>e&&(e.disabled||e.disabled===``),bi=e=>e&&(e.defer||e.defer===``),xi=e=>typeof SVGElement<`u`&&e instanceof SVGElement,Si=e=>typeof MathMLElement==`function`&&e instanceof MathMLElement,Ci=(e,t)=>{let n=e&&e.to;return Oe(n)?t?t(n):null:n},wi={name:`Teleport`,__isTeleport:!0,process(e,t,n,r,i,a,o,s,c,l){let{mc:u,pc:d,pbc:f,o:{insert:p,querySelector:m,createText:h,createComment:g}}=l,_=yi(t.props),{shapeFlag:v,children:y,dynamicChildren:b}=t;if(e==null){let e=t.el=h(``),l=t.anchor=h(``);p(e,n,r),p(l,n,r);let d=(e,t)=>{v&16&&u(y,e,t,i,a,o,s,c)},f=()=>{let e=t.target=Ci(t.props,m),n=ki(e,t,h,p);e&&(o!==`svg`&&xi(e)?o=`svg`:o!==`mathml`&&Si(e)&&(o=`mathml`),i&&i.isCE&&(i.ce._teleportTargets||(i.ce._teleportTargets=new Set)).add(e),_||(d(e,n),Oi(t,!1)))};_&&(d(n,l),Oi(t,!0)),bi(t.props)?(t.el.__isMounted=!1,ms(()=>{f(),delete t.el.__isMounted},a)):f()}else{if(bi(t.props)&&e.el.__isMounted===!1){ms(()=>{wi.process(e,t,n,r,i,a,o,s,c,l)},a);return}t.el=e.el,t.targetStart=e.targetStart;let u=t.anchor=e.anchor,p=t.target=e.target,h=t.targetAnchor=e.targetAnchor,g=yi(e.props),v=g?n:p,y=g?u:h;if(o===`svg`||xi(p)?o=`svg`:(o===`mathml`||Si(p))&&(o=`mathml`),b?(f(e.dynamicChildren,b,v,i,a,o,s),xs(e,t,!0)):c||d(e,t,v,y,i,a,o,s,!1),_)g?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Ti(t,n,u,l,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){let e=t.target=Ci(t.props,m);e&&Ti(t,e,null,l,0)}else g&&Ti(t,p,h,l,1);Oi(t,_)}},remove(e,t,n,{um:r,o:{remove:i}},a){let{shapeFlag:o,children:s,anchor:c,targetStart:l,targetAnchor:u,target:d,props:f}=e;if(d&&(i(l),i(u)),a&&i(c),o&16){let e=a||!yi(f);for(let i=0;i{e.isMounted=!0}),Na(()=>{e.isUnmounting=!0}),e}var Ni=[Function,Array],Pi={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Ni,onEnter:Ni,onAfterEnter:Ni,onEnterCancelled:Ni,onBeforeLeave:Ni,onLeave:Ni,onAfterLeave:Ni,onLeaveCancelled:Ni,onBeforeAppear:Ni,onAppear:Ni,onAfterAppear:Ni,onAppearCancelled:Ni},Fi=e=>{let t=e.subTree;return t.component?Fi(t.component):t},Ii={name:`BaseTransition`,props:Pi,setup(e,{slots:t}){let n=fc(),r=Mi();return()=>{let i=t.default&&Wi(t.default(),!0);if(!i||!i.length)return;let a=Li(i),o=Kn(e),{mode:s}=o;if(r.isLeaving)return Vi(a);let c=Hi(a);if(!c)return Vi(a);let l=Bi(c,o,r,n,e=>l=e);c.type!==Bs&&Ui(c,l);let u=n.subTree&&Hi(n.subTree);if(u&&u.type!==Bs&&!Ys(u,c)&&Fi(n).type!==Bs){let e=Bi(u,o,r,n);if(Ui(u,e),s===`out-in`&&c.type!==Bs)return r.isLeaving=!0,e.afterLeave=()=>{r.isLeaving=!1,n.job.flags&8||n.update(),delete e.afterLeave,u=void 0},Vi(a);s===`in-out`&&c.type!==Bs?e.delayLeave=(e,t,n)=>{let i=zi(r,u);i[String(u.key)]=u,e[Ai]=()=>{t(),e[Ai]=void 0,delete l.delayedLeave,u=void 0},l.delayedLeave=()=>{n(),delete l.delayedLeave,u=void 0}}:u=void 0}else u&&=void 0;return a}}};function Li(e){let t=e[0];if(e.length>1){for(let n of e)if(n.type!==Bs){t=n;break}}return t}var Ri=Ii;function zi(e,t){let{leavingVNodes:n}=e,r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function Bi(e,t,n,r,i){let{appear:a,mode:o,persisted:s=!1,onBeforeEnter:c,onEnter:l,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:f,onLeave:p,onAfterLeave:m,onLeaveCancelled:h,onBeforeAppear:g,onAppear:_,onAfterAppear:v,onAppearCancelled:y}=t,b=String(e.key),x=zi(n,e),ee=(e,t)=>{e&&kr(e,r,9,t)},S=(e,t)=>{let n=t[1];ee(e,t),O(e)?e.every(e=>e.length<=1)&&n():e.length<=1&&n()},C={mode:o,persisted:s,beforeEnter(t){let r=c;if(!n.isMounted)if(a)r=g||c;else return;t[Ai]&&t[Ai](!0);let i=x[b];i&&Ys(e,i)&&i.el[Ai]&&i.el[Ai](),ee(r,[t])},enter(e){let t=l,r=u,i=d;if(!n.isMounted)if(a)t=_||l,r=v||u,i=y||d;else return;let o=!1,s=e[ji]=t=>{o||(o=!0,ee(t?i:r,[e]),C.delayedLeave&&C.delayedLeave(),e[ji]=void 0)};t?S(t,[e,s]):s()},leave(t,r){let i=String(e.key);if(t[ji]&&t[ji](!0),n.isUnmounting)return r();ee(f,[t]);let a=!1,o=t[Ai]=n=>{a||(a=!0,r(),ee(n?h:m,[t]),t[Ai]=void 0,x[i]===e&&delete x[i])};x[i]=e,p?S(p,[t,o]):o()},clone(e){let a=Bi(e,t,n,r,i);return i&&i(a),a}};return C}function Vi(e){if(va(e))return e=tc(e),e.children=null,e}function Hi(e){if(!va(e))return vi(e.type)&&e.children?Li(e.children):e;if(e.component)return e.component.subTree;let{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&k(n.default))return n.default()}}function Ui(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Ui(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Wi(e,t=!1,n){let r=[],i=0;for(let a=0;a1)for(let e=0;ebe({name:e.name},t,{setup:e}))():e}function Gi(){let e=fc();return e?(e.appContext.config.idPrefix||`v`)+`-`+e.ids[0]+ e.ids[1]++:``}function Ki(e){e.ids=[e.ids[0]+ e.ids[2]+++`-`,0,0]}function qi(e){let t=fc(),n=Zn(null);if(t){let r=t.refs===me?t.refs={}:t.refs;Object.defineProperty(r,e,{enumerable:!0,get:()=>n.value,set:e=>n.value=e})}return n}var Ji=new WeakMap;function Yi(e,t,n,r,i=!1){if(O(e)){e.forEach((e,a)=>Yi(e,t&&(O(t)?t[a]:t),n,r,i));return}if(ha(r)&&!i){r.shapeFlag&512&&r.type.__asyncResolved&&r.component.subTree.component&&Yi(e,t,n,r.component.subTree);return}let a=r.shapeFlag&4?kc(r.component):r.el,o=i?null:a,{i:s,r:c}=e,l=t&&t.r,u=s.refs===me?s.refs={}:s.refs,d=s.setupState,f=Kn(d),p=d===me?_e:e=>Ce(f,e),m=e=>!0;if(l!=null&&l!==c){if(Xi(t),Oe(l))u[l]=null,p(l)&&(d[l]=null);else if(Xn(l)){m(l)&&(l.value=null);let e=t;e.k&&(u[e.k]=null)}}if(k(c))Or(c,s,12,[o,u]);else{let t=Oe(c),r=Xn(c);if(t||r){let s=()=>{if(e.f){let n=t?p(c)?d[c]:u[c]:m(c)||!e.k?c.value:u[e.k];if(i)O(n)&&xe(n,a);else if(O(n))n.includes(a)||n.push(a);else if(t)u[c]=[a],p(c)&&(d[c]=u[c]);else{let t=[a];m(c)&&(c.value=t),e.k&&(u[e.k]=t)}}else t?(u[c]=o,p(c)&&(d[c]=o)):r&&(m(c)&&(c.value=o),e.k&&(u[e.k]=o))};if(o){let t=()=>{s(),Ji.delete(e)};t.id=-1,Ji.set(e,t),ms(t,n)}else Xi(e),s()}}}function Xi(e){let t=Ji.get(e);t&&(t.flags|=8,Ji.delete(e))}var Zi=!1,Qi=()=>{Zi||=(console.error(`Hydration completed but contains mismatches.`),!0)},$i=e=>e.namespaceURI.includes(`svg`)&&e.tagName!==`foreignObject`,ea=e=>e.namespaceURI.includes(`MathML`),ta=e=>{if(e.nodeType===1){if($i(e))return`svg`;if(ea(e))return`mathml`}},na=e=>e.nodeType===8;function ra(e){let{mt:t,p:n,o:{patchProp:r,createText:i,nextSibling:a,parentNode:o,remove:s,insert:c,createComment:l}}=e,u=(e,t)=>{if(!t.hasChildNodes()){n(null,e,t),Gr(),t._vnode=e;return}d(t.firstChild,e,null,null,null),Gr(),t._vnode=e},d=(n,r,s,l,u,y=!1)=>{y||=!!r.dynamicChildren;let b=na(n)&&n.data===`[`,x=()=>h(n,r,s,l,u,b),{type:ee,ref:S,shapeFlag:C,patchFlag:w}=r,te=n.nodeType;r.el=n,w===-2&&(y=!1,r.dynamicChildren=null);let ne=null;switch(ee){case zs:te===3?(n.data!==r.children&&(Qi(),n.data=r.children),ne=a(n)):r.children===``?(c(r.el=i(``),o(n),n),ne=n):ne=x();break;case Bs:v(n)?(ne=a(n),_(r.el=n.content.firstChild,n,s)):ne=te!==8||b?x():a(n);break;case Vs:if(b&&(n=a(n),te=n.nodeType),te===1||te===3){ne=n;let e=!r.children.length;for(let t=0;t{o||=!!t.dynamicChildren;let{type:c,props:l,patchFlag:u,shapeFlag:d,dirs:f,transition:m}=t,h=c===`input`||c===`option`;if(h||u!==-1){f&&ii(t,null,n,`created`);let c=!1;if(v(e)){c=bs(null,m)&&n&&n.vnode.props&&n.vnode.props.appear;let r=e.content.firstChild;if(c){let e=r.getAttribute(`class`);e&&(r.$cls=e),m.beforeEnter(r)}_(r,e,n),t.el=e=r}if(d&16&&!(l&&(l.innerHTML||l.textContent))){let r=p(e.firstChild,t,e,n,i,a,o);for(;r;){oa(e,1)||Qi();let t=r;r=r.nextSibling,s(t)}}else if(d&8){let n=t.children;n[0]===` +`&&(e.tagName===`PRE`||e.tagName===`TEXTAREA`)&&(n=n.slice(1));let{textContent:r}=e;r!==n&&r!==n.replace(/\r\n|\r/g,` +`)&&(oa(e,0)||Qi(),e.textContent=t.children)}if(l){if(h||!o||u&48){let t=e.tagName.includes(`-`);for(let i in l)(h&&(i.endsWith(`value`)||i===`indeterminate`)||ve(i)&&!Le(i)||i[0]===`.`||t&&!Le(i))&&r(e,i,null,l[i],void 0,n)}else if(l.onClick)r(e,`onClick`,null,l.onClick,void 0,n);else if(u&4&&Hn(l.style))for(let e in l.style)l.style[e]}let g;(g=l&&l.onVnodeBeforeMount)&&sc(g,n,t),f&&ii(t,null,n,`beforeMount`),((g=l&&l.onVnodeMounted)||f||c)&&Is(()=>{g&&sc(g,n,t),c&&m.enter(e),f&&ii(t,null,n,`mounted`)},i)}return e.nextSibling},p=(e,t,r,o,s,l,u)=>{u||=!!t.dynamicChildren;let f=t.children,p=f.length;for(let t=0;t{let{slotScopeIds:u}=t;u&&(i=i?i.concat(u):u);let d=o(e),f=p(a(e),t,d,n,r,i,s);return f&&na(f)&&f.data===`]`?a(t.anchor=f):(Qi(),c(t.anchor=l(`]`),d,f),f)},h=(e,t,r,i,c,l)=>{if(oa(e.parentElement,1)||Qi(),t.el=null,l){let t=g(e);for(;;){let n=a(e);if(n&&n!==t)s(n);else break}}let u=a(e),d=o(e);return s(e),n(null,t,d,u,r,i,ta(d),c),r&&(r.vnode.el=t.el,Yo(r,t.el)),u},g=(e,t=`[`,n=`]`)=>{let r=0;for(;e;)if(e=a(e),e&&na(e)&&(e.data===t&&r++,e.data===n)){if(r===0)return a(e);r--}return e},_=(e,t,n)=>{let r=t.parentNode;r&&r.replaceChild(e,t);let i=n;for(;i;)i.vnode.el===t&&(i.vnode.el=i.subTree.el=e),i=i.parent},v=e=>e.nodeType===1&&e.tagName===`TEMPLATE`;return[u,d]}var ia=`data-allow-mismatch`,aa={0:`text`,1:`children`,2:`class`,3:`style`,4:`attribute`};function oa(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(ia);)e=e.parentElement;let n=e&&e.getAttribute(ia);if(n==null)return!1;if(n===``)return!0;{let e=n.split(`,`);return t===0&&e.includes(`children`)?!0:e.includes(aa[t])}}var sa=Ze().requestIdleCallback||(e=>setTimeout(e,1)),ca=Ze().cancelIdleCallback||(e=>clearTimeout(e)),la=(e=1e4)=>t=>{let n=sa(t,{timeout:e});return()=>ca(n)};function ua(e){let{top:t,left:n,bottom:r,right:i}=e.getBoundingClientRect(),{innerHeight:a,innerWidth:o}=window;return(t>0&&t0&&r0&&n0&&i(t,n)=>{let r=new IntersectionObserver(e=>{for(let n of e)if(n.isIntersecting){r.disconnect(),t();break}},e);return n(e=>{if(e instanceof Element){if(ua(e))return t(),r.disconnect(),!1;r.observe(e)}}),()=>r.disconnect()},fa=e=>t=>{if(e){let n=matchMedia(e);if(n.matches)t();else return n.addEventListener(`change`,t,{once:!0}),()=>n.removeEventListener(`change`,t)}},pa=(e=[])=>(t,n)=>{Oe(e)&&(e=[e]);let r=!1,i=e=>{r||(r=!0,a(),t(),e.target.dispatchEvent(new e.constructor(e.type,e)))},a=()=>{n(t=>{for(let n of e)t.removeEventListener(n,i)})};return n(t=>{for(let n of e)t.addEventListener(n,i,{once:!0})}),a};function ma(e,t){if(na(e)&&e.data===`[`){let n=1,r=e.nextSibling;for(;r;){if(r.nodeType===1){if(t(r)===!1)break}else if(na(r))if(r.data===`]`){if(--n===0)break}else r.data===`[`&&n++;r=r.nextSibling}}else t(e)}var ha=e=>!!e.type.__asyncLoader;function ga(e){k(e)&&(e={loader:e});let{loader:t,loadingComponent:n,errorComponent:r,delay:i=200,hydrate:a,timeout:o,suspensible:s=!0,onError:c}=e,l=null,u,d=0,f=()=>(d++,l=null,p()),p=()=>{let e;return l||(e=l=t().catch(e=>{if(e=e instanceof Error?e:Error(String(e)),c)return new Promise((t,n)=>{c(e,()=>t(f()),()=>n(e),d+1)});throw e}).then(t=>e!==l&&l?l:(t&&(t.__esModule||t[Symbol.toStringTag]===`Module`)&&(t=t.default),u=t,t)))};return P({name:`AsyncComponentWrapper`,__asyncLoader:p,__asyncHydrate(e,t,n){let r=!1;(t.bu||=[]).push(()=>r=!0);let i=()=>{r||n()},o=a?()=>{let n=a(i,t=>ma(e,t));n&&(t.bum||=[]).push(n)}:i;u?o():p().then(()=>!t.isUnmounted&&o())},get __asyncResolved(){return u},setup(){let e=dc;if(Ki(e),u)return()=>_a(u,e);let t=t=>{l=null,Ar(t,e,13,!r)};if(s&&e.suspense||vc)return p().then(t=>()=>_a(t,e)).catch(e=>(t(e),()=>r?V(r,{error:e}):null));let a=j(!1),c=j(),d=j(!!i);return i&&setTimeout(()=>{d.value=!1},i),o!=null&&setTimeout(()=>{if(!a.value&&!c.value){let e=Error(`Async component timed out after ${o}ms.`);t(e),c.value=e}},o),p().then(()=>{a.value=!0,e.parent&&va(e.parent.vnode)&&e.parent.update()}).catch(e=>{t(e),c.value=e}),()=>{if(a.value&&u)return _a(u,e);if(c.value&&r)return V(r,{error:c.value});if(n&&!d.value)return _a(n,e)}}})}function _a(e,t){let{ref:n,props:r,children:i,ce:a}=t.vnode,o=V(e,r,i);return o.ref=n,o.ce=a,delete t.vnode.ce,o}var va=e=>e.type.__isKeepAlive,ya={name:`KeepAlive`,__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){let n=fc(),r=n.ctx;if(!r.renderer)return()=>{let e=t.default&&t.default();return e&&e.length===1?e[0]:e};let i=new Map,a=new Set,o=null,s=n.suspense,{renderer:{p:c,m:l,um:u,o:{createElement:d}}}=r,f=d(`div`);r.activate=(e,t,n,r,i)=>{let a=e.component;l(e,t,n,0,s),c(a.vnode,e,t,n,a,s,r,e.slotScopeIds,i),ms(()=>{a.isDeactivated=!1,a.a&&Ke(a.a);let t=e.props&&e.props.onVnodeMounted;t&&sc(t,a.parent,e)},s)},r.deactivate=e=>{let t=e.component;ws(t.m),ws(t.a),l(e,f,null,1,s),ms(()=>{t.da&&Ke(t.da);let n=e.props&&e.props.onVnodeUnmounted;n&&sc(n,t.parent,e),t.isDeactivated=!0},s)};function p(e){Ta(e),u(e,n,s,!0)}function m(e){i.forEach((t,n)=>{let r=Ac(ha(t)?t.type.__asyncResolved||{}:t.type);r&&!e(r)&&h(n)})}function h(e){let t=i.get(e);t&&(!o||!Ys(t,o))?p(t):o&&Ta(o),i.delete(e),a.delete(e)}pi(()=>[e.include,e.exclude],([e,t])=>{e&&m(t=>ba(e,t)),t&&m(e=>!ba(t,e))},{flush:`post`,deep:!0});let g=null,_=()=>{g!=null&&(Es(n.subTree.type)?ms(()=>{i.set(g,Ea(n.subTree))},n.subTree.suspense):i.set(g,Ea(n.subTree)))};return Aa(_),Ma(_),Na(()=>{i.forEach(e=>{let{subTree:t,suspense:r}=n,i=Ea(t);if(e.type===i.type&&e.key===i.key){Ta(i);let e=i.component.da;e&&ms(e,r);return}p(e)})}),()=>{if(g=null,!t.default)return o=null;let n=t.default(),r=n[0];if(n.length>1)return o=null,n;if(!Js(r)||!(r.shapeFlag&4)&&!(r.shapeFlag&128))return o=null,r;let s=Ea(r);if(s.type===Bs)return o=null,s;let c=s.type,l=Ac(ha(s)?s.type.__asyncResolved||{}:c),{include:u,exclude:d,max:f}=e;if(u&&(!l||!ba(u,l))||d&&l&&ba(d,l))return s.shapeFlag&=-257,o=s,r;let p=s.key==null?c:s.key,m=i.get(p);return s.el&&(s=tc(s),r.shapeFlag&128&&(r.ssContent=s)),g=p,m?(s.el=m.el,s.component=m.component,s.transition&&Ui(s,s.transition),s.shapeFlag|=512,a.delete(p),a.add(p)):(a.add(p),f&&a.size>parseInt(f,10)&&h(a.values().next().value)),s.shapeFlag|=256,o=s,Es(r.type)?r:s}}};function ba(e,t){return O(e)?e.some(e=>ba(e,t)):Oe(e)?e.split(`,`).includes(t):De(e)?(e.lastIndex=0,e.test(t)):!1}function xa(e,t){Ca(e,`a`,t)}function Sa(e,t){Ca(e,`da`,t)}function Ca(e,t,n=dc){let r=e.__wdc||=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()};if(Da(t,r,n),n){let e=n.parent;for(;e&&e.parent;)va(e.parent.vnode)&&wa(r,t,n,e),e=e.parent}}function wa(e,t,n,r){let i=Da(t,e,r,!0);Pa(()=>{xe(r[t],i)},n)}function Ta(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function Ea(e){return e.shapeFlag&128?e.ssContent:e}function Da(e,t,n=dc,r=!1){if(n){let i=n[e]||(n[e]=[]),a=t.__weh||=(...r)=>{Bt();let i=hc(n),a=kr(t,n,e,r);return i(),Vt(),a};return r?i.unshift(a):i.push(a),a}}var Oa=e=>(t,n=dc)=>{(!vc||e===`sp`)&&Da(e,(...e)=>t(...e),n)},ka=Oa(`bm`),Aa=Oa(`m`),ja=Oa(`bu`),Ma=Oa(`u`),Na=Oa(`bum`),Pa=Oa(`um`),Fa=Oa(`sp`),Ia=Oa(`rtg`),La=Oa(`rtc`);function Ra(e,t=dc){Da(`ec`,e,t)}var za=`components`,Ba=`directives`;function Va(e,t){return Ga(za,e,!0,t)||e}var Ha=Symbol.for(`v-ndc`);function Ua(e){return Oe(e)?Ga(za,e,!1)||e:e||Ha}function Wa(e){return Ga(Ba,e)}function Ga(e,t,n=!0,r=!1){let i=Zr||dc;if(i){let n=i.type;if(e===za){let e=Ac(n,!1);if(e&&(e===t||e===Be(t)||e===Ue(Be(t))))return n}let a=Ka(i[e]||n[e],t)||Ka(i.appContext[e],t);return!a&&r?n:a}}function Ka(e,t){return e&&(e[t]||e[Be(t)]||e[Ue(Be(t))])}function qa(e,t,n,r){let i,a=n&&n[r],o=O(e);if(o||Oe(e)){let n=o&&Hn(e),r=!1,s=!1;n&&(r=!Wn(e),s=Un(e),e=tn(e)),i=Array(e.length);for(let n=0,o=e.length;nt(e,n,void 0,a&&a[n]));else{let n=Object.keys(e);i=Array(n.length);for(let r=0,o=n.length;r{let t=r.fn(...e);return t&&(t.key=r.key),t}:r.fn)}return e}function F(e,t,n={},r,i){if(Zr.ce||Zr.parent&&ha(Zr.parent)&&Zr.parent.ce){let e=Object.keys(n).length>0;return t!==`default`&&(n.name=t),L(),z(I,null,[V(`slot`,n,r&&r())],e?-2:64)}let a=e[t];a&&a._c&&(a._d=!1),L();let o=a&&Ya(a(n)),s=n.key||o&&o.key,c=z(I,{key:(s&&!ke(s)?s:`_${t}`)+(!o&&r?`_fb`:``)},o||(r?r():[]),o&&e._===1?64:-2);return!i&&c.scopeId&&(c.slotScopeIds=[c.scopeId+`-s`]),a&&a._c&&(a._d=!0),c}function Ya(e){return e.some(e=>Js(e)?!(e.type===Bs||e.type===I&&!Ya(e.children)):!0)?e:null}function Xa(e,t){let n={};for(let r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:We(r)]=e[r];return n}var Za=e=>e?_c(e)?kc(e):Za(e.parent):null,Qa=be(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Za(e.parent),$root:e=>Za(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Co(e),$forceUpdate:e=>e.f||=()=>{Vr(e.update)},$nextTick:e=>e.n||=zr.bind(e.proxy),$watch:e=>hi.bind(e)}),$a=(e,t)=>e!==me&&!e.__isScriptSetup&&Ce(e,t),eo={get({_:e},t){if(t===`__v_skip`)return!0;let{ctx:n,setupState:r,data:i,props:a,accessCache:o,type:s,appContext:c}=e;if(t[0]!==`$`){let e=o[t];if(e!==void 0)switch(e){case 1:return r[t];case 2:return i[t];case 4:return n[t];case 3:return a[t]}else if($a(r,t))return o[t]=1,r[t];else if(i!==me&&Ce(i,t))return o[t]=2,i[t];else if(Ce(a,t))return o[t]=3,a[t];else if(n!==me&&Ce(n,t))return o[t]=4,n[t];else vo&&(o[t]=0)}let l=Qa[t],u,d;if(l)return t===`$attrs`&&Zt(e.attrs,`get`,``),l(e);if((u=s.__cssModules)&&(u=u[t]))return u;if(n!==me&&Ce(n,t))return o[t]=4,n[t];if(d=c.config.globalProperties,Ce(d,t))return d[t]},set({_:e},t,n){let{data:r,setupState:i,ctx:a}=e;return $a(i,t)?(i[t]=n,!0):r!==me&&Ce(r,t)?(r[t]=n,!0):Ce(e.props,t)||t[0]===`$`&&t.slice(1)in e?!1:(a[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,props:a,type:o}},s){let c;return!!(n[s]||e!==me&&s[0]!==`$`&&Ce(e,s)||$a(t,s)||Ce(a,s)||Ce(r,s)||Ce(Qa,s)||Ce(i.config.globalProperties,s)||(c=o.__cssModules)&&c[s])},defineProperty(e,t,n){return n.get==null?Ce(n,`value`)&&this.set(e,t,n.value,null):e._.accessCache[t]=0,Reflect.defineProperty(e,t,n)}},to=be({},eo,{get(e,t){if(t!==Symbol.unscopables)return eo.get(e,t,e)},has(e,t){return t[0]!==`_`&&!Qe(t)}});function no(){return null}function ro(){return null}function io(e){}function ao(e){}function oo(){return null}function so(){}function co(e,t){return null}function lo(){return fo(`useSlots`).slots}function uo(){return fo(`useAttrs`).attrs}function fo(e){let t=fc();return t.setupContext||=Oc(t)}function po(e){return O(e)?e.reduce((e,t)=>(e[t]=null,e),{}):e}function mo(e,t){let n=po(e);for(let e in t){if(e.startsWith(`__skip`))continue;let r=n[e];r?O(r)||k(r)?r=n[e]={type:r,default:t[e]}:r.default=t[e]:r===null&&(r=n[e]={default:t[e]}),r&&t[`__skip_${e}`]&&(r.skipFactory=!0)}return n}function ho(e,t){return!e||!t?e||t:O(e)&&O(t)?e.concat(t):be({},po(e),po(t))}function go(e,t){let n={};for(let r in e)t.includes(r)||Object.defineProperty(n,r,{enumerable:!0,get:()=>e[r]});return n}function _o(e){let t=fc(),n=e();return gc(),je(n)&&(n=n.catch(e=>{throw hc(t),e})),[n,()=>hc(t)]}var vo=!0;function yo(e){let t=Co(e),n=e.proxy,r=e.ctx;vo=!1,t.beforeCreate&&xo(t.beforeCreate,e,`bc`);let{data:i,computed:a,methods:o,watch:s,provide:c,inject:l,created:u,beforeMount:d,mounted:f,beforeUpdate:p,updated:m,activated:h,deactivated:g,beforeDestroy:_,beforeUnmount:v,destroyed:y,unmounted:b,render:x,renderTracked:ee,renderTriggered:S,errorCaptured:C,serverPrefetch:w,expose:te,inheritAttrs:ne,components:re,directives:T,filters:ie}=t;if(l&&bo(l,r,null),o)for(let e in o){let t=o[e];k(t)&&(r[e]=t.bind(n))}if(i){let t=i.call(n,n);Ae(t)&&(e.data=Ln(t))}if(vo=!0,a)for(let e in a){let t=a[e],i=W({get:k(t)?t.bind(n,n):k(t.get)?t.get.bind(n,n):ge,set:!k(t)&&k(t.set)?t.set.bind(n):ge});Object.defineProperty(r,e,{enumerable:!0,configurable:!0,get:()=>i.value,set:e=>i.value=e})}if(s)for(let e in s)So(s[e],r,n,e);if(c){let e=k(c)?c.call(n):c;Reflect.ownKeys(e).forEach(t=>{ai(t,e[t])})}u&&xo(u,e,`c`);function ae(e,t){O(t)?t.forEach(t=>e(t.bind(n))):t&&e(t.bind(n))}if(ae(ka,d),ae(Aa,f),ae(ja,p),ae(Ma,m),ae(xa,h),ae(Sa,g),ae(Ra,C),ae(La,ee),ae(Ia,S),ae(Na,v),ae(Pa,b),ae(Fa,w),O(te))if(te.length){let t=e.exposed||={};te.forEach(e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t,enumerable:!0})})}else e.exposed||={};x&&e.render===ge&&(e.render=x),ne!=null&&(e.inheritAttrs=ne),re&&(e.components=re),T&&(e.directives=T),w&&Ki(e)}function bo(e,t,n=ge){for(let n in O(e)&&(e=Oo(e)),e){let r=e[n],i;i=Ae(r)?`default`in r?oi(r.from||n,r.default,!0):oi(r.from||n):oi(r),Xn(i)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>i.value,set:e=>i.value=e}):t[n]=i}}function xo(e,t,n){kr(O(e)?e.map(e=>e.bind(t.proxy)):e.bind(t.proxy),t,n)}function So(e,t,n,r){let i=r.includes(`.`)?gi(n,r):()=>n[r];if(Oe(e)){let n=t[e];k(n)&&pi(i,n)}else if(k(e))pi(i,e.bind(n));else if(Ae(e))if(O(e))e.forEach(e=>So(e,t,n,r));else{let r=k(e.handler)?e.handler.bind(n):t[e.handler];k(r)&&pi(i,r,e)}}function Co(e){let t=e.type,{mixins:n,extends:r}=t,{mixins:i,optionsCache:a,config:{optionMergeStrategies:o}}=e.appContext,s=a.get(t),c;return s?c=s:!i.length&&!n&&!r?c=t:(c={},i.length&&i.forEach(e=>wo(c,e,o,!0)),wo(c,t,o)),Ae(t)&&a.set(t,c),c}function wo(e,t,n,r=!1){let{mixins:i,extends:a}=t;for(let o in a&&wo(e,a,n,!0),i&&i.forEach(t=>wo(e,t,n,!0)),t)if(!(r&&o===`expose`)){let r=To[o]||n&&n[o];e[o]=r?r(e[o],t[o]):t[o]}return e}var To={data:Eo,props:jo,emits:jo,methods:Ao,computed:Ao,beforeCreate:ko,created:ko,beforeMount:ko,mounted:ko,beforeUpdate:ko,updated:ko,beforeDestroy:ko,beforeUnmount:ko,destroyed:ko,unmounted:ko,activated:ko,deactivated:ko,errorCaptured:ko,serverPrefetch:ko,components:Ao,directives:Ao,watch:Mo,provide:Eo,inject:Do};function Eo(e,t){return t?e?function(){return be(k(e)?e.call(this,this):e,k(t)?t.call(this,this):t)}:t:e}function Do(e,t){return Ao(Oo(e),Oo(t))}function Oo(e){if(O(e)){let t={};for(let n=0;n{let c,l=me,u;return fi(()=>{let t=e[i];Ge(c,t)&&(c=t,s())}),{get(){return o(),n.get?n.get(c):c},set(e){let o=n.set?n.set(e):e;if(!Ge(o,c)&&!(l!==me&&Ge(e,l)))return;let d=r.vnode.props;d&&(t in d||i in d||a in d)&&(`onUpdate:${t}`in d||`onUpdate:${i}`in d||`onUpdate:${a}`in d)||(c=e,s()),r.emit(`update:${t}`,o),Ge(e,o)&&Ge(e,l)&&!Ge(o,u)&&s(),l=e,u=o}}});return s[Symbol.iterator]=()=>{let e=0;return{next(){return e<2?{value:e++?o||me:s,done:!1}:{done:!0}}}},s}var Ro=(e,t)=>t===`modelValue`||t===`model-value`?e.modelModifiers:e[`${t}Modifiers`]||e[`${Be(t)}Modifiers`]||e[`${He(t)}Modifiers`];function zo(e,t,...n){if(e.isUnmounted)return;let r=e.vnode.props||me,i=n,a=t.startsWith(`update:`),o=a&&Ro(r,t.slice(7));o&&(o.trim&&(i=n.map(e=>Oe(e)?e.trim():e)),o.number&&(i=n.map(Je)));let s,c=r[s=We(t)]||r[s=We(Be(t))];!c&&a&&(c=r[s=We(He(t))]),c&&kr(c,e,6,i);let l=r[s+`Once`];if(l){if(!e.emitted)e.emitted={};else if(e.emitted[s])return;e.emitted[s]=!0,kr(l,e,6,i)}}var Bo=new WeakMap;function Vo(e,t,n=!1){let r=n?Bo:t.emitsCache,i=r.get(e);if(i!==void 0)return i;let a=e.emits,o={},s=!1;if(!k(e)){let r=e=>{let n=Vo(e,t,!0);n&&(s=!0,be(o,n))};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}return!a&&!s?(Ae(e)&&r.set(e,null),null):(O(a)?a.forEach(e=>o[e]=null):be(o,a),Ae(e)&&r.set(e,o),o)}function Ho(e,t){return!e||!ve(t)?!1:(t=t.slice(2).replace(/Once$/,``),Ce(e,t[0].toLowerCase()+t.slice(1))||Ce(e,He(t))||Ce(e,t))}function Uo(e){let{type:t,vnode:n,proxy:r,withProxy:i,propsOptions:[a],slots:o,attrs:s,emit:c,render:l,renderCache:u,props:d,data:f,setupState:p,ctx:m,inheritAttrs:h}=e,g=$r(e),_,v;try{if(n.shapeFlag&4){let e=i||r,t=e;_=ic(l.call(t,e,u,d,p,f,m)),v=s}else{let e=t;_=ic(e.length>1?e(d,{attrs:s,slots:o,emit:c}):e(d,null)),v=t.props?s:Go(s)}}catch(t){Hs.length=0,Ar(t,e,1),_=V(Bs)}let y=_;if(v&&h!==!1){let e=Object.keys(v),{shapeFlag:t}=y;e.length&&t&7&&(a&&e.some(ye)&&(v=Ko(v,a)),y=tc(y,v,!1,!0))}return n.dirs&&(y=tc(y,null,!1,!0),y.dirs=y.dirs?y.dirs.concat(n.dirs):n.dirs),n.transition&&Ui(y,n.transition),_=y,$r(g),_}function Wo(e,t=!0){let n;for(let t=0;t{let t;for(let n in e)(n===`class`||n===`style`||ve(n))&&((t||={})[n]=e[n]);return t},Ko=(e,t)=>{let n={};for(let r in e)(!ye(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function qo(e,t,n){let{props:r,children:i,component:a}=e,{props:o,children:s,patchFlag:c}=t,l=a.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return r?Jo(r,o,l):!!o;if(c&8){let e=t.dynamicProps;for(let t=0;tObject.create(Xo),Qo=e=>Object.getPrototypeOf(e)===Xo;function $o(e,t,n,r=!1){let i={},a=Zo();for(let n in e.propsDefaults=Object.create(null),ts(e,t,i,a),e.propsOptions[0])n in i||(i[n]=void 0);n?e.props=r?i:Rn(i):e.type.props?e.props=i:e.props=a,e.attrs=a}function es(e,t,n,r){let{props:i,attrs:a,vnode:{patchFlag:o}}=e,s=Kn(i),[c]=e.propsOptions,l=!1;if((r||o>0)&&!(o&16)){if(o&8){let n=e.vnode.dynamicProps;for(let r=0;r{c=!0;let[n,r]=is(e,t,!0);be(o,n),r&&s.push(...r)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!a&&!c)return Ae(e)&&r.set(e,he),he;if(O(a))for(let e=0;ee===`_`||e===`_ctx`||e===`$stable`,ss=e=>O(e)?e.map(ic):[ic(e)],cs=(e,t,n)=>{if(t._n)return t;let r=N((...e)=>ss(t(...e)),n);return r._c=!1,r},ls=(e,t,n)=>{let r=e._ctx;for(let n in e){if(os(n))continue;let i=e[n];if(k(i))t[n]=cs(n,i,r);else if(i!=null){let e=ss(i);t[n]=()=>e}}},us=(e,t)=>{let n=ss(t);e.slots.default=()=>n},ds=(e,t,n)=>{for(let r in t)(n||!os(r))&&(e[r]=t[r])},fs=(e,t,n)=>{let r=e.slots=Zo();if(e.vnode.shapeFlag&32){let e=t._;e?(ds(r,t,n),n&&qe(r,`_`,e,!0)):ls(t,r)}else t&&us(e,t)},ps=(e,t,n)=>{let{vnode:r,slots:i}=e,a=!0,o=me;if(r.shapeFlag&32){let e=t._;e?n&&e===1?a=!1:ds(i,t,n):(a=!t.$stable,ls(t,i)),o=t}else t&&(us(e,t),o={default:1});if(a)for(let e in i)!os(e)&&o[e]==null&&delete i[e]},ms=Is;function hs(e){return _s(e)}function gs(e){return _s(e,ra)}function _s(e,t){let n=Ze();n.__VUE__=!0;let{insert:r,remove:i,patchProp:a,createElement:o,createText:s,createComment:c,setText:l,setElementText:u,parentNode:d,nextSibling:f,setScopeId:p=ge,insertStaticContent:m}=e,h=(e,t,n,r=null,i=null,a=null,o=void 0,s=null,c=!!t.dynamicChildren)=>{if(e===t)return;e&&!Ys(e,t)&&(r=ve(e),D(e,i,a,!0),e=null),t.patchFlag===-2&&(c=!1,t.dynamicChildren=null);let{type:l,ref:u,shapeFlag:d}=t;switch(l){case zs:g(e,t,n,r);break;case Bs:_(e,t,n,r);break;case Vs:e??v(t,n,r,o);break;case I:re(e,t,n,r,i,a,o,s,c);break;default:d&1?x(e,t,n,r,i,a,o,s,c):d&6?T(e,t,n,r,i,a,o,s,c):(d&64||d&128)&&l.process(e,t,n,r,i,a,o,s,c,xe)}u!=null&&i?Yi(u,e&&e.ref,a,t||e,!t):u==null&&e&&e.ref!=null&&Yi(e.ref,null,a,e,!0)},g=(e,t,n,i)=>{if(e==null)r(t.el=s(t.children),n,i);else{let n=t.el=e.el;t.children!==e.children&&l(n,t.children)}},_=(e,t,n,i)=>{e==null?r(t.el=c(t.children||``),n,i):t.el=e.el},v=(e,t,n,r)=>{[e.el,e.anchor]=m(e.children,t,n,r,e.el,e.anchor)},y=({el:e,anchor:t},n,i)=>{let a;for(;e&&e!==t;)a=f(e),r(e,n,i),e=a;r(t,n,i)},b=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=f(e),i(e),e=n;i(t)},x=(e,t,n,r,i,a,o,s,c)=>{if(t.type===`svg`?o=`svg`:t.type===`math`&&(o=`mathml`),e==null)ee(t,n,r,i,a,o,s,c);else{let n=e.el&&e.el._isVueCE?e.el:null;try{n&&n._beginPatch(),w(e,t,i,a,o,s,c)}finally{n&&n._endPatch()}}},ee=(e,t,n,i,s,c,l,d)=>{let f,p,{props:m,shapeFlag:h,transition:g,dirs:_}=e;if(f=e.el=o(e.type,c,m&&m.is,m),h&8?u(f,e.children):h&16&&C(e.children,f,null,i,s,vs(e,c),l,d),_&&ii(e,null,i,`created`),S(f,e,e.scopeId,l,i),m){for(let e in m)e!==`value`&&!Le(e)&&a(f,e,null,m[e],c,i);`value`in m&&a(f,`value`,null,m.value,c),(p=m.onVnodeBeforeMount)&&sc(p,i,e)}_&&ii(e,null,i,`beforeMount`);let v=bs(s,g);v&&g.beforeEnter(f),r(f,t,n),((p=m&&m.onVnodeMounted)||v||_)&&ms(()=>{p&&sc(p,i,e),v&&g.enter(f),_&&ii(e,null,i,`mounted`)},s)},S=(e,t,n,r,i)=>{if(n&&p(e,n),r)for(let t=0;t{for(let l=c;l{let c=t.el=e.el,{patchFlag:l,dynamicChildren:d,dirs:f}=t;l|=e.patchFlag&16;let p=e.props||me,m=t.props||me,h;if(n&&ys(n,!1),(h=m.onVnodeBeforeUpdate)&&sc(h,n,t,e),f&&ii(t,e,n,`beforeUpdate`),n&&ys(n,!0),(p.innerHTML&&m.innerHTML==null||p.textContent&&m.textContent==null)&&u(c,``),d?te(e.dynamicChildren,d,c,n,r,vs(t,i),o):s||E(e,t,c,null,n,r,vs(t,i),o,!1),l>0){if(l&16)ne(c,p,m,n,i);else if(l&2&&p.class!==m.class&&a(c,`class`,null,m.class,i),l&4&&a(c,`style`,p.style,m.style,i),l&8){let e=t.dynamicProps;for(let t=0;t{h&&sc(h,n,t,e),f&&ii(t,e,n,`updated`)},r)},te=(e,t,n,r,i,a,o)=>{for(let s=0;s{if(t!==n){if(t!==me)for(let o in t)!Le(o)&&!(o in n)&&a(e,o,t[o],null,i,r);for(let o in n){if(Le(o))continue;let s=n[o],c=t[o];s!==c&&o!==`value`&&a(e,o,c,s,i,r)}`value`in n&&a(e,`value`,t.value,n.value,i)}},re=(e,t,n,i,a,o,c,l,u)=>{let d=t.el=e?e.el:s(``),f=t.anchor=e?e.anchor:s(``),{patchFlag:p,dynamicChildren:m,slotScopeIds:h}=t;h&&(l=l?l.concat(h):h),e==null?(r(d,n,i),r(f,n,i),C(t.children||[],n,f,a,o,c,l,u)):p>0&&p&64&&m&&e.dynamicChildren&&e.dynamicChildren.length===m.length?(te(e.dynamicChildren,m,n,a,o,c,l),(t.key!=null||a&&t===a.subTree)&&xs(e,t,!0)):E(e,t,n,f,a,o,c,l,u)},T=(e,t,n,r,i,a,o,s,c)=>{t.slotScopeIds=s,e==null?t.shapeFlag&512?i.ctx.activate(t,n,r,o,c):ie(t,n,r,i,a,o,c):ae(e,t,c)},ie=(e,t,n,r,i,a,o)=>{let s=e.component=uc(e,r,i);if(va(e)&&(s.ctx.renderer=xe),yc(s,!1,o),s.asyncDep){if(i&&i.registerDep(s,oe,o),!e.el){let r=s.subTree=V(Bs);_(null,r,t,n),e.placeholder=r.el}}else oe(s,e,t,n,i,a,o)},ae=(e,t,n)=>{let r=t.component=e.component;if(qo(e,t,n))if(r.asyncDep&&!r.asyncResolved){se(r,t,n);return}else r.next=t,r.update();else t.el=e.el,r.vnode=t},oe=(e,t,n,r,i,a,o)=>{let s=()=>{if(e.isMounted){let{next:t,bu:n,u:r,parent:c,vnode:l}=e;{let n=Cs(e);if(n){t&&(t.el=l.el,se(e,t,o)),n.asyncDep.then(()=>{e.isUnmounted||s()});return}}let u=t,f;ys(e,!1),t?(t.el=l.el,se(e,t,o)):t=l,n&&Ke(n),(f=t.props&&t.props.onVnodeBeforeUpdate)&&sc(f,c,t,l),ys(e,!0);let p=Uo(e),m=e.subTree;e.subTree=p,h(m,p,d(m.el),ve(m),e,i,a),t.el=p.el,u===null&&Yo(e,p.el),r&&ms(r,i),(f=t.props&&t.props.onVnodeUpdated)&&ms(()=>sc(f,c,t,l),i)}else{let o,{el:s,props:c}=t,{bm:l,m:u,parent:d,root:f,type:p}=e,m=ha(t);if(ys(e,!1),l&&Ke(l),!m&&(o=c&&c.onVnodeBeforeMount)&&sc(o,d,t),ys(e,!0),s&&Ce){let t=()=>{e.subTree=Uo(e),Ce(s,e.subTree,e,i,null)};m&&p.__asyncHydrate?p.__asyncHydrate(s,e,t):t()}else{f.ce&&f.ce._def.shadowRoot!==!1&&f.ce._injectChildStyle(p);let o=e.subTree=Uo(e);h(null,o,n,r,e,i,a),t.el=o.el}if(u&&ms(u,i),!m&&(o=c&&c.onVnodeMounted)){let e=t;ms(()=>sc(o,d,e),i)}(t.shapeFlag&256||d&&ha(d.vnode)&&d.vnode.shapeFlag&256)&&e.a&&ms(e.a,i),e.isMounted=!0,t=n=r=null}};e.scope.on();let c=e.effect=new Ct(s);e.scope.off();let l=e.update=c.run.bind(c),u=e.job=c.runIfDirty.bind(c);u.i=e,u.id=e.uid,c.scheduler=()=>Vr(u),ys(e,!0),l()},se=(e,t,n)=>{t.component=e;let r=e.vnode.props;e.vnode=t,e.next=null,es(e,t.props,r,n),ps(e,t.children,n),Bt(),Wr(e),Vt()},E=(e,t,n,r,i,a,o,s,c=!1)=>{let l=e&&e.children,d=e?e.shapeFlag:0,f=t.children,{patchFlag:p,shapeFlag:m}=t;if(p>0){if(p&128){le(l,f,n,r,i,a,o,s,c);return}else if(p&256){ce(l,f,n,r,i,a,o,s,c);return}}m&8?(d&16&&_e(l,i,a),f!==l&&u(n,f)):d&16?m&16?le(l,f,n,r,i,a,o,s,c):_e(l,i,a,!0):(d&8&&u(n,``),m&16&&C(f,n,r,i,a,o,s,c))},ce=(e,t,n,r,i,a,o,s,c)=>{e||=he,t||=he;let l=e.length,u=t.length,d=Math.min(l,u),f;for(f=0;fu?_e(e,i,a,!0,!1,d):C(t,n,r,i,a,o,s,c,d)},le=(e,t,n,r,i,a,o,s,c)=>{let l=0,u=t.length,d=e.length-1,f=u-1;for(;l<=d&&l<=f;){let r=e[l],u=t[l]=c?ac(t[l]):ic(t[l]);if(Ys(r,u))h(r,u,n,null,i,a,o,s,c);else break;l++}for(;l<=d&&l<=f;){let r=e[d],l=t[f]=c?ac(t[f]):ic(t[f]);if(Ys(r,l))h(r,l,n,null,i,a,o,s,c);else break;d--,f--}if(l>d){if(l<=f){let e=f+1,d=ef)for(;l<=d;)D(e[l],i,a,!0),l++;else{let p=l,m=l,g=new Map;for(l=m;l<=f;l++){let e=t[l]=c?ac(t[l]):ic(t[l]);e.key!=null&&g.set(e.key,l)}let _,v=0,y=f-m+1,b=!1,x=0,ee=Array(y);for(l=0;l=y){D(r,i,a,!0);continue}let u;if(r.key!=null)u=g.get(r.key);else for(_=m;_<=f;_++)if(ee[_-m]===0&&Ys(r,t[_])){u=_;break}u===void 0?D(r,i,a,!0):(ee[u-m]=l+1,u>=x?x=u:b=!0,h(r,t[u],n,null,i,a,o,s,c),v++)}let S=b?Ss(ee):he;for(_=S.length-1,l=y-1;l>=0;l--){let e=m+l,d=t[e],f=t[e+1],p=e+1{let{el:s,type:c,transition:l,children:u,shapeFlag:d}=e;if(d&6){ue(e.component.subTree,t,n,a);return}if(d&128){e.suspense.move(t,n,a);return}if(d&64){c.move(e,t,n,xe);return}if(c===I){r(s,t,n);for(let e=0;el.enter(s),o);else{let{leave:a,delayLeave:o,afterLeave:c}=l,u=()=>{e.ctx.isUnmounted?i(s):r(s,t,n)},d=()=>{s._isLeaving&&s[Ai](!0),a(s,()=>{u(),c&&c()})};o?o(s,u,d):d()}else r(s,t,n)},D=(e,t,n,r=!1,i=!1)=>{let{type:a,props:o,ref:s,children:c,dynamicChildren:l,shapeFlag:u,patchFlag:d,dirs:f,cacheIndex:p}=e;if(d===-2&&(i=!1),s!=null&&(Bt(),Yi(s,null,n,e,!0),Vt()),p!=null&&(t.renderCache[p]=void 0),u&256){t.ctx.deactivate(e);return}let m=u&1&&f,h=!ha(e),g;if(h&&(g=o&&o.onVnodeBeforeUnmount)&&sc(g,t,e),u&6)pe(e.component,n,r);else{if(u&128){e.suspense.unmount(n,r);return}m&&ii(e,null,t,`beforeUnmount`),u&64?e.type.remove(e,t,n,xe,r):l&&!l.hasOnce&&(a!==I||d>0&&d&64)?_e(l,t,n,!1,!0):(a===I&&d&384||!i&&u&16)&&_e(c,t,n),r&&de(e)}(h&&(g=o&&o.onVnodeUnmounted)||m)&&ms(()=>{g&&sc(g,t,e),m&&ii(e,null,t,`unmounted`)},n)},de=e=>{let{type:t,el:n,anchor:r,transition:a}=e;if(t===I){fe(n,r);return}if(t===Vs){b(e);return}let o=()=>{i(n),a&&!a.persisted&&a.afterLeave&&a.afterLeave()};if(e.shapeFlag&1&&a&&!a.persisted){let{leave:t,delayLeave:r}=a,i=()=>t(n,o);r?r(e.el,o,i):i()}else o()},fe=(e,t)=>{let n;for(;e!==t;)n=f(e),i(e),e=n;i(t)},pe=(e,t,n)=>{let{bum:r,scope:i,job:a,subTree:o,um:s,m:c,a:l}=e;ws(c),ws(l),r&&Ke(r),i.stop(),a&&(a.flags|=8,D(o,e,t,n)),s&&ms(s,t),ms(()=>{e.isUnmounted=!0},t)},_e=(e,t,n,r=!1,i=!1,a=0)=>{for(let o=a;o{if(e.shapeFlag&6)return ve(e.component.subTree);if(e.shapeFlag&128)return e.suspense.next();let t=f(e.anchor||e.el),n=t&&t[_i];return n?f(n):t},ye=!1,be=(e,t,n)=>{let r;e==null?t._vnode&&(D(t._vnode,null,null,!0),r=t._vnode.component):h(t._vnode||null,e,t,null,null,null,n),t._vnode=e,ye||=(ye=!0,Wr(r),Gr(),!1)},xe={p:h,um:D,m:ue,r:de,mt:ie,mc:C,pc:E,pbc:te,n:ve,o:e},Se,Ce;return t&&([Se,Ce]=t(xe)),{render:be,hydrate:Se,createApp:Fo(be,Se)}}function vs({type:e,props:t},n){return n===`svg`&&e===`foreignObject`||n===`mathml`&&e===`annotation-xml`&&t&&t.encoding&&t.encoding.includes(`html`)?void 0:n}function ys({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function bs(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function xs(e,t,n=!1){let r=e.children,i=t.children;if(O(r)&&O(i))for(let t=0;t>1,e[n[s]]0&&(t[r]=n[a-1]),n[a]=r)}}for(a=n.length,o=n[a-1];a-- >0;)n[a]=o,o=t[o];return n}function Cs(e){let t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Cs(t)}function ws(e){if(e)for(let t=0;te.__isSuspense,Ds=0,Os={name:`Suspense`,__isSuspense:!0,process(e,t,n,r,i,a,o,s,c,l){if(e==null)As(t,n,r,i,a,o,s,c,l);else{if(a&&a.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}js(e,t,n,r,i,o,s,c,l)}},hydrate:Ns,normalize:Ps};function ks(e,t){let n=e.props&&e.props[t];k(n)&&n()}function As(e,t,n,r,i,a,o,s,c){let{p:l,o:{createElement:u}}=c,d=u(`div`),f=e.suspense=Ms(e,i,r,t,d,n,a,o,s,c);l(null,f.pendingBranch=e.ssContent,d,null,r,f,a,o),f.deps>0?(ks(e,`onPending`),ks(e,`onFallback`),l(null,e.ssFallback,t,n,r,null,a,o),Ls(f,e.ssFallback)):f.resolve(!1,!0)}function js(e,t,n,r,i,a,o,s,{p:c,um:l,o:{createElement:u}}){let d=t.suspense=e.suspense;d.vnode=t,t.el=e.el;let f=t.ssContent,p=t.ssFallback,{activeBranch:m,pendingBranch:h,isInFallback:g,isHydrating:_}=d;if(h)d.pendingBranch=f,Ys(h,f)?(c(h,f,d.hiddenContainer,null,i,d,a,o,s),d.deps<=0?d.resolve():g&&(_||(c(m,p,n,r,i,null,a,o,s),Ls(d,p)))):(d.pendingId=Ds++,_?(d.isHydrating=!1,d.activeBranch=h):l(h,i,d),d.deps=0,d.effects.length=0,d.hiddenContainer=u(`div`),g?(c(null,f,d.hiddenContainer,null,i,d,a,o,s),d.deps<=0?d.resolve():(c(m,p,n,r,i,null,a,o,s),Ls(d,p))):m&&Ys(m,f)?(c(m,f,n,r,i,d,a,o,s),d.resolve(!0)):(c(null,f,d.hiddenContainer,null,i,d,a,o,s),d.deps<=0&&d.resolve()));else if(m&&Ys(m,f))c(m,f,n,r,i,d,a,o,s),Ls(d,f);else if(ks(t,`onPending`),d.pendingBranch=f,f.shapeFlag&512?d.pendingId=f.component.suspenseId:d.pendingId=Ds++,c(null,f,d.hiddenContainer,null,i,d,a,o,s),d.deps<=0)d.resolve();else{let{timeout:e,pendingId:t}=d;e>0?setTimeout(()=>{d.pendingId===t&&d.fallback(p)},e):e===0&&d.fallback(p)}}function Ms(e,t,n,r,i,a,o,s,c,l,u=!1){let{p:d,m:f,um:p,n:m,o:{parentNode:h,remove:g}}=l,_,v=Rs(e);v&&t&&t.pendingBranch&&(_=t.pendingId,t.deps++);let y=e.props?Ye(e.props.timeout):void 0,b=a,x={vnode:e,parent:t,parentComponent:n,namespace:o,container:r,hiddenContainer:i,deps:0,pendingId:Ds++,timeout:typeof y==`number`?y:-1,activeBranch:null,pendingBranch:null,isInFallback:!u,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1,n=!1){let{vnode:r,activeBranch:i,pendingBranch:o,pendingId:s,effects:c,parentComponent:l,container:u,isInFallback:d}=x,g=!1;x.isHydrating?x.isHydrating=!1:e||(g=i&&o.transition&&o.transition.mode===`out-in`,g&&(i.transition.afterLeave=()=>{s===x.pendingId&&(f(o,u,a===b?m(i):a,0),Ur(c),d&&r.ssFallback&&(r.ssFallback.el=null))}),i&&(h(i.el)===u&&(a=m(i)),p(i,l,x,!0),!g&&d&&r.ssFallback&&ms(()=>r.ssFallback.el=null,x)),g||f(o,u,a,0)),Ls(x,o),x.pendingBranch=null,x.isInFallback=!1;let y=x.parent,ee=!1;for(;y;){if(y.pendingBranch){y.effects.push(...c),ee=!0;break}y=y.parent}!ee&&!g&&Ur(c),x.effects=[],v&&t&&t.pendingBranch&&_===t.pendingId&&(t.deps--,t.deps===0&&!n&&t.resolve()),ks(r,`onResolve`)},fallback(e){if(!x.pendingBranch)return;let{vnode:t,activeBranch:n,parentComponent:r,container:i,namespace:a}=x;ks(t,`onFallback`);let o=m(n),l=()=>{x.isInFallback&&(d(null,e,i,o,r,null,a,s,c),Ls(x,e))},u=e.transition&&e.transition.mode===`out-in`;u&&(n.transition.afterLeave=l),x.isInFallback=!0,p(n,r,null,!0),u||l()},move(e,t,n){x.activeBranch&&f(x.activeBranch,e,t,n),x.container=e},next(){return x.activeBranch&&m(x.activeBranch)},registerDep(e,t,n){let r=!!x.pendingBranch;r&&x.deps++;let i=e.vnode.el;e.asyncDep.catch(t=>{Ar(t,e,0)}).then(a=>{if(e.isUnmounted||x.isUnmounted||x.pendingId!==e.suspenseId)return;e.asyncResolved=!0;let{vnode:s}=e;xc(e,a,!1),i&&(s.el=i);let c=!i&&e.subTree.el;t(e,s,h(i||e.subTree.el),i?null:m(e.subTree),x,o,n),c&&(s.placeholder=null,g(c)),Yo(e,s.el),r&&--x.deps===0&&x.resolve()})},unmount(e,t){x.isUnmounted=!0,x.activeBranch&&p(x.activeBranch,n,e,t),x.pendingBranch&&p(x.pendingBranch,n,e,t)}};return x}function Ns(e,t,n,r,i,a,o,s,c){let l=t.suspense=Ms(t,r,n,e.parentNode,document.createElement(`div`),null,i,a,o,s,!0),u=c(e,l.pendingBranch=t.ssContent,n,l,a,o);return l.deps===0&&l.resolve(!1,!0),u}function Ps(e){let{shapeFlag:t,children:n}=e,r=t&32;e.ssContent=Fs(r?n.default:n),e.ssFallback=r?Fs(n.fallback):V(Bs)}function Fs(e){let t;if(k(e)){let n=Gs&&e._c;n&&(e._d=!1,L()),e=e(),n&&(e._d=!0,t=Us,Ws())}return O(e)&&(e=Wo(e)),e=ic(e),t&&!e.dynamicChildren&&(e.dynamicChildren=t.filter(t=>t!==e)),e}function Is(e,t){t&&t.pendingBranch?O(e)?t.effects.push(...e):t.effects.push(e):Ur(e)}function Ls(e,t){e.activeBranch=t;let{vnode:n,parentComponent:r}=e,i=t.el;for(;!i&&t.component;)t=t.component.subTree,i=t.el;n.el=i,r&&r.subTree===n&&(r.vnode.el=i,Yo(r,i))}function Rs(e){let t=e.props&&e.props.suspensible;return t!=null&&t!==!1}var I=Symbol.for(`v-fgt`),zs=Symbol.for(`v-txt`),Bs=Symbol.for(`v-cmt`),Vs=Symbol.for(`v-stc`),Hs=[],Us=null;function L(e=!1){Hs.push(Us=e?null:[])}function Ws(){Hs.pop(),Us=Hs[Hs.length-1]||null}var Gs=1;function Ks(e,t=!1){Gs+=e,e<0&&Us&&t&&(Us.hasOnce=!0)}function qs(e){return e.dynamicChildren=Gs>0?Us||he:null,Ws(),Gs>0&&Us&&Us.push(e),e}function R(e,t,n,r,i,a){return qs(B(e,t,n,r,i,a,!0))}function z(e,t,n,r,i){return qs(V(e,t,n,r,i,!0))}function Js(e){return e?e.__v_isVNode===!0:!1}function Ys(e,t){return e.type===t.type&&e.key===t.key}function Xs(e){}var Zs=({key:e})=>e??null,Qs=({ref:e,ref_key:t,ref_for:n})=>(typeof e==`number`&&(e=``+e),e==null?null:Oe(e)||Xn(e)||k(e)?{i:Zr,r:e,k:t,f:!!n}:e);function B(e,t=null,n=null,r=0,i=null,a=e===I?0:1,o=!1,s=!1){let c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Zs(t),ref:t&&Qs(t),scopeId:Qr,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:a,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:Zr};return s?(oc(c,n),a&128&&e.normalize(c)):n&&(c.shapeFlag|=Oe(n)?8:16),Gs>0&&!o&&Us&&(c.patchFlag>0||a&6)&&c.patchFlag!==32&&Us.push(c),c}var V=$s;function $s(e,t=null,n=null,r=0,i=null,a=!1){if((!e||e===Ha)&&(e=Bs),Js(e)){let r=tc(e,t,!0);return n&&oc(r,n),Gs>0&&!a&&Us&&(r.shapeFlag&6?Us[Us.indexOf(e)]=r:Us.push(r)),r.patchFlag=-2,r}if(jc(e)&&(e=e.__vccOpts),t){t=ec(t);let{class:e,style:n}=t;e&&!Oe(e)&&(t.class=A(e)),Ae(n)&&(Gn(n)&&!O(n)&&(n=be({},n)),t.style=$e(n))}let o=Oe(e)?1:Es(e)?128:vi(e)?64:Ae(e)?4:k(e)?2:0;return B(e,t,n,r,i,o,a,!0)}function ec(e){return e?Gn(e)||Qo(e)?be({},e):e:null}function tc(e,t,n=!1,r=!1){let{props:i,ref:a,patchFlag:o,children:s,transition:c}=e,l=t?U(i||{},t):i,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Zs(l),ref:t&&t.ref?n&&a?O(a)?a.concat(Qs(t)):[a,Qs(t)]:Qs(t):a,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:s,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==I?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&tc(e.ssContent),ssFallback:e.ssFallback&&tc(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&r&&Ui(u,c.clone(u)),u}function nc(e=` `,t=0){return V(zs,null,e,t)}function rc(e,t){let n=V(Vs,null,e);return n.staticCount=t,n}function H(e=``,t=!1){return t?(L(),z(Bs,null,e)):V(Bs,null,e)}function ic(e){return e==null||typeof e==`boolean`?V(Bs):O(e)?V(I,null,e.slice()):Js(e)?ac(e):V(zs,null,String(e))}function ac(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:tc(e)}function oc(e,t){let n=0,{shapeFlag:r}=e;if(t==null)t=null;else if(O(t))n=16;else if(typeof t==`object`)if(r&65){let n=t.default;n&&(n._c&&(n._d=!1),oc(e,n()),n._c&&(n._d=!0));return}else{n=32;let r=t._;!r&&!Qo(t)?t._ctx=Zr:r===3&&Zr&&(Zr.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else k(t)?(t={default:t,_ctx:Zr},n=32):(t=String(t),r&64?(n=16,t=[nc(t)]):n=8);e.children=t,e.shapeFlag|=n}function U(...e){let t={};for(let n=0;ndc||Zr,pc,mc;{let e=Ze(),t=(t,n)=>{let r;return(r=e[t])||(r=e[t]=[]),r.push(n),e=>{r.length>1?r.forEach(t=>t(e)):r[0](e)}};pc=t(`__VUE_INSTANCE_SETTERS__`,e=>dc=e),mc=t(`__VUE_SSR_SETTERS__`,e=>vc=e)}var hc=e=>{let t=dc;return pc(e),e.scope.on(),()=>{e.scope.off(),pc(t)}},gc=()=>{dc&&dc.scope.off(),pc(null)};function _c(e){return e.vnode.shapeFlag&4}var vc=!1;function yc(e,t=!1,n=!1){t&&mc(t);let{props:r,children:i}=e.vnode,a=_c(e);$o(e,r,a,t),fs(e,i,n||t);let o=a?bc(e,t):void 0;return t&&mc(!1),o}function bc(e,t){let n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,eo);let{setup:r}=n;if(r){Bt();let n=e.setupContext=r.length>1?Oc(e):null,i=hc(e),a=Or(r,e,0,[e.props,n]),o=je(a);if(Vt(),i(),(o||e.sp)&&!ha(e)&&Ki(e),o){if(a.then(gc,gc),t)return a.then(n=>{xc(e,n,t)}).catch(t=>{Ar(t,e,0)});e.asyncDep=a}else xc(e,a,t)}else Ec(e,t)}function xc(e,t,n){k(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Ae(t)&&(e.setupState=rr(t)),Ec(e,n)}var Sc,Cc;function wc(e){Sc=e,Cc=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,to))}}var Tc=()=>!Sc;function Ec(e,t,n){let r=e.type;if(!e.render){if(!t&&Sc&&!r.render){let t=r.template||Co(e).template;if(t){let{isCustomElement:n,compilerOptions:i}=e.appContext.config,{delimiters:a,compilerOptions:o}=r,s=be(be({isCustomElement:n,delimiters:a},i),o);r.render=Sc(t,s)}}e.render=r.render||ge,Cc&&Cc(e)}{let t=hc(e);Bt();try{yo(e)}finally{Vt(),t()}}}var Dc={get(e,t){return Zt(e,`get`,``),e[t]}};function Oc(e){return{attrs:new Proxy(e.attrs,Dc),slots:e.slots,emit:e.emit,expose:t=>{e.exposed=t||{}}}}function kc(e){return e.exposed?e.exposeProxy||=new Proxy(rr(qn(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Qa)return Qa[n](e)},has(e,t){return t in e||t in Qa}}):e.proxy}function Ac(e,t=!0){return k(e)?e.displayName||e.name:e.name||t&&e.__name}function jc(e){return k(e)&&`__vccOpts`in e}var W=(e,t)=>fr(e,t,vc);function Mc(e,t,n){try{Ks(-1);let r=arguments.length;return r===2?Ae(t)&&!O(t)?Js(t)?V(e,null,[t]):V(e,t):V(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Js(n)&&(n=[n]),V(e,t,n))}finally{Ks(1)}}function Nc(){return;function e(t,n,r){let i=t[r];if(O(i)&&i.includes(n)||Ae(i)&&n in i||t.extends&&e(t.extends,n,r)||t.mixins&&t.mixins.some(t=>e(t,n,r)))return!0}}function Pc(e,t,n,r){let i=n[r];if(i&&Fc(i,e))return i;let a=t();return a.memo=e.slice(),a.cacheIndex=r,n[r]=a}function Fc(e,t){let n=e.memo;if(n.length!=t.length)return!1;for(let e=0;e0&&Us&&Us.push(e),!0}var Ic=`3.5.27`,Lc=ge,Rc=Dr,zc=Jr,Bc=Xr,Vc={createComponentInstance:uc,setupComponent:yc,renderComponentRoot:Uo,setCurrentRenderingInstance:$r,isVNode:Js,normalizeVNode:ic,getComponentPublicInstance:kc,ensureValidVNode:Ya,pushWarningContext:Cr,popWarningContext:wr},Hc=void 0,Uc=typeof window<`u`&&window.trustedTypes;if(Uc)try{Hc=Uc.createPolicy(`vue`,{createHTML:e=>e})}catch{}var Wc=Hc?e=>Hc.createHTML(e):e=>e,Gc=`http://www.w3.org/2000/svg`,Kc=`http://www.w3.org/1998/Math/MathML`,qc=typeof document<`u`?document:null,Jc=qc&&qc.createElement(`template`),Yc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{let t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{let i=t===`svg`?qc.createElementNS(Gc,e):t===`mathml`?qc.createElementNS(Kc,e):n?qc.createElement(e,{is:n}):qc.createElement(e);return e===`select`&&r&&r.multiple!=null&&i.setAttribute(`multiple`,r.multiple),i},createText:e=>qc.createTextNode(e),createComment:e=>qc.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>qc.querySelector(e),setScopeId(e,t){e.setAttribute(t,``)},insertStaticContent(e,t,n,r,i,a){let o=n?n.previousSibling:t.lastChild;if(i&&(i===a||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),!(i===a||!(i=i.nextSibling)););else{Jc.innerHTML=Wc(r===`svg`?`${e}`:r===`mathml`?`${e}`:e);let i=Jc.content;if(r===`svg`||r===`mathml`){let e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Xc=`transition`,Zc=`animation`,Qc=Symbol(`_vtc`),$c={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},el=be({},Pi,$c),tl=(e=>(e.displayName=`Transition`,e.props=el,e))((e,{slots:t})=>Mc(Ri,il(e),t)),nl=(e,t=[])=>{O(e)?e.forEach(e=>e(...t)):e&&e(...t)},rl=e=>e?O(e)?e.some(e=>e.length>1):e.length>1:!1;function il(e){let t={};for(let n in e)n in $c||(t[n]=e[n]);if(e.css===!1)return t;let{name:n=`v`,type:r,duration:i,enterFromClass:a=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:s=`${n}-enter-to`,appearFromClass:c=a,appearActiveClass:l=o,appearToClass:u=s,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:f=`${n}-leave-active`,leaveToClass:p=`${n}-leave-to`}=e,m=al(i),h=m&&m[0],g=m&&m[1],{onBeforeEnter:_,onEnter:v,onEnterCancelled:y,onLeave:b,onLeaveCancelled:x,onBeforeAppear:ee=_,onAppear:S=v,onAppearCancelled:C=y}=t,w=(e,t,n,r)=>{e._enterCancelled=r,cl(e,t?u:s),cl(e,t?l:o),n&&n()},te=(e,t)=>{e._isLeaving=!1,cl(e,d),cl(e,p),cl(e,f),t&&t()},ne=e=>(t,n)=>{let i=e?S:v,o=()=>w(t,e,n);nl(i,[t,o]),ll(()=>{cl(t,e?c:a),sl(t,e?u:s),rl(i)||dl(t,r,h,o)})};return be(t,{onBeforeEnter(e){nl(_,[e]),sl(e,a),sl(e,o)},onBeforeAppear(e){nl(ee,[e]),sl(e,c),sl(e,l)},onEnter:ne(!1),onAppear:ne(!0),onLeave(e,t){e._isLeaving=!0;let n=()=>te(e,t);sl(e,d),e._enterCancelled?(sl(e,f),hl(e)):(hl(e),sl(e,f)),ll(()=>{e._isLeaving&&(cl(e,d),sl(e,p),rl(b)||dl(e,r,g,n))}),nl(b,[e,n])},onEnterCancelled(e){w(e,!1,void 0,!0),nl(y,[e])},onAppearCancelled(e){w(e,!0,void 0,!0),nl(C,[e])},onLeaveCancelled(e){te(e),nl(x,[e])}})}function al(e){if(e==null)return null;if(Ae(e))return[ol(e.enter),ol(e.leave)];{let t=ol(e);return[t,t]}}function ol(e){return Ye(e)}function sl(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.add(t)),(e[Qc]||(e[Qc]=new Set)).add(t)}function cl(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.remove(t));let n=e[Qc];n&&(n.delete(t),n.size||(e[Qc]=void 0))}function ll(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}var ul=0;function dl(e,t,n,r){let i=e._endId=++ul,a=()=>{i===e._endId&&r()};if(n!=null)return setTimeout(a,n);let{type:o,timeout:s,propCount:c}=fl(e,t);if(!o)return r();let l=o+`end`,u=0,d=()=>{e.removeEventListener(l,f),a()},f=t=>{t.target===e&&++u>=c&&d()};setTimeout(()=>{u(n[e]||``).split(`, `),i=r(`${Xc}Delay`),a=r(`${Xc}Duration`),o=pl(i,a),s=r(`${Zc}Delay`),c=r(`${Zc}Duration`),l=pl(s,c),u=null,d=0,f=0;t===Xc?o>0&&(u=Xc,d=o,f=a.length):t===Zc?l>0&&(u=Zc,d=l,f=c.length):(d=Math.max(o,l),u=d>0?o>l?Xc:Zc:null,f=u?u===Xc?a.length:c.length:0);let p=u===Xc&&/\b(?:transform|all)(?:,|$)/.test(r(`${Xc}Property`).toString());return{type:u,timeout:d,propCount:f,hasTransform:p}}function pl(e,t){for(;e.lengthml(t)+ml(e[n])))}function ml(e){return e===`auto`?0:Number(e.slice(0,-1).replace(`,`,`.`))*1e3}function hl(e){return(e?e.ownerDocument:document).body.offsetHeight}function gl(e,t,n){let r=e[Qc];r&&(t=(t?[t,...r]:[...r]).join(` `)),t==null?e.removeAttribute(`class`):n?e.setAttribute(`class`,t):e.className=t}var _l=Symbol(`_vod`),vl=Symbol(`_vsh`),yl={name:`show`,beforeMount(e,{value:t},{transition:n}){e[_l]=e.style.display===`none`?``:e.style.display,n&&t?n.beforeEnter(e):bl(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),bl(e,!0),r.enter(e)):r.leave(e,()=>{bl(e,!1)}):bl(e,t))},beforeUnmount(e,{value:t}){bl(e,t)}};function bl(e,t){e.style.display=t?e[_l]:`none`,e[vl]=!t}function xl(){yl.getSSRProps=({value:e})=>{if(!e)return{style:{display:`none`}}}}var Sl=Symbol(``);function Cl(e){let t=fc();if(!t)return;let n=t.ut=(n=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(e=>Tl(e,n))},r=()=>{let r=e(t.proxy);t.ce?Tl(t.ce,r):wl(t.subTree,r),n(r)};ja(()=>{Ur(r)}),Aa(()=>{pi(r,ge,{flush:`post`});let e=new MutationObserver(r);e.observe(t.subTree.el.parentNode,{childList:!0}),Pa(()=>e.disconnect())})}function wl(e,t){if(e.shapeFlag&128){let n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{wl(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)Tl(e.el,t);else if(e.type===I)e.children.forEach(e=>wl(e,t));else if(e.type===Vs){let{el:n,anchor:r}=e;for(;n&&(Tl(n,t),n!==r);)n=n.nextSibling}}function Tl(e,t){if(e.nodeType===1){let n=e.style,r=``;for(let e in t){let i=ht(t[e]);n.setProperty(`--${e}`,i),r+=`--${e}: ${i};`}n[Sl]=r}}var El=/(?:^|;)\s*display\s*:/;function Dl(e,t,n){let r=e.style,i=Oe(n),a=!1;if(n&&!i){if(t)if(Oe(t))for(let e of t.split(`;`)){let t=e.slice(0,e.indexOf(`:`)).trim();n[t]??kl(r,t,``)}else for(let e in t)n[e]??kl(r,e,``);for(let e in n)e===`display`&&(a=!0),kl(r,e,n[e])}else if(i){if(t!==n){let e=r[Sl];e&&(n+=`;`+e),r.cssText=n,a=El.test(n)}}else t&&e.removeAttribute(`style`);_l in e&&(e[_l]=a?r.display:``,e[vl]&&(r.display=`none`))}var Ol=/\s*!important$/;function kl(e,t,n){if(O(n))n.forEach(n=>kl(e,t,n));else if(n??=``,t.startsWith(`--`))e.setProperty(t,n);else{let r=Ml(e,t);Ol.test(n)?e.setProperty(He(r),n.replace(Ol,``),`important`):e[r]=n}}var Al=[`Webkit`,`Moz`,`ms`],jl={};function Ml(e,t){let n=jl[t];if(n)return n;let r=Be(t);if(r!==`filter`&&r in e)return jl[t]=r;r=Ue(r);for(let n=0;nHl||=(Ul.then(()=>Hl=0),Date.now());function Gl(e,t){let n=e=>{if(!e._vts)e._vts=Date.now();else if(e._vts<=n.attached)return;kr(Kl(e,n.value),t,5,[e])};return n.value=e,n.attached=Wl(),n}function Kl(e,t){if(O(t)){let n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(e=>t=>!t._stopped&&e&&e(t))}else return t}var ql=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Jl=(e,t,n,r,i,a)=>{let o=i===`svg`;t===`class`?gl(e,r,o):t===`style`?Dl(e,n,r):ve(t)?ye(t)||zl(e,t,n,r,a):(t[0]===`.`?(t=t.slice(1),!0):t[0]===`^`?(t=t.slice(1),!1):Yl(e,t,r,o))?(Fl(e,t,r),!e.tagName.includes(`-`)&&(t===`value`||t===`checked`||t===`selected`)&&Pl(e,t,r,o,a,t!==`value`)):e._isVueCE&&(/[A-Z]/.test(t)||!Oe(r))?Fl(e,Be(t),r,a,t):(t===`true-value`?e._trueValue=r:t===`false-value`&&(e._falseValue=r),Pl(e,t,r,o))};function Yl(e,t,n,r){if(r)return!!(t===`innerHTML`||t===`textContent`||t in e&&ql(t)&&k(n));if(t===`spellcheck`||t===`draggable`||t===`translate`||t===`autocorrect`||t===`sandbox`&&e.tagName===`IFRAME`||t===`form`||t===`list`&&e.tagName===`INPUT`||t===`type`&&e.tagName===`TEXTAREA`)return!1;if(t===`width`||t===`height`){let t=e.tagName;if(t===`IMG`||t===`VIDEO`||t===`CANVAS`||t===`SOURCE`)return!1}return ql(t)&&Oe(n)?!1:t in e}var Xl={};function Zl(e,t,n){let r=P(e,t);Fe(r)&&(r=be({},r,t));class i extends eu{constructor(e){super(r,e,n)}}return i.def=r,i}var Ql=((e,t)=>Zl(e,t,Uu)),$l=typeof HTMLElement<`u`?HTMLElement:class{},eu=class e extends $l{constructor(e,t={},n=Hu){super(),this._def=e,this._props=t,this._createApp=n,this._isVueCE=!0,this._instance=null,this._app=null,this._nonce=this._def.nonce,this._connected=!1,this._resolved=!1,this._patching=!1,this._dirty=!1,this._numberProps=null,this._styleChildren=new WeakSet,this._ob=null,this.shadowRoot&&n!==Hu?this._root=this.shadowRoot:e.shadowRoot===!1?this._root=this:(this.attachShadow(be({},e.shadowRootOptions,{mode:`open`})),this._root=this.shadowRoot)}connectedCallback(){if(!this.isConnected)return;!this.shadowRoot&&!this._resolved&&this._parseSlots(),this._connected=!0;let t=this;for(;t&&=t.parentNode||t.host;)if(t instanceof e){this._parent=t;break}this._instance||(this._resolved?this._mount(this._def):t&&t._pendingResolve?this._pendingResolve=t._pendingResolve.then(()=>{this._pendingResolve=void 0,this._resolveDef()}):this._resolveDef())}_setParent(e=this._parent){e&&(this._instance.parent=e._instance,this._inheritParentContext(e))}_inheritParentContext(e=this._parent){e&&this._app&&Object.setPrototypeOf(this._app._context.provides,e._instance.provides)}disconnectedCallback(){this._connected=!1,zr(()=>{this._connected||(this._ob&&=(this._ob.disconnect(),null),this._app&&this._app.unmount(),this._instance&&(this._instance.ce=void 0),this._app=this._instance=null,this._teleportTargets&&=(this._teleportTargets.clear(),void 0))})}_processMutations(e){for(let t of e)this._setAttr(t.attributeName)}_resolveDef(){if(this._pendingResolve)return;for(let e=0;e{this._resolved=!0,this._pendingResolve=void 0;let{props:n,styles:r}=e,i;if(n&&!O(n))for(let e in n){let t=n[e];(t===Number||t&&t.type===Number)&&(e in this._props&&(this._props[e]=Ye(this._props[e])),(i||=Object.create(null))[Be(e)]=!0)}this._numberProps=i,this._resolveProps(e),this.shadowRoot&&this._applyStyles(r),this._mount(e)},t=this._def.__asyncLoader;t?this._pendingResolve=t().then(t=>{t.configureApp=this._def.configureApp,e(this._def=t,!0)}):e(this._def)}_mount(e){this._app=this._createApp(e),this._inheritParentContext(),e.configureApp&&e.configureApp(this._app),this._app._ceVNode=this._createVNode(),this._app.mount(this._root);let t=this._instance&&this._instance.exposed;if(t)for(let e in t)Ce(this,e)||Object.defineProperty(this,e,{get:()=>M(t[e])})}_resolveProps(e){let{props:t}=e,n=O(t)?t:Object.keys(t||{});for(let e of Object.keys(this))e[0]!==`_`&&n.includes(e)&&this._setProp(e,this[e]);for(let e of n.map(Be))Object.defineProperty(this,e,{get(){return this._getProp(e)},set(t){this._setProp(e,t,!0,!this._patching)}})}_setAttr(e){if(e.startsWith(`data-v-`))return;let t=this.hasAttribute(e),n=t?this.getAttribute(e):Xl,r=Be(e);t&&this._numberProps&&this._numberProps[r]&&(n=Ye(n)),this._setProp(r,n,!1,!0)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,r=!1){if(t!==this._props[e]&&(this._dirty=!0,t===Xl?delete this._props[e]:(this._props[e]=t,e===`key`&&this._app&&(this._app._ceVNode.key=t)),r&&this._instance&&this._update(),n)){let n=this._ob;n&&(this._processMutations(n.takeRecords()),n.disconnect()),t===!0?this.setAttribute(He(e),``):typeof t==`string`||typeof t==`number`?this.setAttribute(He(e),t+``):t||this.removeAttribute(He(e)),n&&n.observe(this,{attributes:!0})}}_update(){let e=this._createVNode();this._app&&(e.appContext=this._app._context),Bu(e,this._root)}_createVNode(){let e={};this.shadowRoot||(e.onVnodeMounted=e.onVnodeUpdated=this._renderSlots.bind(this));let t=V(this._def,be(e,this._props));return this._instance||(t.ce=e=>{this._instance=e,e.ce=this,e.isCE=!0;let t=(e,t)=>{this.dispatchEvent(new CustomEvent(e,Fe(t[0])?be({detail:t},t[0]):{detail:t}))};e.emit=(e,...n)=>{t(e,n),He(e)!==e&&t(He(e),n)},this._setParent()}),t}_applyStyles(e,t){if(!e)return;if(t){if(t===this._def||this._styleChildren.has(t))return;this._styleChildren.add(t)}let n=this._nonce;for(let t=e.length-1;t>=0;t--){let r=document.createElement(`style`);n&&r.setAttribute(`nonce`,n),r.textContent=e[t],this.shadowRoot.prepend(r)}}_parseSlots(){let e=this._slots={},t;for(;t=this.firstChild;){let n=t.nodeType===1&&t.getAttribute(`slot`)||`default`;(e[n]||(e[n]=[])).push(t),this.removeChild(t)}}_renderSlots(){let e=this._getSlots(),t=this._instance.type.__scopeId;for(let n=0;n(delete e.props.mode,e))({name:`TransitionGroup`,props:be({},el,{tag:String,moveClass:String}),setup(e,{slots:t}){let n=fc(),r=Mi(),i,a;return Ma(()=>{if(!i.length)return;let t=e.moveClass||`${e.name||`v`}-move`;if(!fu(i[0].el,n.vnode.el,t)){i=[];return}i.forEach(lu),i.forEach(uu);let r=i.filter(du);hl(n.vnode.el),r.forEach(e=>{let n=e.el,r=n.style;sl(n,t),r.transform=r.webkitTransform=r.transitionDuration=``;let i=n[ou]=e=>{e&&e.target!==n||(!e||e.propertyName.endsWith(`transform`))&&(n.removeEventListener(`transitionend`,i),n[ou]=null,cl(n,t))};n.addEventListener(`transitionend`,i)}),i=[]}),()=>{let o=Kn(e),s=il(o),c=o.tag||I;if(i=[],a)for(let e=0;e{e.split(/\s+/).forEach(e=>e&&r.classList.remove(e))}),n.split(/\s+/).forEach(e=>e&&r.classList.add(e)),r.style.display=`none`;let a=t.nodeType===1?t:t.parentNode;a.appendChild(r);let{hasTransform:o}=fl(r);return a.removeChild(r),o}var pu=e=>{let t=e.props[`onUpdate:modelValue`]||!1;return O(t)?e=>Ke(t,e):t};function mu(e){e.target.composing=!0}function hu(e){let t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event(`input`)))}var gu=Symbol(`_assign`);function _u(e,t,n){return t&&(e=e.trim()),n&&(e=Je(e)),e}var vu={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e[gu]=pu(i);let a=r||i.props&&i.props.type===`number`;Il(e,t?`change`:`input`,t=>{t.target.composing||e[gu](_u(e.value,n,a))}),(n||a)&&Il(e,`change`,()=>{e.value=_u(e.value,n,a)}),t||(Il(e,`compositionstart`,mu),Il(e,`compositionend`,hu),Il(e,`change`,hu))},mounted(e,{value:t}){e.value=t??``},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:a}},o){if(e[gu]=pu(o),e.composing)return;let s=(a||e.type===`number`)&&!/^0\d/.test(e.value)?Je(e.value):e.value,c=t??``;s!==c&&(document.activeElement===e&&e.type!==`range`&&(r&&t===n||i&&e.value.trim()===c)||(e.value=c))}},yu={deep:!0,created(e,t,n){e[gu]=pu(n),Il(e,`change`,()=>{let t=e._modelValue,n=wu(e),r=e.checked,i=e[gu];if(O(t)){let e=ut(t,n),a=e!==-1;if(r&&!a)i(t.concat(n));else if(!r&&a){let n=[...t];n.splice(e,1),i(n)}}else if(Te(t)){let e=new Set(t);r?e.add(n):e.delete(n),i(e)}else i(Tu(e,r))})},mounted:bu,beforeUpdate(e,t,n){e[gu]=pu(n),bu(e,t,n)}};function bu(e,{value:t,oldValue:n},r){e._modelValue=t;let i;if(O(t))i=ut(t,r.props.value)>-1;else if(Te(t))i=t.has(r.props.value);else{if(t===n)return;i=lt(t,Tu(e,!0))}e.checked!==i&&(e.checked=i)}var xu={created(e,{value:t},n){e.checked=lt(t,n.props.value),e[gu]=pu(n),Il(e,`change`,()=>{e[gu](wu(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[gu]=pu(r),t!==n&&(e.checked=lt(t,r.props.value))}},Su={deep:!0,created(e,{value:t,modifiers:{number:n}},r){let i=Te(t);Il(e,`change`,()=>{let t=Array.prototype.filter.call(e.options,e=>e.selected).map(e=>n?Je(wu(e)):wu(e));e[gu](e.multiple?i?new Set(t):t:t[0]),e._assigning=!0,zr(()=>{e._assigning=!1})}),e[gu]=pu(r)},mounted(e,{value:t}){Cu(e,t)},beforeUpdate(e,t,n){e[gu]=pu(n)},updated(e,{value:t}){e._assigning||Cu(e,t)}};function Cu(e,t){let n=e.multiple,r=O(t);if(!(n&&!r&&!Te(t))){for(let i=0,a=e.options.length;iString(e)===String(o)):a.selected=ut(t,o)>-1}else a.selected=t.has(o);else if(lt(wu(a),t)){e.selectedIndex!==i&&(e.selectedIndex=i);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function wu(e){return`_value`in e?e._value:e.value}function Tu(e,t){let n=t?`_trueValue`:`_falseValue`;return n in e?e[n]:t}var Eu={created(e,t,n){Ou(e,t,n,null,`created`)},mounted(e,t,n){Ou(e,t,n,null,`mounted`)},beforeUpdate(e,t,n,r){Ou(e,t,n,r,`beforeUpdate`)},updated(e,t,n,r){Ou(e,t,n,r,`updated`)}};function Du(e,t){switch(e){case`SELECT`:return Su;case`TEXTAREA`:return vu;default:switch(t){case`checkbox`:return yu;case`radio`:return xu;default:return vu}}}function Ou(e,t,n,r,i){let a=Du(e.tagName,n.props&&n.props.type)[i];a&&a(e,t,n,r)}function ku(){vu.getSSRProps=({value:e})=>({value:e}),xu.getSSRProps=({value:e},t)=>{if(t.props&<(t.props.value,e))return{checked:!0}},yu.getSSRProps=({value:e},t)=>{if(O(e)){if(t.props&&ut(e,t.props.value)>-1)return{checked:!0}}else if(Te(e)){if(t.props&&e.has(t.props.value))return{checked:!0}}else if(e)return{checked:!0}},Eu.getSSRProps=(e,t)=>{if(typeof t.type!=`string`)return;let n=Du(t.type.toUpperCase(),t.props&&t.props.type);if(n.getSSRProps)return n.getSSRProps(e,t)}}var Au=[`ctrl`,`shift`,`alt`,`meta`],ju={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>`button`in e&&e.button!==0,middle:e=>`button`in e&&e.button!==1,right:e=>`button`in e&&e.button!==2,exact:(e,t)=>Au.some(n=>e[`${n}Key`]&&!t.includes(n))},Mu=(e,t)=>{let n=e._withMods||={},r=t.join(`.`);return n[r]||(n[r]=((n,...r)=>{for(let e=0;e{let n=e._withKeys||={},r=t.join(`.`);return n[r]||(n[r]=(n=>{if(!(`key`in n))return;let r=He(n.key);if(t.some(e=>e===r||Nu[e]===r))return e(n)}))},Fu=be({patchProp:Jl},Yc),Iu,Lu=!1;function Ru(){return Iu||=hs(Fu)}function zu(){return Iu=Lu?Iu:gs(Fu),Lu=!0,Iu}var Bu=((...e)=>{Ru().render(...e)}),Vu=((...e)=>{zu().hydrate(...e)}),Hu=((...e)=>{let t=Ru().createApp(...e),{mount:n}=t;return t.mount=e=>{let r=Gu(e);if(!r)return;let i=t._component;!k(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent=``);let a=n(r,!1,Wu(r));return r instanceof Element&&(r.removeAttribute(`v-cloak`),r.setAttribute(`data-v-app`,``)),a},t}),Uu=((...e)=>{let t=zu().createApp(...e),{mount:n}=t;return t.mount=e=>{let t=Gu(e);if(t)return n(t,!0,Wu(t))},t});function Wu(e){if(e instanceof SVGElement)return`svg`;if(typeof MathMLElement==`function`&&e instanceof MathMLElement)return`mathml`}function Gu(e){return Oe(e)?document.querySelector(e):e}var Ku=!1,qu=()=>{Ku||(Ku=!0,ku(),xl())},Ju=n({BaseTransition:()=>Ri,BaseTransitionPropsValidators:()=>Pi,Comment:()=>Bs,DeprecationTypes:()=>null,EffectScope:()=>_t,ErrorCodes:()=>Er,ErrorTypeStrings:()=>Rc,Fragment:()=>I,KeepAlive:()=>ya,ReactiveEffect:()=>Ct,Static:()=>Vs,Suspense:()=>Os,Teleport:()=>Di,Text:()=>zs,TrackOpTypes:()=>pr,Transition:()=>tl,TransitionGroup:()=>cu,TriggerOpTypes:()=>mr,VueElement:()=>eu,assertNumber:()=>Tr,callWithAsyncErrorHandling:()=>kr,callWithErrorHandling:()=>Or,camelize:()=>Be,capitalize:()=>Ue,cloneVNode:()=>tc,compatUtils:()=>null,compile:()=>Yu,computed:()=>W,createApp:()=>Hu,createBlock:()=>z,createCommentVNode:()=>H,createElementBlock:()=>R,createElementVNode:()=>B,createHydrationRenderer:()=>gs,createPropsRestProxy:()=>go,createRenderer:()=>hs,createSSRApp:()=>Uu,createSlots:()=>Ja,createStaticVNode:()=>rc,createTextVNode:()=>nc,createVNode:()=>V,customRef:()=>ar,defineAsyncComponent:()=>ga,defineComponent:()=>P,defineCustomElement:()=>Zl,defineEmits:()=>ro,defineExpose:()=>io,defineModel:()=>so,defineOptions:()=>ao,defineProps:()=>no,defineSSRCustomElement:()=>Ql,defineSlots:()=>oo,devtools:()=>zc,effect:()=>It,effectScope:()=>vt,getCurrentInstance:()=>fc,getCurrentScope:()=>yt,getCurrentWatcher:()=>vr,getTransitionRawChildren:()=>Wi,guardReactiveProps:()=>ec,h:()=>Mc,handleError:()=>Ar,hasInjectionContext:()=>si,hydrate:()=>Vu,hydrateOnIdle:()=>la,hydrateOnInteraction:()=>pa,hydrateOnMediaQuery:()=>fa,hydrateOnVisible:()=>da,initCustomFormatter:()=>Nc,initDirectivesForSSR:()=>qu,inject:()=>oi,isMemoSame:()=>Fc,isProxy:()=>Gn,isReactive:()=>Hn,isReadonly:()=>Un,isRef:()=>Xn,isRuntimeOnly:()=>Tc,isShallow:()=>Wn,isVNode:()=>Js,markRaw:()=>qn,mergeDefaults:()=>mo,mergeModels:()=>ho,mergeProps:()=>U,nextTick:()=>zr,nodeOps:()=>Yc,normalizeClass:()=>A,normalizeProps:()=>it,normalizeStyle:()=>$e,onActivated:()=>xa,onBeforeMount:()=>ka,onBeforeUnmount:()=>Na,onBeforeUpdate:()=>ja,onDeactivated:()=>Sa,onErrorCaptured:()=>Ra,onMounted:()=>Aa,onRenderTracked:()=>La,onRenderTriggered:()=>Ia,onScopeDispose:()=>bt,onServerPrefetch:()=>Fa,onUnmounted:()=>Pa,onUpdated:()=>Ma,onWatcherCleanup:()=>yr,openBlock:()=>L,patchProp:()=>Jl,popScopeId:()=>ti,provide:()=>ai,proxyRefs:()=>rr,pushScopeId:()=>ei,queuePostFlushCb:()=>Ur,reactive:()=>Ln,readonly:()=>zn,ref:()=>j,registerRuntimeCompiler:()=>wc,render:()=>Bu,renderList:()=>qa,renderSlot:()=>F,resolveComponent:()=>Va,resolveDirective:()=>Wa,resolveDynamicComponent:()=>Ua,resolveFilter:()=>null,resolveTransitionHooks:()=>Bi,setBlockTracking:()=>Ks,setDevtoolsHook:()=>Bc,setTransitionHooks:()=>Ui,shallowReactive:()=>Rn,shallowReadonly:()=>Bn,shallowRef:()=>Zn,ssrContextKey:()=>ci,ssrUtils:()=>Vc,stop:()=>Lt,toDisplayString:()=>ft,toHandlerKey:()=>We,toHandlers:()=>Xa,toRaw:()=>Kn,toRef:()=>lr,toRefs:()=>or,toValue:()=>tr,transformVNodeArgs:()=>Xs,triggerRef:()=>er,unref:()=>M,useAttrs:()=>uo,useCssModule:()=>ru,useCssVars:()=>Cl,useHost:()=>tu,useId:()=>Gi,useModel:()=>Lo,useSSRContext:()=>li,useShadowRoot:()=>nu,useSlots:()=>lo,useTemplateRef:()=>qi,useTransitionState:()=>Mi,vModelCheckbox:()=>yu,vModelDynamic:()=>Eu,vModelRadio:()=>xu,vModelSelect:()=>Su,vModelText:()=>vu,vShow:()=>yl,version:()=>Ic,warn:()=>Lc,watch:()=>pi,watchEffect:()=>ui,watchPostEffect:()=>di,watchSyncEffect:()=>fi,withAsyncContext:()=>_o,withCtx:()=>N,withDefaults:()=>co,withDirectives:()=>ri,withKeys:()=>Pu,withMemo:()=>Pc,withModifiers:()=>Mu,withScopeId:()=>ni},1),Yu=()=>{},Xu=(e,t)=>Xn(t)?tr(t):t,Zu=`usehead`;function Qu(e){return{install(t){t.config.globalProperties.$unhead=e,t.config.globalProperties.$head=e,t.provide(Zu,e)}}.install}function $u(){if(si()){let e=oi(Zu);if(!e)throw Error(`useHead() was called without provide context, ensure you call it through the setup() function.`);return e}throw Error(`useHead() was called without provide context, ensure you call it through the setup() function.`)}function ed(e,t={}){let n=t.head||$u();return n.ssr?n.push(e||{},t):td(n,e,t)}function td(e,t,n={}){let r=j(!1),i;return ui(()=>{let a=r.value?{}:C(t,Xu);i?i.patch(a):i=e.push(a,n)}),fc()&&(Na(()=>{i.dispose()}),Sa(()=>{r.value=!0}),xa(()=>{r.value=!1})),i}function nd(e={}){let t=de({domOptions:{render:fe(()=>D(t),e=>setTimeout(e,0))},...e});return t.install=Qu(t),t}var rd={install(e){if(e._context.provides.usehead)return;let t=nd();e.use(t)}},id={inherit:`inherit`,current:`currentcolor`,transparent:`transparent`,black:`#000`,white:`#fff`,slate:{50:`oklch(98.4% 0.003 247.858)`,100:`oklch(96.8% 0.007 247.896)`,200:`oklch(92.9% 0.013 255.508)`,300:`oklch(86.9% 0.022 252.894)`,400:`oklch(70.4% 0.04 256.788)`,500:`oklch(55.4% 0.046 257.417)`,600:`oklch(44.6% 0.043 257.281)`,700:`oklch(37.2% 0.044 257.287)`,800:`oklch(27.9% 0.041 260.031)`,900:`oklch(20.8% 0.042 265.755)`,950:`oklch(12.9% 0.042 264.695)`},gray:{50:`oklch(98.5% 0.002 247.839)`,100:`oklch(96.7% 0.003 264.542)`,200:`oklch(92.8% 0.006 264.531)`,300:`oklch(87.2% 0.01 258.338)`,400:`oklch(70.7% 0.022 261.325)`,500:`oklch(55.1% 0.027 264.364)`,600:`oklch(44.6% 0.03 256.802)`,700:`oklch(37.3% 0.034 259.733)`,800:`oklch(27.8% 0.033 256.848)`,900:`oklch(21% 0.034 264.665)`,950:`oklch(13% 0.028 261.692)`},zinc:{50:`oklch(98.5% 0 0)`,100:`oklch(96.7% 0.001 286.375)`,200:`oklch(92% 0.004 286.32)`,300:`oklch(87.1% 0.006 286.286)`,400:`oklch(70.5% 0.015 286.067)`,500:`oklch(55.2% 0.016 285.938)`,600:`oklch(44.2% 0.017 285.786)`,700:`oklch(37% 0.013 285.805)`,800:`oklch(27.4% 0.006 286.033)`,900:`oklch(21% 0.006 285.885)`,950:`oklch(14.1% 0.005 285.823)`},neutral:{50:`oklch(98.5% 0 0)`,100:`oklch(97% 0 0)`,200:`oklch(92.2% 0 0)`,300:`oklch(87% 0 0)`,400:`oklch(70.8% 0 0)`,500:`oklch(55.6% 0 0)`,600:`oklch(43.9% 0 0)`,700:`oklch(37.1% 0 0)`,800:`oklch(26.9% 0 0)`,900:`oklch(20.5% 0 0)`,950:`oklch(14.5% 0 0)`},stone:{50:`oklch(98.5% 0.001 106.423)`,100:`oklch(97% 0.001 106.424)`,200:`oklch(92.3% 0.003 48.717)`,300:`oklch(86.9% 0.005 56.366)`,400:`oklch(70.9% 0.01 56.259)`,500:`oklch(55.3% 0.013 58.071)`,600:`oklch(44.4% 0.011 73.639)`,700:`oklch(37.4% 0.01 67.558)`,800:`oklch(26.8% 0.007 34.298)`,900:`oklch(21.6% 0.006 56.043)`,950:`oklch(14.7% 0.004 49.25)`},red:{50:`oklch(97.1% 0.013 17.38)`,100:`oklch(93.6% 0.032 17.717)`,200:`oklch(88.5% 0.062 18.334)`,300:`oklch(80.8% 0.114 19.571)`,400:`oklch(70.4% 0.191 22.216)`,500:`oklch(63.7% 0.237 25.331)`,600:`oklch(57.7% 0.245 27.325)`,700:`oklch(50.5% 0.213 27.518)`,800:`oklch(44.4% 0.177 26.899)`,900:`oklch(39.6% 0.141 25.723)`,950:`oklch(25.8% 0.092 26.042)`},orange:{50:`oklch(98% 0.016 73.684)`,100:`oklch(95.4% 0.038 75.164)`,200:`oklch(90.1% 0.076 70.697)`,300:`oklch(83.7% 0.128 66.29)`,400:`oklch(75% 0.183 55.934)`,500:`oklch(70.5% 0.213 47.604)`,600:`oklch(64.6% 0.222 41.116)`,700:`oklch(55.3% 0.195 38.402)`,800:`oklch(47% 0.157 37.304)`,900:`oklch(40.8% 0.123 38.172)`,950:`oklch(26.6% 0.079 36.259)`},amber:{50:`oklch(98.7% 0.022 95.277)`,100:`oklch(96.2% 0.059 95.617)`,200:`oklch(92.4% 0.12 95.746)`,300:`oklch(87.9% 0.169 91.605)`,400:`oklch(82.8% 0.189 84.429)`,500:`oklch(76.9% 0.188 70.08)`,600:`oklch(66.6% 0.179 58.318)`,700:`oklch(55.5% 0.163 48.998)`,800:`oklch(47.3% 0.137 46.201)`,900:`oklch(41.4% 0.112 45.904)`,950:`oklch(27.9% 0.077 45.635)`},yellow:{50:`oklch(98.7% 0.026 102.212)`,100:`oklch(97.3% 0.071 103.193)`,200:`oklch(94.5% 0.129 101.54)`,300:`oklch(90.5% 0.182 98.111)`,400:`oklch(85.2% 0.199 91.936)`,500:`oklch(79.5% 0.184 86.047)`,600:`oklch(68.1% 0.162 75.834)`,700:`oklch(55.4% 0.135 66.442)`,800:`oklch(47.6% 0.114 61.907)`,900:`oklch(42.1% 0.095 57.708)`,950:`oklch(28.6% 0.066 53.813)`},lime:{50:`oklch(98.6% 0.031 120.757)`,100:`oklch(96.7% 0.067 122.328)`,200:`oklch(93.8% 0.127 124.321)`,300:`oklch(89.7% 0.196 126.665)`,400:`oklch(84.1% 0.238 128.85)`,500:`oklch(76.8% 0.233 130.85)`,600:`oklch(64.8% 0.2 131.684)`,700:`oklch(53.2% 0.157 131.589)`,800:`oklch(45.3% 0.124 130.933)`,900:`oklch(40.5% 0.101 131.063)`,950:`oklch(27.4% 0.072 132.109)`},green:{50:`oklch(98.2% 0.018 155.826)`,100:`oklch(96.2% 0.044 156.743)`,200:`oklch(92.5% 0.084 155.995)`,300:`oklch(87.1% 0.15 154.449)`,400:`oklch(79.2% 0.209 151.711)`,500:`oklch(72.3% 0.219 149.579)`,600:`oklch(62.7% 0.194 149.214)`,700:`oklch(52.7% 0.154 150.069)`,800:`oklch(44.8% 0.119 151.328)`,900:`oklch(39.3% 0.095 152.535)`,950:`oklch(26.6% 0.065 152.934)`},emerald:{50:`oklch(97.9% 0.021 166.113)`,100:`oklch(95% 0.052 163.051)`,200:`oklch(90.5% 0.093 164.15)`,300:`oklch(84.5% 0.143 164.978)`,400:`oklch(76.5% 0.177 163.223)`,500:`oklch(69.6% 0.17 162.48)`,600:`oklch(59.6% 0.145 163.225)`,700:`oklch(50.8% 0.118 165.612)`,800:`oklch(43.2% 0.095 166.913)`,900:`oklch(37.8% 0.077 168.94)`,950:`oklch(26.2% 0.051 172.552)`},teal:{50:`oklch(98.4% 0.014 180.72)`,100:`oklch(95.3% 0.051 180.801)`,200:`oklch(91% 0.096 180.426)`,300:`oklch(85.5% 0.138 181.071)`,400:`oklch(77.7% 0.152 181.912)`,500:`oklch(70.4% 0.14 182.503)`,600:`oklch(60% 0.118 184.704)`,700:`oklch(51.1% 0.096 186.391)`,800:`oklch(43.7% 0.078 188.216)`,900:`oklch(38.6% 0.063 188.416)`,950:`oklch(27.7% 0.046 192.524)`},cyan:{50:`oklch(98.4% 0.019 200.873)`,100:`oklch(95.6% 0.045 203.388)`,200:`oklch(91.7% 0.08 205.041)`,300:`oklch(86.5% 0.127 207.078)`,400:`oklch(78.9% 0.154 211.53)`,500:`oklch(71.5% 0.143 215.221)`,600:`oklch(60.9% 0.126 221.723)`,700:`oklch(52% 0.105 223.128)`,800:`oklch(45% 0.085 224.283)`,900:`oklch(39.8% 0.07 227.392)`,950:`oklch(30.2% 0.056 229.695)`},sky:{50:`oklch(97.7% 0.013 236.62)`,100:`oklch(95.1% 0.026 236.824)`,200:`oklch(90.1% 0.058 230.902)`,300:`oklch(82.8% 0.111 230.318)`,400:`oklch(74.6% 0.16 232.661)`,500:`oklch(68.5% 0.169 237.323)`,600:`oklch(58.8% 0.158 241.966)`,700:`oklch(50% 0.134 242.749)`,800:`oklch(44.3% 0.11 240.79)`,900:`oklch(39.1% 0.09 240.876)`,950:`oklch(29.3% 0.066 243.157)`},blue:{50:`oklch(97% 0.014 254.604)`,100:`oklch(93.2% 0.032 255.585)`,200:`oklch(88.2% 0.059 254.128)`,300:`oklch(80.9% 0.105 251.813)`,400:`oklch(70.7% 0.165 254.624)`,500:`oklch(62.3% 0.214 259.815)`,600:`oklch(54.6% 0.245 262.881)`,700:`oklch(48.8% 0.243 264.376)`,800:`oklch(42.4% 0.199 265.638)`,900:`oklch(37.9% 0.146 265.522)`,950:`oklch(28.2% 0.091 267.935)`},indigo:{50:`oklch(96.2% 0.018 272.314)`,100:`oklch(93% 0.034 272.788)`,200:`oklch(87% 0.065 274.039)`,300:`oklch(78.5% 0.115 274.713)`,400:`oklch(67.3% 0.182 276.935)`,500:`oklch(58.5% 0.233 277.117)`,600:`oklch(51.1% 0.262 276.966)`,700:`oklch(45.7% 0.24 277.023)`,800:`oklch(39.8% 0.195 277.366)`,900:`oklch(35.9% 0.144 278.697)`,950:`oklch(25.7% 0.09 281.288)`},violet:{50:`oklch(96.9% 0.016 293.756)`,100:`oklch(94.3% 0.029 294.588)`,200:`oklch(89.4% 0.057 293.283)`,300:`oklch(81.1% 0.111 293.571)`,400:`oklch(70.2% 0.183 293.541)`,500:`oklch(60.6% 0.25 292.717)`,600:`oklch(54.1% 0.281 293.009)`,700:`oklch(49.1% 0.27 292.581)`,800:`oklch(43.2% 0.232 292.759)`,900:`oklch(38% 0.189 293.745)`,950:`oklch(28.3% 0.141 291.089)`},purple:{50:`oklch(97.7% 0.014 308.299)`,100:`oklch(94.6% 0.033 307.174)`,200:`oklch(90.2% 0.063 306.703)`,300:`oklch(82.7% 0.119 306.383)`,400:`oklch(71.4% 0.203 305.504)`,500:`oklch(62.7% 0.265 303.9)`,600:`oklch(55.8% 0.288 302.321)`,700:`oklch(49.6% 0.265 301.924)`,800:`oklch(43.8% 0.218 303.724)`,900:`oklch(38.1% 0.176 304.987)`,950:`oklch(29.1% 0.149 302.717)`},fuchsia:{50:`oklch(97.7% 0.017 320.058)`,100:`oklch(95.2% 0.037 318.852)`,200:`oklch(90.3% 0.076 319.62)`,300:`oklch(83.3% 0.145 321.434)`,400:`oklch(74% 0.238 322.16)`,500:`oklch(66.7% 0.295 322.15)`,600:`oklch(59.1% 0.293 322.896)`,700:`oklch(51.8% 0.253 323.949)`,800:`oklch(45.2% 0.211 324.591)`,900:`oklch(40.1% 0.17 325.612)`,950:`oklch(29.3% 0.136 325.661)`},pink:{50:`oklch(97.1% 0.014 343.198)`,100:`oklch(94.8% 0.028 342.258)`,200:`oklch(89.9% 0.061 343.231)`,300:`oklch(82.3% 0.12 346.018)`,400:`oklch(71.8% 0.202 349.761)`,500:`oklch(65.6% 0.241 354.308)`,600:`oklch(59.2% 0.249 0.584)`,700:`oklch(52.5% 0.223 3.958)`,800:`oklch(45.9% 0.187 3.815)`,900:`oklch(40.8% 0.153 2.432)`,950:`oklch(28.4% 0.109 3.907)`},rose:{50:`oklch(96.9% 0.015 12.422)`,100:`oklch(94.1% 0.03 12.58)`,200:`oklch(89.2% 0.058 10.001)`,300:`oklch(81% 0.117 11.638)`,400:`oklch(71.2% 0.194 13.428)`,500:`oklch(64.5% 0.246 16.439)`,600:`oklch(58.6% 0.253 17.585)`,700:`oklch(51.4% 0.222 16.935)`,800:`oklch(45.5% 0.188 13.697)`,900:`oklch(41% 0.159 10.272)`,950:`oklch(27.1% 0.105 12.094)`}};function ad(e,t){return yt()?(bt(e,t),!0):!1}var od=new WeakMap,sd=(...e)=>{let t=e[0],n=fc()?.proxy??yt();if(n==null&&!si())throw Error(`injectLocal must be called in setup`);return n&&od.has(n)&&t in od.get(n)?od.get(n)[t]:oi(...e)},cd=typeof window<`u`&&typeof document<`u`;typeof WorkerGlobalScope<`u`&&globalThis instanceof WorkerGlobalScope;var ld=e=>e!==void 0,ud=e=>e!=null,dd=Object.prototype.toString,fd=e=>dd.call(e)===`[object Object]`,pd=()=>{};function md(...e){if(e.length!==1)return lr(...e);let t=e[0];return typeof t==`function`?zn(ar(()=>({get:t,set:pd}))):j(t)}function hd(e,t){function n(...n){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,n),{fn:t,thisArg:this,args:n})).then(r).catch(i)})}return n}var gd=e=>e();function _d(e,t={}){let n,r,i=pd,a=e=>{clearTimeout(e),i(),i=pd},o;return s=>{let c=tr(e),l=tr(t.maxWait);return n&&a(n),c<=0||l!==void 0&&l<=0?(r&&=(a(r),void 0),Promise.resolve(s())):new Promise((e,u)=>{i=t.rejectOnCancel?u:e,o=s,l&&!r&&(r=setTimeout(()=>{n&&a(n),r=void 0,e(o())},l)),n=setTimeout(()=>{r&&a(r),r=void 0,e(s())},c)})}}function vd(e=gd,t={}){let{initialState:n=`active`}=t,r=md(n===`active`);function i(){r.value=!1}function a(){r.value=!0}return{isActive:zn(r),pause:i,resume:a,eventFilter:(...t)=>{r.value&&e(...t)}}}function yd(e){let t;function n(){return t||=e(),t}return n.reset=async()=>{let e=t;t=void 0,e&&await e},n}function bd(e){return e.endsWith(`rem`)?Number.parseFloat(e)*16:Number.parseFloat(e)}function xd(e){return Array.isArray(e)?e:[e]}function Sd(e){let t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))}var Cd=/-(\w)/g,wd=Sd(e=>e.replace(Cd,(e,t)=>t?t.toUpperCase():``));function Td(e){return e||fc()}function Ed(e){if(!cd)return e;let t=0,n,r,i=()=>{--t,r&&t<=0&&(r.stop(),n=void 0,r=void 0)};return((...a)=>(t+=1,r||(r=vt(!0),n=r.run(()=>e(...a))),ad(i),n))}function Dd(e,t){if(typeof Symbol<`u`){let n={...e};return Object.defineProperty(n,Symbol.iterator,{enumerable:!1,value(){let e=0;return{next:()=>({value:t[e++],done:e>t.length})}}}),n}else return Object.assign([...t],e)}function Od(e){return Ln(Xn(e)?new Proxy({},{get(t,n,r){return M(Reflect.get(e.value,n,r))},set(t,n,r){return Xn(e.value[n])&&!Xn(r)?e.value[n].value=r:e.value[n]=r,!0},deleteProperty(t,n){return Reflect.deleteProperty(e.value,n)},has(t,n){return Reflect.has(e.value,n)},ownKeys(){return Object.keys(e.value)},getOwnPropertyDescriptor(){return{enumerable:!0,configurable:!0}}}):e)}function kd(e){return Od(W(e))}function Ad(e,...t){let n=t.flat(),r=n[0];return kd(()=>typeof r==`function`?Object.fromEntries(Object.entries(or(e)).filter(([e,t])=>!r(tr(t),e))):Object.fromEntries(Object.entries(or(e)).filter(e=>!n.includes(e[0]))))}function jd(e,...t){let n=t.flat(),r=n[0];return kd(()=>typeof r==`function`?Object.fromEntries(Object.entries(or(e)).filter(([e,t])=>r(tr(t),e))):Object.fromEntries(n.map(t=>[t,md(e,t)])))}function Md(e,t=200,n={}){return hd(_d(t,n),e)}function Nd(e,t,n={}){let{eventFilter:r=gd,...i}=n;return pi(e,hd(r,t),i)}function Pd(e,t,n={}){let{eventFilter:r,initialState:i=`active`,...a}=n,{eventFilter:o,pause:s,resume:c,isActive:l}=vd(r,{initialState:i});return{stop:Nd(e,t,{...a,eventFilter:o}),pause:s,resume:c,isActive:l}}var Fd=Pd;function Id(e,t=!0,n){Td(n)?Aa(e,n):t?e():zr(e)}function Ld(e,t,n={}){let{immediate:r=!0,immediateCallback:i=!1}=n,a=Zn(!1),o;function s(){o&&=(clearTimeout(o),void 0)}function c(){a.value=!1,s()}function l(...n){i&&e(),s(),a.value=!0,o=setTimeout(()=>{a.value=!1,o=void 0,e(...n)},tr(t))}return r&&(a.value=!0,cd&&l()),ad(c),{isPending:Bn(a),start:l,stop:c}}function Rd(e,t,n){return pi(e,t,{...n,immediate:!0})}function zd(e={}){let{inheritAttrs:t=!0}=e,n=Zn(),r=P({setup(e,{slots:t}){return()=>{n.value=t.default}}}),i=P({inheritAttrs:t,props:e.props,setup(r,{attrs:i,slots:a}){return()=>{if(!n.value)throw Error(`[VueUse] Failed to find the definition of reusable template`);let o=n.value?.call(n,{...e.props==null?Bd(i):r,$slots:a});return t&&o?.length===1?o[0]:o}}});return Dd({define:r,reuse:i},[r,i])}function Bd(e){let t={};for(let n in e)t[wd(n)]=e[n];return t}var Vd=cd?window:void 0,Hd=cd?window.document:void 0,Ud=cd?window.navigator:void 0;cd&&window.location;function Wd(e){let t=tr(e);return t?.$el??t}function Gd(...e){let t=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),n=W(()=>{let t=xd(tr(e[0])).filter(e=>e!=null);return t.every(e=>typeof e!=`string`)?t:void 0});return Rd(()=>[n.value?.map(e=>Wd(e))??[Vd].filter(e=>e!=null),xd(tr(n.value?e[1]:e[0])),xd(M(n.value?e[2]:e[1])),tr(n.value?e[3]:e[2])],([e,n,r,i],a,o)=>{if(!e?.length||!n?.length||!r?.length)return;let s=fd(i)?{...i}:i,c=e.flatMap(e=>n.flatMap(n=>r.map(r=>t(e,n,r,s))));o(()=>{c.forEach(e=>e())})},{flush:`post`})}function Kd(){let e=Zn(!1),t=fc();return t&&Aa(()=>{e.value=!0},t),e}function qd(e){let t=Kd();return W(()=>(t.value,!!e()))}function Jd(e,t,n={}){let{window:r=Vd,...i}=n,a,o=qd(()=>r&&`MutationObserver`in r),s=()=>{a&&=(a.disconnect(),void 0)},c=pi(W(()=>{let t=xd(tr(e)).map(Wd).filter(ud);return new Set(t)}),e=>{s(),o.value&&e.size&&(a=new MutationObserver(t),e.forEach(e=>a.observe(e,i)))},{immediate:!0,flush:`post`}),l=()=>a?.takeRecords(),u=()=>{c(),s()};return ad(u),{isSupported:o,stop:u,takeRecords:l}}var Yd=Symbol(`vueuse-ssr-width`);function Xd(){let e=si()?sd(Yd,null):null;return typeof e==`number`?e:void 0}function Zd(e,t={}){let{window:n=Vd,ssrWidth:r=Xd()}=t,i=qd(()=>n&&`matchMedia`in n&&typeof n.matchMedia==`function`),a=Zn(typeof r==`number`),o=Zn(),s=Zn(!1);return ui(()=>{if(a.value){a.value=!i.value,s.value=tr(e).split(`,`).some(e=>{let t=e.includes(`not all`),n=e.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),i=e.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),a=!!(n||i);return n&&a&&(a=r>=bd(n[1])),i&&a&&(a=r<=bd(i[1])),t?!a:a});return}i.value&&(o.value=n.matchMedia(tr(e)),s.value=o.value.matches)}),Gd(o,`change`,e=>{s.value=e.matches},{passive:!0}),W(()=>s.value)}function Qd(e,t={}){let{controls:n=!1,navigator:r=Ud}=t,i=qd(()=>r&&`permissions`in r),a=Zn(),o=typeof e==`string`?{name:e}:e,s=Zn(),c=()=>{s.value=a.value?.state??`prompt`};Gd(a,`change`,c,{passive:!0});let l=yd(async()=>{if(i.value){if(!a.value)try{a.value=await r.permissions.query(o)}catch{a.value=void 0}finally{c()}if(n)return Kn(a.value)}});return l(),n?{state:s,isSupported:i,query:l}:s}function $d(e={}){let{navigator:t=Ud,read:n=!1,source:r,copiedDuring:i=1500,legacy:a=!1}=e,o=qd(()=>t&&`clipboard`in t),s=Qd(`clipboard-read`),c=Qd(`clipboard-write`),l=W(()=>o.value||a),u=Zn(``),d=Zn(!1),f=Ld(()=>d.value=!1,i,{immediate:!1});async function p(){let e=!(o.value&&_(s.value));if(!e)try{u.value=await t.clipboard.readText()}catch{e=!0}e&&(u.value=g())}l.value&&n&&Gd([`copy`,`cut`],p,{passive:!0});async function m(e=tr(r)){if(l.value&&e!=null){let n=!(o.value&&_(c.value));if(!n)try{await t.clipboard.writeText(e)}catch{n=!0}n&&h(e),u.value=e,d.value=!0,f.start()}}function h(e){let t=document.createElement(`textarea`);t.value=e,t.style.position=`absolute`,t.style.opacity=`0`,t.setAttribute(`readonly`,``),document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}function g(){var e,t;return((e=document)==null||(t=e.getSelection)==null||(t=t.call(e))==null?void 0:t.toString())??``}function _(e){return e===`granted`||e===`prompt`}return{isSupported:l,text:zn(u),copied:zn(d),copy:m}}function ef(e){return JSON.parse(JSON.stringify(e))}var tf=typeof globalThis<`u`?globalThis:typeof window<`u`?window:typeof global<`u`?global:typeof self<`u`?self:{},nf=`__vueuse_ssr_handlers__`,rf=af();function af(){return nf in tf||(tf[nf]=tf[nf]||{}),tf[nf]}function of(e,t){return rf[e]||t}function sf(e){return Zd(`(prefers-color-scheme: dark)`,e)}function cf(e){return e==null?`any`:e instanceof Set?`set`:e instanceof Map?`map`:e instanceof Date?`date`:typeof e==`boolean`?`boolean`:typeof e==`string`?`string`:typeof e==`object`?`object`:Number.isNaN(e)?`any`:`number`}var lf={boolean:{read:e=>e===`true`,write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},uf=`vueuse-storage`;function df(e,t,n,r={}){let{flush:i=`pre`,deep:a=!0,listenToStorageChanges:o=!0,writeDefaults:s=!0,mergeDefaults:c=!1,shallow:l,window:u=Vd,eventFilter:d,onError:f=e=>{console.error(e)},initOnMounted:p}=r,m=(l?Zn:j)(typeof t==`function`?t():t),h=W(()=>tr(e));if(!n)try{n=of(`getDefaultStorage`,()=>Vd?.localStorage)()}catch(e){f(e)}if(!n)return m;let g=tr(t),_=cf(g),v=r.serializer??lf[_],{pause:y,resume:b}=Fd(m,e=>S(e),{flush:i,deep:a,eventFilter:d});pi(h,()=>w(),{flush:i});let x=!1;u&&o&&(n instanceof Storage?Gd(u,`storage`,e=>{p&&!x||w(e)},{passive:!0}):Gd(u,uf,e=>{p&&!x||te(e)})),p?Id(()=>{x=!0,w()}):w();function ee(e,t){if(u){let r={key:h.value,oldValue:e,newValue:t,storageArea:n};u.dispatchEvent(n instanceof Storage?new StorageEvent(`storage`,r):new CustomEvent(uf,{detail:r}))}}function S(e){try{let t=n.getItem(h.value);if(e==null)ee(t,null),n.removeItem(h.value);else{let r=v.write(e);t!==r&&(n.setItem(h.value,r),ee(t,r))}}catch(e){f(e)}}function C(e){let t=e?e.newValue:n.getItem(h.value);if(t==null)return s&&g!=null&&n.setItem(h.value,v.write(g)),g;if(!e&&c){let e=v.read(t);return typeof c==`function`?c(e,g):_===`object`&&!Array.isArray(e)?{...g,...e}:e}else if(typeof t!=`string`)return t;else return v.read(t)}function w(e){if(!(e&&e.storageArea!==n)){if(e&&e.key==null){m.value=g;return}if(!(e&&e.key!==h.value)){y();try{let t=v.write(m.value);(e===void 0||e?.newValue!==t)&&(m.value=C(e))}catch(e){f(e)}finally{e?zr(b):b()}}}}function te(e){w(e.detail)}return m}var ff=`*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`;function pf(e={}){let{selector:t=`html`,attribute:n=`class`,initialValue:r=`auto`,window:i=Vd,storage:a,storageKey:o=`vueuse-color-scheme`,listenToStorageChanges:s=!0,storageRef:c,emitAuto:l,disableTransition:u=!0}=e,d={auto:``,light:`light`,dark:`dark`,...e.modes||{}},f=sf({window:i}),p=W(()=>f.value?`dark`:`light`),m=c||(o==null?md(r):df(o,r,a,{window:i,listenToStorageChanges:s})),h=W(()=>m.value===`auto`?p.value:m.value),g=of(`updateHTMLAttrs`,(e,t,n)=>{let r=typeof e==`string`?i?.document.querySelector(e):Wd(e);if(!r)return;let a=new Set,o=new Set,s=null;if(t===`class`){let e=n.split(/\s/g);Object.values(d).flatMap(e=>(e||``).split(/\s/g)).filter(Boolean).forEach(t=>{e.includes(t)?a.add(t):o.add(t)})}else s={key:t,value:n};if(a.size===0&&o.size===0&&s===null)return;let c;u&&(c=i.document.createElement(`style`),c.appendChild(document.createTextNode(ff)),i.document.head.appendChild(c));for(let e of a)r.classList.add(e);for(let e of o)r.classList.remove(e);s&&r.setAttribute(s.key,s.value),u&&(i.getComputedStyle(c).opacity,document.head.removeChild(c))});function _(e){g(t,n,d[e]??e)}function v(t){e.onChanged?e.onChanged(t,_):_(t)}pi(h,v,{flush:`post`,immediate:!0}),Id(()=>v(h.value));let y=W({get(){return l?m.value:h.value},set(e){m.value=e}});return Object.assign(y,{store:m,system:p,state:h})}function mf(e={}){let{valueDark:t=`dark`,valueLight:n=``}=e,r=pf({...e,onChanged:(t,n)=>{var r;e.onChanged?(r=e.onChanged)==null||r.call(e,t===`dark`,n,t):n(t)},modes:{dark:t,light:n}}),i=W(()=>r.system.value);return W({get(){return r.value===`dark`},set(e){let t=e?`dark`:`light`;i.value===t?r.value=`auto`:r.value=t}})}var hf=new Map;function gf(e){let t=yt();function n(n){var r;let a=hf.get(e)||new Set;a.add(n),hf.set(e,a);let o=()=>i(n);return t==null||(r=t.cleanups)==null||r.push(o),o}function r(e){function t(...n){i(t),e(...n)}return n(t)}function i(t){let n=hf.get(e);n&&(n.delete(t),n.size||a())}function a(){hf.delete(e)}function o(t,n){var r;(r=hf.get(e))==null||r.forEach(e=>e(t,n))}return{on:n,once:r,off:i,emit:o,reset:a}}function _f(e=null,t={}){let{baseUrl:n=``,rel:r=`icon`,document:i=Hd}=t,a=md(e),o=e=>{let t=i?.head.querySelectorAll(`link[rel*="${r}"]`);if(!t||t.length===0){let t=i?.createElement(`link`);t&&(t.rel=r,t.href=`${n}${e}`,t.type=`image/${e.split(`.`).pop()}`,i?.head.append(t));return}t?.forEach(t=>t.href=`${n}${e}`)};return pi(a,(e,t)=>{typeof e==`string`&&e!==t&&o(e)},{immediate:!0}),a}function vf(e,t,n={}){let{window:r=Vd}=n;return df(e,t,r?.localStorage,n)}function yf(e){let t=window.getComputedStyle(e);if(t.overflowX===`scroll`||t.overflowY===`scroll`||t.overflowX===`auto`&&e.clientWidthe}=t,i=n?.title??``,a=md(e??n?.title??null),o=!!(e&&typeof e==`function`);function s(e){if(!(`titleTemplate`in t))return e;let n=t.titleTemplate||`%s`;return typeof n==`function`?n(e):tr(n).replace(/%s/g,e)}return pi(a,(e,t)=>{e!==t&&n&&(n.title=s(e??``))},{immediate:!0}),t.observe&&!t.titleTemplate&&n&&!o&&Jd(n.head?.querySelector(`title`),()=>{n&&n.title!==a.value&&(a.value=s(n.title))},{childList:!0}),ad(()=>{if(r){let e=r(i,a.value||``);e!=null&&n&&(n.title=e)}}),a}function xf(e,t,n,r={}){var i,a;let{clone:o=!1,passive:s=!1,eventName:c,deep:l=!1,defaultValue:u,shouldEmit:d}=r,f=fc(),p=n||f?.emit||(f==null||(i=f.$emit)==null?void 0:i.bind(f))||(f==null||(a=f.proxy)==null||(a=a.$emit)==null?void 0:a.bind(f?.proxy)),m=c;t||=`modelValue`,m||=`update:${t.toString()}`;let h=e=>o?typeof o==`function`?o(e):ef(e):e,g=()=>ld(e[t])?h(e[t]):u,_=e=>{d?d(e)&&p(m,e):p(m,e)};if(s){let n=j(g()),r=!1;return pi(()=>e[t],e=>{r||(r=!0,n.value=h(e),zr(()=>r=!1))}),pi(n,n=>{!r&&(n!==e[t]||l)&&_(n)},{deep:l}),n}else return W({get(){return g()},set(e){_(e)}})}var Sf={ui:{colors:{primary:`blue`,secondary:`blue`,success:`green`,info:`blue`,warning:`yellow`,error:`red`,neutral:`zinc`},icons:{arrowDown:`i-lucide-arrow-down`,arrowLeft:`i-lucide-arrow-left`,arrowRight:`i-lucide-arrow-right`,arrowUp:`i-lucide-arrow-up`,caution:`i-lucide-circle-alert`,check:`i-lucide-check`,chevronDoubleLeft:`i-lucide-chevrons-left`,chevronDoubleRight:`i-lucide-chevrons-right`,chevronDown:`i-lucide-chevron-down`,chevronLeft:`i-lucide-chevron-left`,chevronRight:`i-lucide-chevron-right`,chevronUp:`i-lucide-chevron-up`,close:`i-lucide-x`,copy:`i-lucide-copy`,copyCheck:`i-lucide-copy-check`,dark:`i-lucide-moon`,drag:`i-lucide-grip-vertical`,ellipsis:`i-lucide-ellipsis`,error:`i-lucide-circle-x`,external:`i-lucide-arrow-up-right`,eye:`i-lucide-eye`,eyeOff:`i-lucide-eye-off`,file:`i-lucide-file`,folder:`i-lucide-folder`,folderOpen:`i-lucide-folder-open`,hash:`i-lucide-hash`,info:`i-lucide-info`,light:`i-lucide-sun`,loading:`i-lucide-loader-circle`,menu:`i-lucide-menu`,minus:`i-lucide-minus`,panelClose:`i-lucide-panel-left-close`,panelOpen:`i-lucide-panel-left-open`,plus:`i-lucide-plus`,reload:`i-lucide-rotate-ccw`,search:`i-lucide-search`,stop:`i-lucide-square`,success:`i-lucide-circle-check`,system:`i-lucide-monitor`,tip:`i-lucide-lightbulb`,upload:`i-lucide-upload`,warning:`i-lucide-triangle-alert`},tv:{twMergeConfig:{}},card:{defaultVariants:{variant:`soft`}},badge:{defaultVariants:{variant:`soft`}},button:{slots:{base:`not-disabled:cursor-pointer`},defaultVariants:{variant:`soft`}},tooltip:{slots:{content:`px-4 py-2 h-auto bg-default/90 text-toned font-mono`}}},colorMode:!0},Cf=Ln(Sf);const wf=()=>Cf,Tf={meta:``,ctrl:``,alt:``,win:`⊞`,command:`⌘`,shift:`⇧`,control:`⌃`,option:`⌥`,enter:`↵`,delete:`⌦`,backspace:`⌫`,escape:`Esc`,tab:`⇥`,capslock:`⇪`,arrowup:`↑`,arrowright:`→`,arrowdown:`↓`,arrowleft:`←`,pageup:`⇞`,pagedown:`⇟`,home:`↖`,end:`↘`},Ef=Ed(()=>{let e=W(()=>navigator&&navigator.userAgent&&navigator.userAgent.match(/Macintosh;/)),t=Ln({meta:` `,alt:` `,ctrl:` `});Aa(()=>{t.meta=e.value?Tf.command:`Ctrl`,t.ctrl=e.value?Tf.control:`Ctrl`,t.alt=e.value?Tf.option:`Alt`});function n(e){if(e)return[`meta`,`alt`,`ctrl`].includes(e)?t[e]:Tf[e]||e}return{macOS:e,getKbdKey:n}});function Df(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t!==null&&t!==Object.prototype&&Object.getPrototypeOf(t)!==null||Symbol.iterator in e?!1:Symbol.toStringTag in e?Object.prototype.toString.call(e)===`[object Module]`:!0}function Of(e,t,n=`.`,r){if(!Df(t))return Of(e,{},n,r);let i=Object.assign({},t);for(let t in e){if(t===`__proto__`||t===`constructor`)continue;let a=e[t];a!=null&&(r&&r(i,t,a,n)||(Array.isArray(a)&&Array.isArray(i[t])?i[t]=[...a,...i[t]]:Df(a)&&Df(i[t])?i[t]=Of(a,i[t],(n?`${n}.`:``)+t.toString(),r):i[t]=a))}return i}function kf(e){return(...t)=>t.reduce((t,n)=>Of(t,n,``,e),{})}var Af=kf();function jf(e){return Af(e,{dir:`ltr`})}function Mf(e){return typeof e==`string`?`'${e}'`:new Nf().serialize(e)}var Nf=function(){class e{#e=new Map;compare(e,t){let n=typeof e,r=typeof t;return n===`string`&&r===`string`?e.localeCompare(t):n===`number`&&r===`number`?e-t:String.prototype.localeCompare.call(this.serialize(e,!0),this.serialize(t,!0))}serialize(e,t){if(e===null)return`null`;switch(typeof e){case`string`:return t?e:`'${e}'`;case`bigint`:return`${e}n`;case`object`:return this.$object(e);case`function`:return this.$function(e)}return String(e)}serializeObject(e){let t=Object.prototype.toString.call(e);if(t!==`[object Object]`)return this.serializeBuiltInType(t.length<10?`unknown:${t}`:t.slice(8,-1),e);let n=e.constructor,r=n===Object||n===void 0?``:n.name;if(r!==``&&globalThis[r]===n)return this.serializeBuiltInType(r,e);if(typeof e.toJSON==`function`){let t=e.toJSON();return r+(typeof t==`object`&&t?this.$object(t):`(${this.serialize(t)})`)}return this.serializeObjectEntries(r,Object.entries(e))}serializeBuiltInType(e,t){let n=this[`$`+e];if(n)return n.call(this,t);if(typeof t?.entries==`function`)return this.serializeObjectEntries(e,t.entries());throw Error(`Cannot serialize ${e}`)}serializeObjectEntries(e,t){let n=Array.from(t).sort((e,t)=>this.compare(e[0],t[0])),r=`${e}{`;for(let e=0;ethis.compare(e,t)))}`}$Map(e){return this.serializeObjectEntries(`Map`,e.entries())}}for(let t of[`Error`,`RegExp`,`URL`])e.prototype[`$`+t]=function(e){return`${t}(${e})`};for(let t of[`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float32Array`,`Float64Array`])e.prototype[`$`+t]=function(e){return`${t}[${e.join(`,`)}]`};for(let t of[`BigInt64Array`,`BigUint64Array`])e.prototype[`$`+t]=function(e){return`${t}[${e.join(`n,`)}${e.length>0?`n`:``}]`};return e}();function Pf(e,t){return e===t||Mf(e)===Mf(t)}function Ff(e,t){return If(Lf(e),Lf(t))}function If(e,t){let n=[],r=new Set([...Object.keys(e.props||{}),...Object.keys(t.props||{})]);if(e.props&&t.props)for(let i of r){let r=e.props[i],a=t.props[i];r&&a?n.push(...If(e.props?.[i],t.props?.[i])):(r||a)&&n.push(new Rf((a||r).key,r?`removed`:`added`,a,r))}return r.size===0&&e.hash!==t.hash&&n.push(new Rf((t||e).key,`changed`,t,e)),n}function Lf(e,t=``){if(e&&typeof e!=`object`)return new zf(t,e,Mf(e));let n={},r=[];for(let i in e)n[i]=Lf(e[i],t?`${t}.${i}`:i),r.push(n[i].hash);return new zf(t,e,`{${r.join(`:`)}}`,n)}var Rf=class{constructor(e,t,n,r){this.key=e,this.type=t,this.newValue=n,this.oldValue=r}toString(){return this.toJSON()}toJSON(){switch(this.type){case`added`:return`Added \`${this.key}\``;case`removed`:return`Removed \`${this.key}\``;case`changed`:return`Changed \`${this.key}\` from \`${this.oldValue?.toString()||`-`}\` to \`${this.newValue.toString()}\``}}},zf=class{constructor(e,t,n,r){this.key=e,this.value=t,this.hash=n,this.props=r}toString(){return this.props?`{${Object.keys(this.props).join(`,`)}}`:JSON.stringify(this.value)}toJSON(){let e=this.key||`.`;return this.props?`${e}({${Object.keys(this.props).join(`,`)}})`:`${e}(${this.value})`}};function Bf(e,t){let n={...e};for(let e of t)delete n[e];return n}function Vf(e,t,n){typeof t==`string`&&(t=t.split(`.`).map(e=>{let t=Number(e);return Number.isNaN(t)?e:t}));let r=e;for(let e of t){if(r==null)return n;r=r[e]}return r===void 0?n:r}function Hf(e){let t=Number.parseFloat(e);return Number.isNaN(t)?e:t}function Uf(e,t,n){return e===void 0||t===void 0?!1:typeof e==`string`?e===t:typeof n==`function`?n(e,t):typeof n==`string`?Vf(e,n)===Vf(t,n):Pf(e,t)}function Wf(e){if(e==null)return!0;if(typeof e==`boolean`||typeof e==`number`)return!1;if(typeof e==`string`)return e.trim().length===0;if(Array.isArray(e))return e.length===0;if(e instanceof Map||e instanceof Set)return e.size===0;if(e instanceof Date||e instanceof RegExp||typeof e==`function`)return!1;if(typeof e==`object`){for(let t in e)if(Object.prototype.hasOwnProperty.call(e,t))return!1;return!0}return!1}function Gf(e,t,n={}){let{valueKey:r,labelKey:i}=n,a=e.find(e=>Uf(typeof e==`object`&&e&&r?Vf(e,r):e,t));if(Wf(t)&&a)return i?Vf(a,i):void 0;if(Wf(t))return;let o=a??t;if(o!=null)return typeof o==`object`?i?Vf(o,i):void 0:String(o)}function Kf(e){return Array.isArray(e[0])}function qf(e,t){return!e&&!t?``:[...Array.isArray(e)?e:[e],t].filter(Boolean)}function Jf(e){return(t,n)=>Yf(t,n,M(e))}function Yf(e,t,n){return Vf(n,`messages.${e}`,e).replace(/\{(\w+)\}/g,(e,n)=>`${t?.[n]??`{${n}}`}`)}function Xf(e){return{lang:W(()=>M(e).name),code:W(()=>M(e).code),dir:W(()=>M(e).dir),locale:Xn(e)?e:j(e),t:Jf(e)}}var Zf=jf({name:`English`,code:`en`,messages:{alert:{close:`Close`},authForm:{hidePassword:`Hide password`,showPassword:`Show password`,submit:`Continue`},banner:{close:`Close`},calendar:{nextMonth:`Next month`,nextYear:`Next year`,prevMonth:`Previous month`,prevYear:`Previous year`},carousel:{dots:`Choose slide to display`,goto:`Go to slide {slide}`,next:`Next`,prev:`Prev`},chatPrompt:{placeholder:`Type your message here…`},chatPromptSubmit:{label:`Send prompt`},colorMode:{dark:`Dark`,light:`Light`,switchToDark:`Switch to dark mode`,switchToLight:`Switch to light mode`,system:`System`},commandPalette:{back:`Back`,close:`Close`,noData:`No data`,noMatch:`No matching data`,placeholder:`Type a command or search…`},contentSearch:{links:`Links`,theme:`Theme`},contentSearchButton:{label:`Search…`},contentToc:{title:`On this page`},dashboardSearch:{theme:`Theme`},dashboardSearchButton:{label:`Search…`},dashboardSidebarCollapse:{collapse:`Collapse sidebar`,expand:`Expand sidebar`},dashboardSidebarToggle:{close:`Close sidebar`,open:`Open sidebar`},error:{clear:`Back to home`},fileUpload:{removeFile:`Remove {filename}`},header:{close:`Close menu`,open:`Open menu`},inputMenu:{create:`Create "{label}"`,noData:`No data`,noMatch:`No matching data`},inputNumber:{decrement:`Decrement`,increment:`Increment`},modal:{close:`Close`},pricingTable:{caption:`Pricing plan comparison`},prose:{codeCollapse:{closeText:`Collapse`,name:`code`,openText:`Expand`},collapsible:{closeText:`Hide`,name:`properties`,openText:`Show`},pre:{copy:`Copy code to clipboard`}},selectMenu:{create:`Create "{label}"`,noData:`No data`,noMatch:`No matching data`,search:`Search…`},slideover:{close:`Close`},table:{noData:`No data`},toast:{close:`Close`}}});const Qf=Symbol.for(`nuxt-ui.locale-context`),$f=Ed(e=>{let t=e||lr(oi(Qf,Zf));return Xf(W(()=>t.value||Zf))}),ep=()=>{if(!Sf.colorMode)return{forced:!0};let{store:e,system:t}=pf();return{get preference(){return e.value===`auto`?`system`:e.value},set preference(t){e.value=t===`system`?`auto`:t},get value(){return e.value===`auto`?t.value:e.value},forced:!1}};var tp={};const np=(e,t)=>{if(tp[e])return tp[e];let n=j(t());return tp[e]=n,n};var rp=u();function ip(){return{isHydrating:!0,payload:{serverRendered:!1},hooks:rp,hook:rp.hook}}function ap(e){return{install(t){t.runWithContext(()=>e({vueApp:t}))}}}var op=typeof document<`u`;function sp(e){return typeof e==`object`||`displayName`in e||`props`in e||`__vccOpts`in e}function cp(e){return e.__esModule||e[Symbol.toStringTag]===`Module`||e.default&&sp(e.default)}var lp=Object.assign;function up(e,t){let n={};for(let r in t){let i=t[r];n[r]=fp(i)?i.map(e):e(i)}return n}var dp=()=>{},fp=Array.isArray;function pp(e,t){let n={};for(let r in e)n[r]=r in t?t[r]:e[r];return n}var mp=/#/g,hp=/&/g,gp=/\//g,_p=/=/g,vp=/\?/g,yp=/\+/g,bp=/%5B/g,xp=/%5D/g,Sp=/%5E/g,Cp=/%60/g,wp=/%7B/g,Tp=/%7C/g,Ep=/%7D/g,Dp=/%20/g;function Op(e){return e==null?``:encodeURI(``+e).replace(Tp,`|`).replace(bp,`[`).replace(xp,`]`)}function kp(e){return Op(e).replace(wp,`{`).replace(Ep,`}`).replace(Sp,`^`)}function Ap(e){return Op(e).replace(yp,`%2B`).replace(Dp,`+`).replace(mp,`%23`).replace(hp,`%26`).replace(Cp,"`").replace(wp,`{`).replace(Ep,`}`).replace(Sp,`^`)}function jp(e){return Ap(e).replace(_p,`%3D`)}function Mp(e){return Op(e).replace(mp,`%23`).replace(vp,`%3F`)}function Np(e){return Mp(e).replace(gp,`%2F`)}function Pp(e){if(e==null)return null;try{return decodeURIComponent(``+e)}catch{}return``+e}var Fp=/\/$/,Ip=e=>e.replace(Fp,``);function Lp(e,t,n=`/`){let r,i={},a=``,o=``,s=t.indexOf(`#`),c=t.indexOf(`?`);return c=s>=0&&c>s?-1:c,c>=0&&(r=t.slice(0,c),a=t.slice(c,s>0?s:t.length),i=e(a.slice(1))),s>=0&&(r||=t.slice(0,s),o=t.slice(s,t.length)),r=Gp(r??t,n),{fullPath:r+a+o,path:r,query:i,hash:Pp(o)}}function Rp(e,t){let n=t.query?e(t.query):``;return t.path+(n&&`?`)+n+(t.hash||``)}function zp(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||`/`}function Bp(e,t,n){let r=t.matched.length-1,i=n.matched.length-1;return r>-1&&r===i&&Vp(t.matched[r],n.matched[i])&&Hp(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function Vp(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Hp(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var n in e)if(!Up(e[n],t[n]))return!1;return!0}function Up(e,t){return fp(e)?Wp(e,t):fp(t)?Wp(t,e):e?.valueOf()===t?.valueOf()}function Wp(e,t){return fp(t)?e.length===t.length&&e.every((e,n)=>e===t[n]):e.length===1&&e[0]===t}function Gp(e,t){if(e.startsWith(`/`))return e;if(!e)return t;let n=t.split(`/`),r=e.split(`/`),i=r[r.length-1];(i===`..`||i===`.`)&&r.push(``);let a=n.length-1,o,s;for(o=0;o1&&a--;else break;return n.slice(0,a).join(`/`)+`/`+r.slice(o).join(`/`)}var Kp={path:`/`,name:void 0,params:{},query:{},hash:``,fullPath:`/`,matched:[],meta:{},redirectedFrom:void 0},qp=function(e){return e.pop=`pop`,e.push=`push`,e}({}),Jp=function(e){return e.back=`back`,e.forward=`forward`,e.unknown=``,e}({});function Yp(e){if(!e)if(op){let t=document.querySelector(`base`);e=t&&t.getAttribute(`href`)||`/`,e=e.replace(/^\w+:\/\/[^\/]+/,``)}else e=`/`;return e[0]!==`/`&&e[0]!==`#`&&(e=`/`+e),Ip(e)}var Xp=/^[^#]+#/;function Zp(e,t){return e.replace(Xp,`#`)+t}function Qp(e,t){let n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}var $p=()=>({left:window.scrollX,top:window.scrollY});function em(e){let t;if(`el`in e){let n=e.el,r=typeof n==`string`&&n.startsWith(`#`),i=typeof n==`string`?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!i)return;t=Qp(i,e)}else t=e;`scrollBehavior`in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left==null?window.scrollX:t.left,t.top==null?window.scrollY:t.top)}function tm(e,t){return(history.state?history.state.position-t:-1)+e}var nm=new Map;function rm(e,t){nm.set(e,t)}function im(e){let t=nm.get(e);return nm.delete(e),t}function am(e){return typeof e==`string`||e&&typeof e==`object`}function om(e){return typeof e==`string`||typeof e==`symbol`}var sm=function(e){return e[e.MATCHER_NOT_FOUND=1]=`MATCHER_NOT_FOUND`,e[e.NAVIGATION_GUARD_REDIRECT=2]=`NAVIGATION_GUARD_REDIRECT`,e[e.NAVIGATION_ABORTED=4]=`NAVIGATION_ABORTED`,e[e.NAVIGATION_CANCELLED=8]=`NAVIGATION_CANCELLED`,e[e.NAVIGATION_DUPLICATED=16]=`NAVIGATION_DUPLICATED`,e}({}),cm=Symbol(``);sm.MATCHER_NOT_FOUND,sm.NAVIGATION_GUARD_REDIRECT,sm.NAVIGATION_ABORTED,sm.NAVIGATION_CANCELLED,sm.NAVIGATION_DUPLICATED;function lm(e,t){return lp(Error(),{type:e,[cm]:!0},t)}function um(e,t){return e instanceof Error&&cm in e&&(t==null||!!(e.type&t))}function dm(e){let t={};if(e===``||e===`?`)return t;let n=(e[0]===`?`?e.slice(1):e).split(`&`);for(let e=0;ee&&Ap(e)):[r&&Ap(r)]).forEach(e=>{e!==void 0&&(t+=(t.length?`&`:``)+n,e!=null&&(t+=`=`+e))})}return t}function pm(e){let t={};for(let n in e){let r=e[n];r!==void 0&&(t[n]=fp(r)?r.map(e=>e==null?null:``+e):r==null?r:``+r)}return t}var mm=Symbol(``),hm=Symbol(``),gm=Symbol(``),_m=Symbol(``),vm=Symbol(``);function ym(){let e=[];function t(t){return e.push(t),()=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function bm(e,t,n,r,i,a=e=>e()){let o=r&&(r.enterCallbacks[i]=r.enterCallbacks[i]||[]);return()=>new Promise((s,c)=>{let l=e=>{e===!1?c(lm(sm.NAVIGATION_ABORTED,{from:n,to:t})):e instanceof Error?c(e):am(e)?c(lm(sm.NAVIGATION_GUARD_REDIRECT,{from:t,to:e})):(o&&r.enterCallbacks[i]===o&&typeof e==`function`&&o.push(e),s())},u=a(()=>e.call(r&&r.instances[i],t,n,l)),d=Promise.resolve(u);e.length<3&&(d=d.then(l)),d.catch(e=>c(e))})}function xm(e,t,n,r,i=e=>e()){let a=[];for(let o of e)for(let e in o.components){let s=o.components[e];if(!(t!==`beforeRouteEnter`&&!o.instances[e]))if(sp(s)){let c=(s.__vccOpts||s)[t];c&&a.push(bm(c,n,r,o,e,i))}else{let c=s();a.push(()=>c.then(a=>{if(!a)throw Error(`Couldn't resolve component "${e}" at "${o.path}"`);let s=cp(a)?a.default:a;o.mods[e]=a,o.components[e]=s;let c=(s.__vccOpts||s)[t];return c&&bm(c,n,r,o,e,i)()}))}}return a}function Sm(e,t){let n=[],r=[],i=[],a=Math.max(t.matched.length,e.matched.length);for(let o=0;oVp(e,a))?r.push(a):n.push(a));let s=e.matched[o];s&&(t.matched.find(e=>Vp(e,s))||i.push(s))}return[n,r,i]}var Cm=()=>location.protocol+`//`+location.host;function wm(e,t){let{pathname:n,search:r,hash:i}=t,a=e.indexOf(`#`);if(a>-1){let t=i.includes(e.slice(a))?e.slice(a).length:1,n=i.slice(t);return n[0]!==`/`&&(n=`/`+n),zp(n,``)}return zp(n,e)+r+i}function Tm(e,t,n,r){let i=[],a=[],o=null,s=({state:a})=>{let s=wm(e,location),c=n.value,l=t.value,u=0;if(a){if(n.value=s,t.value=a,o&&o===c){o=null;return}u=l?a.position-l.position:0}else r(s);i.forEach(e=>{e(n.value,c,{delta:u,type:qp.pop,direction:u?u>0?Jp.forward:Jp.back:Jp.unknown})})};function c(){o=n.value}function l(e){i.push(e);let t=()=>{let t=i.indexOf(e);t>-1&&i.splice(t,1)};return a.push(t),t}function u(){if(document.visibilityState===`hidden`){let{history:e}=window;if(!e.state)return;e.replaceState(lp({},e.state,{scroll:$p()}),``)}}function d(){for(let e of a)e();a=[],window.removeEventListener(`popstate`,s),window.removeEventListener(`pagehide`,u),document.removeEventListener(`visibilitychange`,u)}return window.addEventListener(`popstate`,s),window.addEventListener(`pagehide`,u),document.addEventListener(`visibilitychange`,u),{pauseListeners:c,listen:l,destroy:d}}function Em(e,t,n,r=!1,i=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:i?$p():null}}function Dm(e){let{history:t,location:n}=window,r={value:wm(e,n)},i={value:t.state};i.value||a(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function a(r,a,o){let s=e.indexOf(`#`),c=s>-1?(n.host&&document.querySelector(`base`)?e:e.slice(s))+r:Cm()+e+r;try{t[o?`replaceState`:`pushState`](a,``,c),i.value=a}catch(e){console.error(e),n[o?`replace`:`assign`](c)}}function o(e,n){a(e,lp({},t.state,Em(i.value.back,e,i.value.forward,!0),n,{position:i.value.position}),!0),r.value=e}function s(e,n){let o=lp({},i.value,t.state,{forward:e,scroll:$p()});a(o.current,o,!0),a(e,lp({},Em(r.value,e,null),{position:o.position+1},n),!1),r.value=e}return{location:r,state:i,push:s,replace:o}}function Om(e){e=Yp(e);let t=Dm(e),n=Tm(e,t.state,t.location,t.replace);function r(e,t=!0){t||n.pauseListeners(),history.go(e)}let i=lp({location:``,base:e,go:r,createHref:Zp.bind(null,e)},t,n);return Object.defineProperty(i,`location`,{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(i,`state`,{enumerable:!0,get:()=>t.state.value}),i}var km=function(e){return e[e.Static=0]=`Static`,e[e.Param=1]=`Param`,e[e.Group=2]=`Group`,e}({}),Am=function(e){return e[e.Static=0]=`Static`,e[e.Param=1]=`Param`,e[e.ParamRegExp=2]=`ParamRegExp`,e[e.ParamRegExpEnd=3]=`ParamRegExpEnd`,e[e.EscapeNext=4]=`EscapeNext`,e}(Am||{}),jm={type:km.Static,value:``},Mm=/[a-zA-Z0-9_]/;function Nm(e){if(!e)return[[]];if(e===`/`)return[[jm]];if(!e.startsWith(`/`))throw Error(`Invalid path "${e}"`);function t(e){throw Error(`ERR (${n})/"${l}": ${e}`)}let n=Am.Static,r=n,i=[],a;function o(){a&&i.push(a),a=[]}let s=0,c,l=``,u=``;function d(){l&&=(n===Am.Static?a.push({type:km.Static,value:l}):n===Am.Param||n===Am.ParamRegExp||n===Am.ParamRegExpEnd?(a.length>1&&(c===`*`||c===`+`)&&t(`A repeatable param (${l}) must be alone in its segment. eg: '/:ids+.`),a.push({type:km.Param,value:l,regexp:u,repeatable:c===`*`||c===`+`,optional:c===`*`||c===`?`})):t(`Invalid state to consume buffer`),``)}function f(){l+=c}for(;st.length?t.length===1&&t[0]===Im.Static+Im.Segment?1:-1:0}function Bm(e,t){let n=0,r=e.score,i=t.score;for(;n0&&t[t.length-1]<0}var Hm={strict:!1,end:!0,sensitive:!1};function Um(e,t,n){let r=lp(Rm(Nm(e.path),n),{record:e,parent:t,children:[],alias:[]});return t&&!r.record.aliasOf==!t.record.aliasOf&&t.children.push(r),r}function Wm(e,t){let n=[],r=new Map;t=pp(Hm,t);function i(e){return r.get(e)}function a(e,n,r){let i=!r,s=Km(e);s.aliasOf=r&&r.record;let l=pp(t,e),u=[s];if(`alias`in e){let t=typeof e.alias==`string`?[e.alias]:e.alias;for(let e of t)u.push(Km(lp({},s,{components:r?r.record.components:s.components,path:e,aliasOf:r?r.record:s})))}let d,f;for(let t of u){let{path:u}=t;if(n&&u[0]!==`/`){let e=n.record.path,r=e[e.length-1]===`/`?``:`/`;t.path=n.record.path+(u&&r+u)}if(d=Um(t,n,l),r?r.alias.push(d):(f||=d,f!==d&&f.alias.push(d),i&&e.name&&!Jm(d)&&o(e.name)),Qm(d)&&c(d),s.children){let e=s.children;for(let t=0;t{o(f)}:dp}function o(e){if(om(e)){let t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(o),t.alias.forEach(o))}else{let t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(o),e.alias.forEach(o))}}function s(){return n}function c(e){let t=Xm(e,n);n.splice(t,0,e),e.record.name&&!Jm(e)&&r.set(e.record.name,e)}function l(e,t){let i,a={},o,s;if(`name`in e&&e.name){if(i=r.get(e.name),!i)throw lm(sm.MATCHER_NOT_FOUND,{location:e});s=i.record.name,a=lp(Gm(t.params,i.keys.filter(e=>!e.optional).concat(i.parent?i.parent.keys.filter(e=>e.optional):[]).map(e=>e.name)),e.params&&Gm(e.params,i.keys.map(e=>e.name))),o=i.stringify(a)}else if(e.path!=null)o=e.path,i=n.find(e=>e.re.test(o)),i&&(a=i.parse(o),s=i.record.name);else{if(i=t.name?r.get(t.name):n.find(e=>e.re.test(t.path)),!i)throw lm(sm.MATCHER_NOT_FOUND,{location:e,currentLocation:t});s=i.record.name,a=lp({},t.params,e.params),o=i.stringify(a)}let c=[],l=i;for(;l;)c.unshift(l.record),l=l.parent;return{name:s,path:o,params:a,matched:c,meta:Ym(c)}}e.forEach(e=>a(e));function u(){n.length=0,r.clear()}return{addRoute:a,resolve:l,removeRoute:o,clearRoutes:u,getRoutes:s,getRecordMatcher:i}}function Gm(e,t){let n={};for(let r of t)r in e&&(n[r]=e[r]);return n}function Km(e){let t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:qm(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:`components`in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,`mods`,{value:{}}),t}function qm(e){let t={},n=e.props||!1;if(`component`in e)t.default=n;else for(let r in e.components)t[r]=typeof n==`object`?n[r]:n;return t}function Jm(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function Ym(e){return e.reduce((e,t)=>lp(e,t.meta),{})}function Xm(e,t){let n=0,r=t.length;for(;n!==r;){let i=n+r>>1;Bm(e,t[i])<0?r=i:n=i+1}let i=Zm(e);return i&&(r=t.lastIndexOf(i,r-1)),r}function Zm(e){let t=e;for(;t=t.parent;)if(Qm(t)&&Bm(e,t)===0)return t}function Qm({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function $m(e){let t=oi(gm),n=oi(_m),r=W(()=>{let n=M(e.to);return t.resolve(n)}),i=W(()=>{let{matched:e}=r.value,{length:t}=e,i=e[t-1],a=n.matched;if(!i||!a.length)return-1;let o=a.findIndex(Vp.bind(null,i));if(o>-1)return o;let s=ih(e[t-2]);return t>1&&ih(i)===s&&a[a.length-1].path!==s?a.findIndex(Vp.bind(null,e[t-2])):o}),a=W(()=>i.value>-1&&rh(n.params,r.value.params)),o=W(()=>i.value>-1&&i.value===n.matched.length-1&&Hp(n.params,r.value.params));function s(n={}){if(nh(n)){let n=t[M(e.replace)?`replace`:`push`](M(e.to)).catch(dp);return e.viewTransition&&typeof document<`u`&&`startViewTransition`in document&&document.startViewTransition(()=>n),n}return Promise.resolve()}return{route:r,href:W(()=>r.value.href),isActive:a,isExactActive:o,navigate:s}}function eh(e){return e.length===1?e[0]:e}var th=P({name:`RouterLink`,compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:`page`},viewTransition:Boolean},useLink:$m,setup(e,{slots:t}){let n=Ln($m(e)),{options:r}=oi(gm),i=W(()=>({[ah(e.activeClass,r.linkActiveClass,`router-link-active`)]:n.isActive,[ah(e.exactActiveClass,r.linkExactActiveClass,`router-link-exact-active`)]:n.isExactActive}));return()=>{let r=t.default&&eh(t.default(n));return e.custom?r:Mc(`a`,{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:i.value},r)}}});function nh(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){let t=e.currentTarget.getAttribute(`target`);if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function rh(e,t){for(let n in t){let r=t[n],i=e[n];if(typeof r==`string`){if(r!==i)return!1}else if(!fp(i)||i.length!==r.length||r.some((e,t)=>e.valueOf()!==i[t].valueOf()))return!1}return!0}function ih(e){return e?e.aliasOf?e.aliasOf.path:e.path:``}var ah=(e,t,n)=>e??t??n,oh=P({name:`RouterView`,inheritAttrs:!1,props:{name:{type:String,default:`default`},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){let r=oi(vm),i=W(()=>e.route||r.value),a=oi(hm,0),o=W(()=>{let e=M(a),{matched:t}=i.value,n;for(;(n=t[e])&&!n.components;)e++;return e}),s=W(()=>i.value.matched[o.value]);ai(hm,W(()=>o.value+1)),ai(mm,s),ai(vm,i);let c=j();return pi(()=>[c.value,s.value,e.name],([e,t,n],[r,i,a])=>{t&&(t.instances[n]=e,i&&i!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=i.leaveGuards),t.updateGuards.size||(t.updateGuards=i.updateGuards))),e&&t&&(!i||!Vp(t,i)||!r)&&(t.enterCallbacks[n]||[]).forEach(t=>t(e))},{flush:`post`}),()=>{let r=i.value,a=e.name,o=s.value,l=o&&o.components[a];if(!l)return sh(n.default,{Component:l,route:r});let u=o.props[a],d=Mc(l,lp({},u?u===!0?r.params:typeof u==`function`?u(r):u:null,t,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(o.instances[a]=null)},ref:c}));return sh(n.default,{Component:d,route:r})||d}}});function sh(e,t){if(!e)return null;let n=e(t);return n.length===1?n[0]:n}var ch=oh;function lh(e){let t=Wm(e.routes,e),n=e.parseQuery||dm,r=e.stringifyQuery||fm,i=e.history,a=ym(),o=ym(),s=ym(),c=Zn(Kp),l=Kp;op&&e.scrollBehavior&&`scrollRestoration`in history&&(history.scrollRestoration=`manual`);let u=up.bind(null,e=>``+e),d=up.bind(null,Np),f=up.bind(null,Pp);function p(e,n){let r,i;return om(e)?(r=t.getRecordMatcher(e),i=n):i=e,t.addRoute(i,r)}function m(e){let n=t.getRecordMatcher(e);n&&t.removeRoute(n)}function h(){return t.getRoutes().map(e=>e.record)}function g(e){return!!t.getRecordMatcher(e)}function _(e,a){if(a=lp({},a||c.value),typeof e==`string`){let r=Lp(n,e,a.path),o=t.resolve({path:r.path},a),s=i.createHref(r.fullPath);return lp(r,o,{params:f(o.params),hash:Pp(r.hash),redirectedFrom:void 0,href:s})}let o;if(e.path!=null)o=lp({},e,{path:Lp(n,e.path,a.path).path});else{let t=lp({},e.params);for(let e in t)t[e]??delete t[e];o=lp({},e,{params:d(t)}),a.params=d(a.params)}let s=t.resolve(o,a),l=e.hash||``;s.params=u(f(s.params));let p=Rp(r,lp({},e,{hash:kp(l),path:s.path})),m=i.createHref(p);return lp({fullPath:p,hash:l,query:r===fm?pm(e.query):e.query||{}},s,{redirectedFrom:void 0,href:m})}function v(e){return typeof e==`string`?Lp(n,e,c.value.path):lp({},e)}function y(e,t){if(l!==e)return lm(sm.NAVIGATION_CANCELLED,{from:t,to:e})}function b(e){return S(e)}function x(e){return b(lp(v(e),{replace:!0}))}function ee(e,t){let n=e.matched[e.matched.length-1];if(n&&n.redirect){let{redirect:r}=n,i=typeof r==`function`?r(e,t):r;return typeof i==`string`&&(i=i.includes(`?`)||i.includes(`#`)?i=v(i):{path:i},i.params={}),lp({query:e.query,hash:e.hash,params:i.path==null?e.params:{}},i)}}function S(e,t){let n=l=_(e),i=c.value,a=e.state,o=e.force,s=e.replace===!0,u=ee(n,i);if(u)return S(lp(v(u),{state:typeof u==`object`?lp({},a,u.state):a,force:o,replace:s}),t||n);let d=n;d.redirectedFrom=t;let f;return!o&&Bp(r,i,n)&&(f=lm(sm.NAVIGATION_DUPLICATED,{to:d,from:i}),ue(i,i,!0,!1)),(f?Promise.resolve(f):te(d,i)).catch(e=>um(e)?um(e,sm.NAVIGATION_GUARD_REDIRECT)?e:le(e):E(e,d,i)).then(e=>{if(e){if(um(e,sm.NAVIGATION_GUARD_REDIRECT))return S(lp({replace:s},v(e.to),{state:typeof e.to==`object`?lp({},a,e.to.state):a,force:o}),t||d)}else e=re(d,i,!0,s,a);return ne(d,i,e),e})}function C(e,t){let n=y(e,t);return n?Promise.reject(n):Promise.resolve()}function w(e){let t=fe.values().next().value;return t&&typeof t.runWithContext==`function`?t.runWithContext(e):e()}function te(e,t){let n,[r,i,s]=Sm(e,t);n=xm(r.reverse(),`beforeRouteLeave`,e,t);for(let i of r)i.leaveGuards.forEach(r=>{n.push(bm(r,e,t))});let c=C.bind(null,e,t);return n.push(c),me(n).then(()=>{n=[];for(let r of a.list())n.push(bm(r,e,t));return n.push(c),me(n)}).then(()=>{n=xm(i,`beforeRouteUpdate`,e,t);for(let r of i)r.updateGuards.forEach(r=>{n.push(bm(r,e,t))});return n.push(c),me(n)}).then(()=>{n=[];for(let r of s)if(r.beforeEnter)if(fp(r.beforeEnter))for(let i of r.beforeEnter)n.push(bm(i,e,t));else n.push(bm(r.beforeEnter,e,t));return n.push(c),me(n)}).then(()=>(e.matched.forEach(e=>e.enterCallbacks={}),n=xm(s,`beforeRouteEnter`,e,t,w),n.push(c),me(n))).then(()=>{n=[];for(let r of o.list())n.push(bm(r,e,t));return n.push(c),me(n)}).catch(e=>um(e,sm.NAVIGATION_CANCELLED)?e:Promise.reject(e))}function ne(e,t,n){s.list().forEach(r=>w(()=>r(e,t,n)))}function re(e,t,n,r,a){let o=y(e,t);if(o)return o;let s=t===Kp,l=op?history.state:{};n&&(r||s?i.replace(e.fullPath,lp({scroll:s&&l&&l.scroll},a)):i.push(e.fullPath,a)),c.value=e,ue(e,t,n,s),le()}let T;function ie(){T||=i.listen((e,t,n)=>{if(!pe.listening)return;let r=_(e),a=ee(r,pe.currentRoute.value);if(a){S(lp(a,{replace:!0,force:!0}),r).catch(dp);return}l=r;let o=c.value;op&&rm(tm(o.fullPath,n.delta),$p()),te(r,o).catch(e=>um(e,sm.NAVIGATION_ABORTED|sm.NAVIGATION_CANCELLED)?e:um(e,sm.NAVIGATION_GUARD_REDIRECT)?(S(lp(v(e.to),{force:!0}),r).then(e=>{um(e,sm.NAVIGATION_ABORTED|sm.NAVIGATION_DUPLICATED)&&!n.delta&&n.type===qp.pop&&i.go(-1,!1)}).catch(dp),Promise.reject()):(n.delta&&i.go(-n.delta,!1),E(e,r,o))).then(e=>{e||=re(r,o,!1),e&&(n.delta&&!um(e,sm.NAVIGATION_CANCELLED)?i.go(-n.delta,!1):n.type===qp.pop&&um(e,sm.NAVIGATION_ABORTED|sm.NAVIGATION_DUPLICATED)&&i.go(-1,!1)),ne(r,o,e)}).catch(dp)})}let ae=ym(),oe=ym(),se;function E(e,t,n){le(e);let r=oe.list();return r.length?r.forEach(r=>r(e,t,n)):console.error(e),Promise.reject(e)}function ce(){return se&&c.value!==Kp?Promise.resolve():new Promise((e,t)=>{ae.add([e,t])})}function le(e){return se||(se=!e,ie(),ae.list().forEach(([t,n])=>e?n(e):t()),ae.reset()),e}function ue(t,n,r,i){let{scrollBehavior:a}=e;if(!op||!a)return Promise.resolve();let o=!r&&im(tm(t.fullPath,0))||(i||!r)&&history.state&&history.state.scroll||null;return zr().then(()=>a(t,n,o)).then(e=>e&&em(e)).catch(e=>E(e,t,n))}let D=e=>i.go(e),de,fe=new Set,pe={currentRoute:c,listening:!0,addRoute:p,removeRoute:m,clearRoutes:t.clearRoutes,hasRoute:g,getRoutes:h,resolve:_,options:e,push:b,replace:x,go:D,back:()=>D(-1),forward:()=>D(1),beforeEach:a.add,beforeResolve:o.add,afterEach:s.add,onError:oe.add,isReady:ce,install(e){e.component(`RouterLink`,th),e.component(`RouterView`,ch),e.config.globalProperties.$router=pe,Object.defineProperty(e.config.globalProperties,`$route`,{enumerable:!0,get:()=>M(c)}),op&&!de&&c.value===Kp&&(de=!0,b(i.location).catch(e=>{}));let t={};for(let e in Kp)Object.defineProperty(t,e,{get:()=>c.value[e],enumerable:!0});e.provide(gm,pe),e.provide(_m,Rn(t)),e.provide(vm,c);let n=e.unmount;fe.add(e),e.unmount=function(){fe.delete(e),fe.size<1&&(l=Kp,T&&T(),T=null,c.value=Kp,de=!1,se=!1),n()}}};function me(e){return e.reduce((e,t)=>e.then(()=>w(t)),Promise.resolve())}return pe}function uh(e){return oi(_m)}var dh=[50,100,200,300,400,500,600,700,800,900,950];function fh(e,t){return e in id&&typeof id[e]==`object`&&t in id[e]?id[e][t]:``}function ph(e,t,n){let r=n?`${n}-`:``;return`${dh.map(n=>`--ui-color-${e}-${n}: var(--${r}color-${t===`neutral`?`old-neutral`:t}-${n}, ${fh(t,n)});`).join(` + `)}`}function mh(e,t){return`--ui-${e}: var(--ui-color-${e}-${t});`}var hh=ap(()=>{let e=wf(),t=ip(),n=W(()=>{let{neutral:t,...n}=e.ui.colors,r=e.ui.prefix;return`@layer theme { + :root, :host { + ${Object.entries(e.ui.colors).map(([e,t])=>ph(e,t,r)).join(` + `)} + } + :root, :host, .light { + ${Object.keys(n).map(e=>mh(e,500)).join(` + `)} + } + .dark { + ${Object.keys(n).map(e=>mh(e,400)).join(` + `)} + } +}`}),r={style:[{innerHTML:()=>n.value,tagPriority:-2,id:`nuxt-ui-colors`}]};if(t.isHydrating&&!t.payload.serverRendered){let e=document.createElement(`style`);e.innerHTML=n.value,e.setAttribute(`data-nuxt-ui-colors`,``),document.head.appendChild(e),r.script=[{innerHTML:`document.head.removeChild(document.querySelector('[data-nuxt-ui-colors]'))`}]}ed(r)}),gh={install(){mf()}},_h={install(e){e.use(rd),e.use(hh),e.use(gh)}};function vh(e,t,n){let r=e.findIndex(e=>Pf(e,t)),i=e.findIndex(e=>Pf(e,n));if(r===-1||i===-1)return[];let[a,o]=[r,i].sort((e,t)=>e-t);return e.slice(a,o+1)}function yh(e,t=-1/0,n=1/0){return Math.min(n,Math.max(t,e))}function bh(e,t){let n=typeof e==`string`&&!t?`${e}Context`:t,r=Symbol(n);return[t=>{let n=oi(r,t);if(n||n===null)return n;throw Error(`Injection \`${r.toString()}\` not found. Component must be used within ${Array.isArray(e)?`one of the following components: ${e.join(`, `)}`:`\`${e}\``}`)},e=>(ai(r,e),e)]}function xh(){let e=document.activeElement;if(e==null)return null;for(;e!=null&&e.shadowRoot!=null&&e.shadowRoot.activeElement!=null;)e=e.shadowRoot.activeElement;return e}function Sh(e,t,n){let r=n.originalEvent.target,i=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&r.addEventListener(e,t,{once:!0}),r.dispatchEvent(i)}function Ch(e){return e==null}function wh(e){return e?e.flatMap(e=>e.type===I?wh(e.children):[e]):[]}var[Th,Eh]=bh(`ConfigProvider`),Dh=P({inheritAttrs:!1,__name:`ConfigProvider`,props:{dir:{type:String,required:!1,default:`ltr`},locale:{type:String,required:!1,default:`en`},scrollBody:{type:[Boolean,Object],required:!1,default:!0},nonce:{type:String,required:!1,default:void 0},useId:{type:Function,required:!1,default:void 0}},setup(e){let t=e,{dir:n,locale:r,scrollBody:i,nonce:a}=or(t);return Eh({dir:n,locale:r,scrollBody:i,nonce:a,useId:t.useId}),(e,t)=>F(e.$slots,`default`)}});function Oh(e,t){let n=Zn();return ui(()=>{n.value=e()},{...t,flush:t?.flush??`sync`}),zn(n)}function kh(e,t){let n,r,i,a=Zn(!0),o=()=>{a.value=!0,i()};pi(e,o,{flush:`sync`});let s=typeof t==`function`?t:t.get,c=typeof t==`function`?void 0:t.set,l=ar((e,t)=>(r=e,i=t,{get(){return a.value&&=(n=s(n),!1),r(),n},set(e){c?.(e)}}));return Object.isExtensible(l)&&(l.trigger=o),l}function Ah(e){return yt()?(bt(e),!0):!1}function jh(){let e=new Set,t=t=>{e.delete(t)};return{on:n=>{e.add(n);let r=()=>t(n);return Ah(r),{off:r}},off:t,trigger:(...t)=>Promise.all(Array.from(e).map(e=>e(...t))),clear:()=>{e.clear()}}}function Mh(e){let t=!1,n,r=vt(!0);return(...i)=>(t||=(n=r.run(()=>e(...i)),!0),n)}function Nh(e){let t=0,n,r,i=()=>{--t,r&&t<=0&&(r.stop(),n=void 0,r=void 0)};return(...a)=>(t+=1,r||(r=vt(!0),n=r.run(()=>e(...a))),Ah(i),n)}var Ph=typeof window<`u`&&typeof document<`u`;typeof WorkerGlobalScope<`u`&&globalThis instanceof WorkerGlobalScope;var Fh=e=>e!==void 0,Ih=Object.prototype.toString,Lh=e=>Ih.call(e)===`[object Object]`,Rh=()=>{},zh=Bh();function Bh(){return Ph&&(window==null?void 0:window.navigator)?.userAgent&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||(window==null?void 0:window.navigator)?.maxTouchPoints>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function Vh(e){return e||fc()}function Hh(e){return Array.isArray(e)?e:[e]}function Uh(e,t=1e4){return ar((n,r)=>{let i=tr(e),a,o=()=>setTimeout(()=>{i=tr(e),r()},tr(t));return Ah(()=>{clearTimeout(a)}),{get(){return n(),i},set(e){i=e,r(),clearTimeout(a),a=o()}}})}function Wh(e,t){Vh(t)&&Na(e,t)}function Gh(e,t=!0,n){Vh()?Aa(e,n):t?e():zr(e)}function Kh(e,t,n={}){let{immediate:r=!0,immediateCallback:i=!1}=n,a=Zn(!1),o=null;function s(){o&&=(clearTimeout(o),null)}function c(){a.value=!1,s()}function l(...n){i&&e(),s(),a.value=!0,o=setTimeout(()=>{a.value=!1,o=null,e(...n)},tr(t))}return r&&(a.value=!0,Ph&&l()),Ah(c),{isPending:zn(a),start:l,stop:c}}function qh(e=1e3,t={}){let{controls:n=!1,callback:r}=t,i=Kh(r??Rh,e,t),a=W(()=>!i.isPending.value);return n?{ready:a,...i}:a}function Jh(e,t,n){return pi(e,t,{...n,immediate:!0})}var Yh=Ph?window:void 0;Ph&&window.document,Ph&&window.navigator,Ph&&window.location;function Xh(e){let t=tr(e);return t?.$el??t}function Zh(...e){let t=[],n=()=>{t.forEach(e=>e()),t.length=0},r=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),i=W(()=>{let t=Hh(tr(e[0])).filter(e=>e!=null);return t.every(e=>typeof e!=`string`)?t:void 0}),a=Jh(()=>[i.value?.map(e=>Xh(e))??[Yh].filter(e=>e!=null),Hh(tr(i.value?e[1]:e[0])),Hh(M(i.value?e[2]:e[1])),tr(i.value?e[3]:e[2])],([e,i,a,o])=>{if(n(),!e?.length||!i?.length||!a?.length)return;let s=Lh(o)?{...o}:o;t.push(...e.flatMap(e=>i.flatMap(t=>a.map(n=>r(e,t,n,s)))))},{flush:`post`});return Ah(n),()=>{a(),n()}}function Qh(){let e=Zn(!1),t=fc();return t&&Aa(()=>{e.value=!0},t),e}function $h(e){let t=Qh();return W(()=>(t.value,!!e()))}function eg(e){return typeof e==`function`?e:typeof e==`string`?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function tg(...e){let t,n,r={};e.length===3?(t=e[0],n=e[1],r=e[2]):e.length===2?typeof e[1]==`object`?(t=!0,n=e[0],r=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);let{target:i=Yh,eventName:a=`keydown`,passive:o=!1,dedupe:s=!1}=r,c=eg(t);return Zh(i,a,e=>{e.repeat&&tr(s)||c(e)&&n(e)},o)}function ng(e,t={}){let{immediate:n=!0,fpsLimit:r=void 0,window:i=Yh,once:a=!1}=t,o=Zn(!1),s=W(()=>r?1e3/tr(r):null),c=0,l=null;function u(t){if(!o.value||!i)return;c||=t;let n=t-c;if(s.value&&nnull,()=>e?Xh(e):t.proxy.$el);return Ma(n.trigger),Aa(n.trigger),n}function ag(e,t,n={}){let{window:r=Yh,...i}=n,a,o=$h(()=>r&&`ResizeObserver`in r),s=()=>{a&&=(a.disconnect(),void 0)},c=pi(W(()=>{let t=tr(e);return Array.isArray(t)?t.map(e=>Xh(e)):[Xh(t)]}),e=>{if(s(),o.value&&r){a=new ResizeObserver(t);for(let t of e)t&&a.observe(t,i)}},{immediate:!0,flush:`post`}),l=()=>{s(),c()};return Ah(l),{isSupported:o,stop:l}}function og(e=ig()){let t=Zn(),n=()=>{let n=Xh(e);n&&(t.value=n.parentElement)};return Gh(n),pi(()=>tr(e),n),t}function sg(e,t,n,r={}){let{clone:i=!1,passive:a=!1,eventName:o,deep:s=!1,defaultValue:c,shouldEmit:l}=r,u=fc(),d=n||u?.emit||(u?.$emit)?.bind(u)||(u?.proxy?.$emit)?.bind(u?.proxy),f=o;t||=`modelValue`,f||=`update:${t.toString()}`;let p=e=>i?typeof i==`function`?i(e):rg(e):e,m=()=>Fh(e[t])?p(e[t]):c,h=e=>{l?l(e)&&d(f,e):d(f,e)};if(a){let n=j(m()),r=!1;return pi(()=>e[t],e=>{r||(r=!0,n.value=p(e),zr(()=>r=!1))}),pi(n,n=>{!r&&(n!==e[t]||s)&&h(n)},{deep:s}),n}else return W({get(){return m()},set(e){h(e)}})}var cg=Nh(()=>{let e=j(new Map),t=j(),n=W(()=>{for(let t of e.value.values())if(t)return!0;return!1}),r=Th({scrollBody:j(!0)}),i=null,a=()=>{document.body.style.paddingRight=``,document.body.style.marginRight=``,document.body.style.pointerEvents=``,document.documentElement.style.removeProperty(`--scrollbar-width`),document.body.style.overflow=t.value??``,zh&&i?.(),t.value=void 0};return pi(n,(e,n)=>{if(!Ph)return;if(!e){n&&a();return}t.value===void 0&&(t.value=document.body.style.overflow);let o=window.innerWidth-document.documentElement.clientWidth,s={padding:o,margin:0},c=r.scrollBody?.value?typeof r.scrollBody.value==`object`?Af({padding:r.scrollBody.value.padding===!0?o:r.scrollBody.value.padding,margin:r.scrollBody.value.margin===!0?o:r.scrollBody.value.margin},s):s:{padding:0,margin:0};o>0&&(document.body.style.paddingRight=typeof c.padding==`number`?`${c.padding}px`:String(c.padding),document.body.style.marginRight=typeof c.margin==`number`?`${c.margin}px`:String(c.margin),document.documentElement.style.setProperty(`--scrollbar-width`,`${o}px`),document.body.style.overflow=`hidden`),zh&&(i=Zh(document,`touchmove`,e=>dg(e),{passive:!1})),zr(()=>{document.body.style.pointerEvents=`none`,document.body.style.overflow=`hidden`})},{immediate:!0,flush:`sync`}),e});function lg(e){let t=Math.random().toString(36).substring(2,7),n=cg();n.value.set(t,e??!1);let r=W({get:()=>n.value.get(t)??!1,set:e=>n.value.set(t,e)});return Wh(()=>{n.value.delete(t)}),r}function ug(e){let t=window.getComputedStyle(e);if(t.overflowX===`scroll`||t.overflowY===`scroll`||t.overflowX===`auto`&&e.clientWidth1?!0:(t.preventDefault&&t.cancelable&&t.preventDefault(),!1)}function fg(e){let t=Th({dir:j(`ltr`)});return W(()=>e?.value||t.dir?.value||`ltr`)}function pg(e){let t=fc(),n=t?.type.emits,r={};return n?.length||console.warn(`No emitted event found. Please check component: ${t?.type.__name}`),n?.forEach(t=>{r[We(Be(t))]=(...n)=>e(t,...n)}),r}function mg(e){let t=W(()=>M(e)),n=W(()=>new Intl.Collator(`en`,{usage:`search`,...t.value}));return{startsWith:(e,t)=>t.length===0?!0:(e=e.normalize(`NFC`),t=t.normalize(`NFC`),n.value.compare(e.slice(0,t.length),t)===0),endsWith:(e,t)=>t.length===0?!0:(e=e.normalize(`NFC`),t=t.normalize(`NFC`),n.value.compare(e.slice(-t.length),t)===0),contains:(e,t)=>{if(t.length===0)return!0;e=e.normalize(`NFC`),t=t.normalize(`NFC`);let r=0,i=t.length;for(;r+i<=e.length;r++){let a=e.slice(r,r+i);if(n.value.compare(t,a)===0)return!0}return!1}}}var hg=0;function gg(){ui(e=>{if(!Ph)return;let t=document.querySelectorAll(`[data-reka-focus-guard]`);document.body.insertAdjacentElement(`afterbegin`,t[0]??_g()),document.body.insertAdjacentElement(`beforeend`,t[1]??_g()),hg++,e(()=>{hg===1&&document.querySelectorAll(`[data-reka-focus-guard]`).forEach(e=>e.remove()),hg--})})}function _g(){let e=document.createElement(`span`);return e.setAttribute(`data-reka-focus-guard`,``),e.tabIndex=0,e.style.outline=`none`,e.style.opacity=`0`,e.style.position=`fixed`,e.style.pointerEvents=`none`,e}function vg(e){return W(()=>tr(e)?!!Xh(e)?.closest(`form`):!0)}function G(){let e=fc(),t=j(),n=W(()=>[`#text`,`#comment`].includes(t.value?.$el.nodeName)?t.value?.$el.nextElementSibling:Xh(t)),r=Object.assign({},e.exposed),i={};for(let t in e.props)Object.defineProperty(i,t,{enumerable:!0,configurable:!0,get:()=>e.props[t]});if(Object.keys(r).length>0)for(let e in r)Object.defineProperty(i,e,{enumerable:!0,configurable:!0,get:()=>r[e]});Object.defineProperty(i,`$el`,{enumerable:!0,configurable:!0,get:()=>e.vnode.el}),e.exposed=i;function a(n){t.value=n,n&&(Object.defineProperty(i,`$el`,{enumerable:!0,configurable:!0,get:()=>n instanceof Element?n:n.$el}),e.exposed=i)}return{forwardRef:a,currentRef:t,currentElement:n}}function yg(e){let t=fc(),n=Object.keys(t?.type.props??{}).reduce((e,n)=>{let r=(t?.type.props[n]).default;return r!==void 0&&(e[n]=r),e},{}),r=lr(e);return W(()=>{let e={},i=t?.vnode.props??{};return Object.keys(i).forEach(t=>{e[Be(t)]=i[t]}),Object.keys({...n,...e}).reduce((e,t)=>(r.value[t]!==void 0&&(e[t]=r.value[t]),e),{})})}function bg(e,t){let n=yg(e),r=t?pg(t):{};return W(()=>({...n.value,...r}))}function xg(e,t){let n=Uh(!1,300),r=j(null),i=jh();function a(){r.value=null,n.value=!1}function o(e,t){let i=e.currentTarget,a={x:e.clientX,y:e.clientY},o=Cg(a,Sg(a,i.getBoundingClientRect())),s=wg(t.getBoundingClientRect());r.value=Eg([...o,...s]),n.value=!0}return ui(n=>{if(e.value&&t.value){let r=e=>o(e,t.value),i=t=>o(t,e.value);e.value.addEventListener(`pointerleave`,r),t.value.addEventListener(`pointerleave`,i),n(()=>{e.value?.removeEventListener(`pointerleave`,r),t.value?.removeEventListener(`pointerleave`,i)})}}),ui(n=>{if(r.value){let o=n=>{if(!r.value||!(n.target instanceof Element))return;let o=n.target,s={x:n.clientX,y:n.clientY},c=e.value?.contains(o)||t.value?.contains(o),l=!Tg(s,r.value),u=!!o.closest(`[data-grace-area-trigger]`);c?a():(l||u)&&(a(),i.trigger())};e.value?.ownerDocument.addEventListener(`pointermove`,o),n(()=>e.value?.ownerDocument.removeEventListener(`pointermove`,o))}}),{isPointerInTransit:n,onPointerExit:i.on}}function Sg(e,t){let n=Math.abs(t.top-e.y),r=Math.abs(t.bottom-e.y),i=Math.abs(t.right-e.x),a=Math.abs(t.left-e.x);switch(Math.min(n,r,i,a)){case a:return`left`;case i:return`right`;case n:return`top`;case r:return`bottom`;default:throw Error(`unreachable`)}}function Cg(e,t,n=5){let r=[];switch(t){case`top`:r.push({x:e.x-n,y:e.y+n},{x:e.x+n,y:e.y+n});break;case`bottom`:r.push({x:e.x-n,y:e.y-n},{x:e.x+n,y:e.y-n});break;case`left`:r.push({x:e.x+n,y:e.y-n},{x:e.x+n,y:e.y+n});break;case`right`:r.push({x:e.x-n,y:e.y-n},{x:e.x-n,y:e.y+n});break}return r}function wg(e){let{top:t,right:n,bottom:r,left:i}=e;return[{x:i,y:t},{x:n,y:t},{x:n,y:r},{x:i,y:r}]}function Tg(e,t){let{x:n,y:r}=e,i=!1;for(let e=0,a=t.length-1;er!=l>r&&n<(c-o)*(r-s)/(l-s)+o&&(i=!i)}return i}function Eg(e){let t=e.slice();return t.sort((e,t)=>e.xt.x?1:e.yt.y?1:0),Dg(t)}function Dg(e){if(e.length<=1)return e.slice();let t=[];for(let n=0;n=2;){let e=t[t.length-1],n=t[t.length-2];if((e.x-n.x)*(r.y-n.y)>=(e.y-n.y)*(r.x-n.x))t.pop();else break}t.push(r)}t.pop();let n=[];for(let t=e.length-1;t>=0;t--){let r=e[t];for(;n.length>=2;){let e=n[n.length-1],t=n[n.length-2];if((e.x-t.x)*(r.y-t.y)>=(e.y-t.y)*(r.x-t.x))n.pop();else break}n.push(r)}return n.pop(),t.length===1&&n.length===1&&t[0].x===n[0].x&&t[0].y===n[0].y?t:t.concat(n)}var Og=function(e){return typeof document>`u`?null:(Array.isArray(e)?e[0]:e).ownerDocument.body},kg=new WeakMap,Ag=new WeakMap,jg={},Mg=0,Ng=function(e){return e&&(e.host||Ng(e.parentNode))},Pg=function(e,t){return t.map(function(t){if(e.contains(t))return t;var n=Ng(t);return n&&e.contains(n)?n:(console.error(`aria-hidden`,t,`in not contained inside`,e,`. Doing nothing`),null)}).filter(function(e){return!!e})},Fg=function(e,t,n,r){var i=Pg(t,Array.isArray(e)?e:[e]);jg[n]||(jg[n]=new WeakMap);var a=jg[n],o=[],s=new Set,c=new Set(i),l=function(e){!e||s.has(e)||(s.add(e),l(e.parentNode))};i.forEach(l);var u=function(e){!e||c.has(e)||Array.prototype.forEach.call(e.children,function(e){if(s.has(e))u(e);else try{var t=e.getAttribute(r),i=t!==null&&t!==`false`,c=(kg.get(e)||0)+1,l=(a.get(e)||0)+1;kg.set(e,c),a.set(e,l),o.push(e),c===1&&i&&Ag.set(e,!0),l===1&&e.setAttribute(n,`true`),i||e.setAttribute(r,`true`)}catch(t){console.error(`aria-hidden: cannot operate on `,e,t)}})};return u(t),s.clear(),Mg++,function(){o.forEach(function(e){var t=kg.get(e)-1,i=a.get(e)-1;kg.set(e,t),a.set(e,i),t||(Ag.has(e)||e.removeAttribute(r),Ag.delete(e)),i||e.removeAttribute(n)}),Mg--,Mg||(kg=new WeakMap,kg=new WeakMap,Ag=new WeakMap,jg={})}},Ig=function(e,t,n){n===void 0&&(n=`data-aria-hidden`);var r=Array.from(Array.isArray(e)?e:[e]),i=t||Og(e);return i?(r.push.apply(r,Array.from(i.querySelectorAll(`[aria-live], script`))),Fg(r,i,n,`aria-hidden`)):function(){return null}};function Lg(e){let t;pi(()=>Xh(e),e=>{e?t=Ig(e):t&&t()}),Pa(()=>{t&&t()})}var Rg=0;function zg(e,t=`reka`){if(e)return e;if(`useId`in Ju)return`${t}-${Gi?.()}`;let n=Th({useId:void 0});return n.useId?`${t}-${n.useId()}`:`${t}-${++Rg}`}function Bg(){return{ALT:`Alt`,ARROW_DOWN:`ArrowDown`,ARROW_LEFT:`ArrowLeft`,ARROW_RIGHT:`ArrowRight`,ARROW_UP:`ArrowUp`,BACKSPACE:`Backspace`,CAPS_LOCK:`CapsLock`,CONTROL:`Control`,DELETE:`Delete`,END:`End`,ENTER:`Enter`,ESCAPE:`Escape`,F1:`F1`,F10:`F10`,F11:`F11`,F12:`F12`,F2:`F2`,F3:`F3`,F4:`F4`,F5:`F5`,F6:`F6`,F7:`F7`,F8:`F8`,F9:`F9`,HOME:`Home`,META:`Meta`,PAGE_DOWN:`PageDown`,PAGE_UP:`PageUp`,SHIFT:`Shift`,SPACE:` `,TAB:`Tab`,CTRL:`Control`,ASTERISK:`*`,SPACE_CODE:`Space`}}function Vg(e){let t=j(),n=W(()=>t.value?.width??0),r=W(()=>t.value?.height??0);return Aa(()=>{let n=Xh(e);if(n){t.value={width:n.offsetWidth,height:n.offsetHeight};let e=new ResizeObserver(e=>{if(!Array.isArray(e)||!e.length)return;let r=e[0],i,a;if(`borderBoxSize`in r){let e=r.borderBoxSize,t=Array.isArray(e)?e[0]:e;i=t.inlineSize,a=t.blockSize}else i=n.offsetWidth,a=n.offsetHeight;t.value={width:i,height:a}});return e.observe(n,{box:`border-box`}),()=>e.unobserve(n)}else t.value=void 0}),{width:n,height:r}}function Hg(e,t){let n=j(e);function r(e){return t[n.value][e]??n.value}return{state:n,dispatch:e=>{n.value=r(e)}}}function Ug(e){let t=Uh(``,1e3);return{search:t,handleTypeaheadSearch:(n,r)=>{if(t.value+=n,e)e(n);else{let e=xh(),n=r.map(e=>({...e,textValue:e.value?.textValue??e.ref.textContent?.trim()??``})),i=n.find(t=>t.ref===e),a=Gg(n.map(e=>e.textValue),t.value,i?.textValue),o=n.find(e=>e.textValue===a);return o&&o.ref.focus(),o?.ref}},resetTypeahead:()=>{t.value=``}}}function Wg(e,t){return e.map((n,r)=>e[(t+r)%e.length])}function Gg(e,t,n){let r=t.length>1&&Array.from(t).every(e=>e===t[0])?t[0]:t,i=n?e.indexOf(n):-1,a=Wg(e,Math.max(i,0));r.length===1&&(a=a.filter(e=>e!==n));let o=a.find(e=>e.toLowerCase().startsWith(r.toLowerCase()));return o===n?void 0:o}function Kg(e,t){let n=j({}),r=j(`none`),i=j(e),a=e.value?`mounted`:`unmounted`,o,s=t.value?.ownerDocument.defaultView??Yh,{state:c,dispatch:l}=Hg(a,{mounted:{UNMOUNT:`unmounted`,ANIMATION_OUT:`unmountSuspended`},unmountSuspended:{MOUNT:`mounted`,ANIMATION_END:`unmounted`},unmounted:{MOUNT:`mounted`}}),u=e=>{if(Ph){let n=new CustomEvent(e,{bubbles:!1,cancelable:!1});t.value?.dispatchEvent(n)}};pi(e,async(e,i)=>{let a=i!==e;if(await zr(),a){let a=r.value,o=qg(t.value);e?(l(`MOUNT`),u(`enter`),o===`none`&&u(`after-enter`)):o===`none`||o===`undefined`||n.value?.display===`none`?(l(`UNMOUNT`),u(`leave`),u(`after-leave`)):i&&a!==o?(l(`ANIMATION_OUT`),u(`leave`)):(l(`UNMOUNT`),u(`after-leave`))}},{immediate:!0});let d=e=>{let n=qg(t.value),r=n.includes(CSS.escape(e.animationName)),a=c.value===`mounted`?`enter`:`leave`;if(e.target===t.value&&r&&(u(`after-${a}`),l(`ANIMATION_END`),!i.value)){let e=t.value.style.animationFillMode;t.value.style.animationFillMode=`forwards`,o=s?.setTimeout(()=>{t.value?.style.animationFillMode===`forwards`&&(t.value.style.animationFillMode=e)})}e.target===t.value&&n===`none`&&l(`ANIMATION_END`)},f=e=>{e.target===t.value&&(r.value=qg(t.value))},p=pi(t,(e,t)=>{e?(n.value=getComputedStyle(e),e.addEventListener(`animationstart`,f),e.addEventListener(`animationcancel`,d),e.addEventListener(`animationend`,d)):(l(`ANIMATION_END`),o!==void 0&&s?.clearTimeout(o),t?.removeEventListener(`animationstart`,f),t?.removeEventListener(`animationcancel`,d),t?.removeEventListener(`animationend`,d))},{immediate:!0}),m=pi(c,()=>{let e=qg(t.value);r.value=c.value===`mounted`?e:`none`});return Pa(()=>{p(),m()}),{isPresent:W(()=>[`mounted`,`unmountSuspended`].includes(c.value))}}function qg(e){return e&&getComputedStyle(e).animationName||`none`}var Jg=P({name:`Presence`,props:{present:{type:Boolean,required:!0},forceMount:{type:Boolean}},slots:{},setup(e,{slots:t,expose:n}){let{present:r,forceMount:i}=or(e),a=j(),{isPresent:o}=Kg(r,a);n({present:o});let s=t.default({present:o.value});s=wh(s||[]);let c=fc();if(s&&s?.length>1){let e=c?.parent?.type.name?`<${c.parent.type.name} />`:`component`;throw Error([`Detected an invalid children for \`${e}\` for \`Presence\` component.`,``,"Note: Presence works similarly to `v-if` directly, but it waits for animation/transition to finished before unmounting. So it expect only one direct child of valid VNode type.",`You can apply a few solutions:`,["Provide a single child element so that `presence` directive attach correctly.",`Ensure the first child is an actual element instead of a raw text node or comment node.`].map(e=>` - ${e}`).join(` +`)].join(` +`))}return()=>i.value||r.value||o.value?Mc(t.default({present:o.value})[0],{ref:e=>{let t=Xh(e);return t?.hasAttribute===void 0||(t?.hasAttribute(`data-reka-popper-content-wrapper`)?a.value=t.firstElementChild:a.value=t),t}}):null}}),Yg=P({name:`PrimitiveSlot`,inheritAttrs:!1,setup(e,{attrs:t,slots:n}){return()=>{if(!n.default)return null;let e=wh(n.default()),r=e.findIndex(e=>e.type!==Bs);if(r===-1)return e;let i=e[r];delete i.props?.ref;let a=i.props?U(t,i.props):t,o=tc({...i,props:{}},a);return e.length===1?o:(e[r]=o,e)}}}),Xg=[`area`,`img`,`input`],K=P({name:`Primitive`,inheritAttrs:!1,props:{asChild:{type:Boolean,default:!1},as:{type:[String,Object],default:`div`}},setup(e,{attrs:t,slots:n}){let r=e.asChild?`template`:e.as;return typeof r==`string`&&Xg.includes(r)?()=>Mc(r,t):r===`template`?()=>Mc(Yg,t,{default:n.default}):()=>Mc(e.as,t,{default:n.default})}});function Zg(){let e=j();return{primitiveElement:e,currentElement:W(()=>[`#text`,`#comment`].includes(e.value?.$el.nodeName)?e.value?.$el.nextElementSibling:Xh(e))}}var[Qg,$g]=bh(`CollapsibleRoot`),e_=P({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:n}){let r=e,i=sg(r,`open`,n,{defaultValue:r.defaultOpen,passive:r.open===void 0}),{disabled:a,unmountOnHide:o}=or(r);return $g({contentId:``,disabled:a,open:i,unmountOnHide:o,onOpenToggle:()=>{a.value||(i.value=!i.value)}}),t({open:i}),G(),(e,t)=>(L(),z(M(K),{as:e.as,"as-child":r.asChild,"data-state":M(i)?`open`:`closed`,"data-disabled":M(a)?``:void 0},{default:N(()=>[F(e.$slots,`default`,{open:M(i)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),t_=P({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(e,{emit:t}){let n=e,r=t,i=Qg();i.contentId||=zg(void 0,`reka-collapsible-content`);let a=j(),{forwardRef:o,currentElement:s}=G(),c=j(0),l=j(0),u=W(()=>i.open.value),d=j(u.value),f=j();pi(()=>[u.value,a.value?.present],async()=>{await zr();let e=s.value;if(!e)return;f.value=f.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();l.value=t.height,c.value=t.width,d.value||(e.style.transitionDuration=f.value.transitionDuration,e.style.animationName=f.value.animationName)},{immediate:!0});let p=W(()=>d.value&&i.open.value);return Aa(()=>{requestAnimationFrame(()=>{d.value=!1})}),Zh(s,`beforematch`,e=>{requestAnimationFrame(()=>{i.onOpenToggle(),r(`contentFound`)})}),(e,t)=>(L(),z(M(Jg),{ref_key:`presentRef`,ref:a,present:e.forceMount||M(i).open.value,"force-mount":!0},{default:N(({present:t})=>[V(M(K),U(e.$attrs,{id:M(i).contentId,ref:M(o),"as-child":n.asChild,as:e.as,hidden:t?void 0:M(i).unmountOnHide.value?``:`until-found`,"data-state":p.value?void 0:M(i).open.value?`open`:`closed`,"data-disabled":M(i).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${l.value}px`,"--reka-collapsible-content-width":`${c.value}px`}}),{default:N(()=>[!M(i).unmountOnHide.value||t?F(e.$slots,`default`,{key:0}):H(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),n_=P({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e;G();let n=Qg();return(e,r)=>(L(),z(M(K),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":t.asChild,"aria-controls":M(n).contentId,"aria-expanded":M(n).open.value,"data-state":M(n).open.value?`open`:`closed`,"data-disabled":M(n).disabled?.value?``:void 0,disabled:M(n).disabled?.value,onClick:M(n).onOpenToggle},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),[r_,i_]=bh(`DialogRoot`),a_=P({inheritAttrs:!1,__name:`DialogRoot`,props:{open:{type:Boolean,required:!1,default:void 0},defaultOpen:{type:Boolean,required:!1,default:!1},modal:{type:Boolean,required:!1,default:!0}},emits:[`update:open`],setup(e,{emit:t}){let n=e,r=sg(n,`open`,t,{defaultValue:n.defaultOpen,passive:n.open===void 0}),i=j(),a=j(),{modal:o}=or(n);return i_({open:r,modal:o,openModal:()=>{r.value=!0},onOpenChange:e=>{r.value=e},onOpenToggle:()=>{r.value=!r.value},contentId:``,titleId:``,descriptionId:``,triggerElement:i,contentElement:a}),(e,t)=>F(e.$slots,`default`,{open:M(r),close:()=>r.value=!1})}}),o_=P({__name:`DialogClose`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e;G();let n=r_();return(e,r)=>(L(),z(M(K),U(t,{type:e.as===`button`?`button`:void 0,onClick:r[0]||=e=>M(n).onOpenChange(!1)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`type`]))}}),s_=`dismissableLayer.pointerDownOutside`,c_=`dismissableLayer.focusOutside`;function l_(e,t){let n=t.closest(`[data-dismissable-layer]`),r=e.dataset.dismissableLayer===``?e:e.querySelector(`[data-dismissable-layer]`),i=Array.from(e.ownerDocument.querySelectorAll(`[data-dismissable-layer]`));return!!(n&&(r===n||i.indexOf(r){});return ui(o=>{if(!Ph||!tr(n))return;let s=async n=>{let o=n.target;if(!(!t?.value||!o)){if(l_(t.value,o)){i.value=!1;return}if(n.target&&!i.value){let t={originalEvent:n};function i(){Sh(s_,e,t)}n.pointerType===`touch`?(r.removeEventListener(`click`,a.value),a.value=i,r.addEventListener(`click`,a.value,{once:!0})):i()}else r.removeEventListener(`click`,a.value);i.value=!1}},c=window.setTimeout(()=>{r.addEventListener(`pointerdown`,s)},0);o(()=>{window.clearTimeout(c),r.removeEventListener(`pointerdown`,s),r.removeEventListener(`click`,a.value)})}),{onPointerDownCapture:()=>{tr(n)&&(i.value=!0)}}}function d_(e,t,n=!0){let r=t?.value?.ownerDocument??globalThis?.document,i=j(!1);return ui(a=>{if(!Ph||!tr(n))return;let o=async n=>{if(!t?.value)return;await zr(),await zr();let r=n.target;!t.value||!r||l_(t.value,r)||n.target&&!i.value&&Sh(c_,e,{originalEvent:n})};r.addEventListener(`focusin`,o),a(()=>r.removeEventListener(`focusin`,o))}),{onFocusCapture:()=>{tr(n)&&(i.value=!0)},onBlurCapture:()=>{tr(n)&&(i.value=!1)}}}var f_=Ln({layersRoot:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),p_=P({__name:`DismissableLayer`,props:{disableOutsidePointerEvents:{type:Boolean,required:!1,default:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`dismiss`],setup(e,{emit:t}){let n=e,r=t,{forwardRef:i,currentElement:a}=G(),o=W(()=>a.value?.ownerDocument??globalThis.document),s=W(()=>f_.layersRoot),c=W(()=>a.value?Array.from(s.value).indexOf(a.value):-1),l=W(()=>f_.layersWithOutsidePointerEventsDisabled.size>0),u=W(()=>{let e=Array.from(s.value),[t]=[...f_.layersWithOutsidePointerEventsDisabled].slice(-1),n=e.indexOf(t);return c.value>=n}),d=u_(async e=>{let t=[...f_.branches].some(t=>t?.contains(e.target));!u.value||t||(r(`pointerDownOutside`,e),r(`interactOutside`,e),await zr(),e.defaultPrevented||r(`dismiss`))},a),f=d_(e=>{[...f_.branches].some(t=>t?.contains(e.target))||(r(`focusOutside`,e),r(`interactOutside`,e),e.defaultPrevented||r(`dismiss`))},a);tg(`Escape`,e=>{c.value===s.value.size-1&&(r(`escapeKeyDown`,e),e.defaultPrevented||r(`dismiss`))});let p;return ui(e=>{a.value&&(n.disableOutsidePointerEvents&&(f_.layersWithOutsidePointerEventsDisabled.size===0&&(p=o.value.body.style.pointerEvents,o.value.body.style.pointerEvents=`none`),f_.layersWithOutsidePointerEventsDisabled.add(a.value)),s.value.add(a.value),e(()=>{n.disableOutsidePointerEvents&&f_.layersWithOutsidePointerEventsDisabled.size===1&&(o.value.body.style.pointerEvents=p)}))}),ui(e=>{e(()=>{a.value&&(s.value.delete(a.value),f_.layersWithOutsidePointerEventsDisabled.delete(a.value))})}),(e,t)=>(L(),z(M(K),{ref:M(i),"as-child":e.asChild,as:e.as,"data-dismissable-layer":``,style:$e({pointerEvents:l.value?u.value?`auto`:`none`:void 0}),onFocusCapture:M(f).onFocusCapture,onBlurCapture:M(f).onBlurCapture,onPointerdownCapture:M(d).onPointerDownCapture},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as-child`,`as`,`style`,`onFocusCapture`,`onBlurCapture`,`onPointerdownCapture`]))}}),m_=P({__name:`DismissableLayerBranch`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,{forwardRef:n,currentElement:r}=G();return Aa(()=>{f_.branches.add(r.value)}),Pa(()=>{f_.branches.delete(r.value)}),(e,r)=>(L(),z(M(K),U({ref:M(n)},t),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),h_=Mh(()=>j([]));function g_(){let e=h_();return{add(t){let n=e.value[0];t!==n&&n?.pause(),e.value=__(e.value,t),e.value.unshift(t)},remove(t){e.value=__(e.value,t),e.value[0]?.resume()}}}function __(e,t){let n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function v_(e){return e.filter(e=>e.tagName!==`A`)}var y_=`focusScope.autoFocusOnMount`,b_=`focusScope.autoFocusOnUnmount`,x_={bubbles:!1,cancelable:!0};function S_(e,{select:t=!1}={}){let n=xh();for(let r of e)if(O_(r,{select:t}),xh()!==n)return!0}function C_(e){let t=w_(e);return[T_(t,e),T_(t.reverse(),e)]}function w_(e){let t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:e=>{let t=e.tagName===`INPUT`&&e.type===`hidden`;return e.disabled||e.hidden||t?NodeFilter.FILTER_SKIP:e.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function T_(e,t){for(let n of e)if(!E_(n,{upTo:t}))return n}function E_(e,{upTo:t}){if(getComputedStyle(e).visibility===`hidden`)return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display===`none`)return!0;e=e.parentElement}return!1}function D_(e){return e instanceof HTMLInputElement&&`select`in e}function O_(e,{select:t=!1}={}){if(e&&e.focus){let n=xh();e.focus({preventScroll:!0}),e!==n&&D_(e)&&t&&e.select()}}var k_=P({__name:`FocusScope`,props:{loop:{type:Boolean,required:!1,default:!1},trapped:{type:Boolean,required:!1,default:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`mountAutoFocus`,`unmountAutoFocus`],setup(e,{emit:t}){let n=e,r=t,{currentRef:i,currentElement:a}=G(),o=j(null),s=g_(),c=Ln({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}});ui(e=>{if(!Ph)return;let t=a.value;if(!n.trapped)return;function r(e){if(c.paused||!t)return;let n=e.target;t.contains(n)?o.value=n:O_(o.value,{select:!0})}function i(e){if(c.paused||!t)return;let n=e.relatedTarget;n!==null&&(t.contains(n)||O_(o.value,{select:!0}))}function s(e){t.contains(o.value)||O_(t)}document.addEventListener(`focusin`,r),document.addEventListener(`focusout`,i);let l=new MutationObserver(s);t&&l.observe(t,{childList:!0,subtree:!0}),e(()=>{document.removeEventListener(`focusin`,r),document.removeEventListener(`focusout`,i),l.disconnect()})}),ui(async e=>{let t=a.value;if(await zr(),!t)return;s.add(c);let n=xh();if(!t.contains(n)){let e=new CustomEvent(y_,x_);t.addEventListener(y_,e=>r(`mountAutoFocus`,e)),t.dispatchEvent(e),e.defaultPrevented||(S_(v_(w_(t)),{select:!0}),xh()===n&&O_(t))}e(()=>{t.removeEventListener(y_,e=>r(`mountAutoFocus`,e));let e=new CustomEvent(b_,x_),i=e=>{r(`unmountAutoFocus`,e)};t.addEventListener(b_,i),t.dispatchEvent(e),setTimeout(()=>{e.defaultPrevented||O_(n??document.body,{select:!0}),t.removeEventListener(b_,i),s.remove(c)},0)})});function l(e){if(!n.loop&&!n.trapped||c.paused)return;let t=e.key===`Tab`&&!e.altKey&&!e.ctrlKey&&!e.metaKey,r=xh();if(t&&r){let t=e.currentTarget,[i,a]=C_(t);i&&a?!e.shiftKey&&r===a?(e.preventDefault(),n.loop&&O_(i,{select:!0})):e.shiftKey&&r===i&&(e.preventDefault(),n.loop&&O_(a,{select:!0})):r===t&&e.preventDefault()}}return(e,t)=>(L(),z(M(K),{ref_key:`currentRef`,ref:i,tabindex:`-1`,"as-child":e.asChild,as:e.as,onKeydown:l},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as-child`,`as`]))}}),A_=[`Enter`,` `],j_=[`ArrowDown`,`PageUp`,`Home`],M_=[`ArrowUp`,`PageDown`,`End`];[...j_,...M_],[...A_],[...A_];function N_(e){return e?`open`:`closed`}function P_(e){let t=xh();for(let n of e)if(n===t||(n.focus(),xh()!==t))return}var F_=P({__name:`DialogContentImpl`,props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`],setup(e,{emit:t}){let n=e,r=t,i=r_(),{forwardRef:a,currentElement:o}=G();return i.titleId||=zg(void 0,`reka-dialog-title`),i.descriptionId||=zg(void 0,`reka-dialog-description`),Aa(()=>{i.contentElement=o,xh()!==document.body&&(i.triggerElement.value=xh())}),(e,t)=>(L(),z(M(k_),{"as-child":``,loop:``,trapped:n.trapFocus,onMountAutoFocus:t[5]||=e=>r(`openAutoFocus`,e),onUnmountAutoFocus:t[6]||=e=>r(`closeAutoFocus`,e)},{default:N(()=>[V(M(p_),U({id:M(i).contentId,ref:M(a),as:e.as,"as-child":e.asChild,"disable-outside-pointer-events":e.disableOutsidePointerEvents,role:`dialog`,"aria-describedby":M(i).descriptionId,"aria-labelledby":M(i).titleId,"data-state":M(N_)(M(i).open.value)},e.$attrs,{onDismiss:t[0]||=e=>M(i).onOpenChange(!1),onEscapeKeyDown:t[1]||=e=>r(`escapeKeyDown`,e),onFocusOutside:t[2]||=e=>r(`focusOutside`,e),onInteractOutside:t[3]||=e=>r(`interactOutside`,e),onPointerDownOutside:t[4]||=e=>r(`pointerDownOutside`,e)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`,`as`,`as-child`,`disable-outside-pointer-events`,`aria-describedby`,`aria-labelledby`,`data-state`])]),_:3},8,[`trapped`]))}}),I_=P({__name:`DialogContentModal`,props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`],setup(e,{emit:t}){let n=e,r=t,i=r_(),a=pg(r),{forwardRef:o,currentElement:s}=G();return Lg(s),(e,t)=>(L(),z(F_,U({...n,...M(a)},{ref:M(o),"trap-focus":M(i).open.value,"disable-outside-pointer-events":!0,onCloseAutoFocus:t[0]||=e=>{e.defaultPrevented||(e.preventDefault(),M(i).triggerElement.value?.focus())},onPointerDownOutside:t[1]||=e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0;(t.button===2||n)&&e.preventDefault()},onFocusOutside:t[2]||=e=>{e.preventDefault()}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`trap-focus`]))}}),L_=P({__name:`DialogContentNonModal`,props:{forceMount:{type:Boolean,required:!1},trapFocus:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`],setup(e,{emit:t}){let n=e,r=pg(t);G();let i=r_(),a=j(!1),o=j(!1);return(e,t)=>(L(),z(F_,U({...n,...M(r)},{"trap-focus":!1,"disable-outside-pointer-events":!1,onCloseAutoFocus:t[0]||=e=>{e.defaultPrevented||(a.value||M(i).triggerElement.value?.focus(),e.preventDefault()),a.value=!1,o.value=!1},onInteractOutside:t[1]||=e=>{e.defaultPrevented||(a.value=!0,e.detail.originalEvent.type===`pointerdown`&&(o.value=!0));let t=e.target;M(i).triggerElement.value?.contains(t)&&e.preventDefault(),e.detail.originalEvent.type===`focusin`&&o.value&&e.preventDefault()}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),R_=P({__name:`DialogContent`,props:{forceMount:{type:Boolean,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`,`openAutoFocus`,`closeAutoFocus`],setup(e,{emit:t}){let n=e,r=t,i=r_(),a=pg(r),{forwardRef:o}=G();return(e,t)=>(L(),z(M(Jg),{present:e.forceMount||M(i).open.value},{default:N(()=>[M(i).modal.value?(L(),z(I_,U({key:0,ref:M(o)},{...n,...M(a),...e.$attrs}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)):(L(),z(L_,U({key:1,ref:M(o)},{...n,...M(a),...e.$attrs}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))]),_:3},8,[`present`]))}}),z_=P({__name:`DialogDescription`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`p`}},setup(e){let t=e;G();let n=r_();return(e,r)=>(L(),z(M(K),U(t,{id:M(n).descriptionId}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`]))}}),B_=P({__name:`DialogOverlayImpl`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=r_();return lg(!0),G(),(e,n)=>(L(),z(M(K),{as:e.as,"as-child":e.asChild,"data-state":M(t).open.value?`open`:`closed`,style:{"pointer-events":`auto`}},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`,`data-state`]))}}),V_=P({__name:`DialogOverlay`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=r_(),{forwardRef:n}=G();return(e,r)=>M(t)?.modal.value?(L(),z(M(Jg),{key:0,present:e.forceMount||M(t).open.value},{default:N(()=>[V(B_,U(e.$attrs,{ref:M(n),as:e.as,"as-child":e.asChild}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`as`,`as-child`])]),_:3},8,[`present`])):H(`v-if`,!0)}}),H_=P({__name:`Teleport`,props:{to:{type:null,required:!1,default:`body`},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=Qh();return(e,n)=>M(t)||e.forceMount?(L(),z(Di,{key:0,to:e.to,disabled:e.disabled,defer:e.defer},[F(e.$slots,`default`)],8,[`to`,`disabled`,`defer`])):H(`v-if`,!0)}}),U_=P({__name:`DialogPortal`,props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(H_),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),W_=P({__name:`DialogTitle`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`h2`}},setup(e){let t=e,n=r_();return G(),(e,r)=>(L(),z(M(K),U(t,{id:M(n).titleId}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`]))}}),G_=P({__name:`DialogTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e,n=r_(),{forwardRef:r,currentElement:i}=G();return n.contentId||=zg(void 0,`reka-dialog-content`),Aa(()=>{n.triggerElement.value=i.value}),(e,i)=>(L(),z(M(K),U(t,{ref:M(r),type:e.as===`button`?`button`:void 0,"aria-haspopup":`dialog`,"aria-expanded":M(n).open.value||!1,"aria-controls":M(n).open.value?M(n).contentId:void 0,"data-state":M(n).open.value?`open`:`closed`,onClick:M(n).onOpenToggle}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`type`,`aria-expanded`,`aria-controls`,`data-state`,`onClick`]))}}),K_=`data-reka-collection-item`;function q_(e={}){let{key:t=``,isProvider:n=!1}=e,r=`${t}CollectionProvider`,i;n?(i={collectionRef:j(),itemMap:j(new Map)},ai(r,i)):i=oi(r);let a=(e=!1)=>{let t=i.collectionRef.value;if(!t)return[];let n=Array.from(t.querySelectorAll(`[${K_}]`)),r=Array.from(i.itemMap.value.values()).sort((e,t)=>n.indexOf(e.ref)-n.indexOf(t.ref));return e?r:r.filter(e=>e.ref.dataset.disabled!==``)},o=P({name:`CollectionSlot`,setup(e,{slots:t}){let{primitiveElement:n,currentElement:r}=Zg();return pi(r,()=>{i.collectionRef.value=r.value}),()=>Mc(Yg,{ref:n},t)}}),s=P({name:`CollectionItem`,inheritAttrs:!1,props:{value:{validator:()=>!0}},setup(e,{slots:t,attrs:n}){let{primitiveElement:r,currentElement:a}=Zg();return ui(t=>{if(a.value){let n=qn(a.value);i.itemMap.value.set(n,{ref:a.value,value:e.value}),t(()=>i.itemMap.value.delete(n))}}),()=>Mc(Yg,{...n,[K_]:``,ref:r},t)}});return{getItems:a,reactiveItems:W(()=>Array.from(i.itemMap.value.values())),itemMapSize:W(()=>i.itemMap.value.size),CollectionSlot:o,CollectionItem:s}}var J_={ArrowLeft:`prev`,ArrowUp:`prev`,ArrowRight:`next`,ArrowDown:`next`,PageUp:`first`,Home:`first`,PageDown:`last`,End:`last`};function Y_(e,t){return t===`rtl`?e===`ArrowLeft`?`ArrowRight`:e===`ArrowRight`?`ArrowLeft`:e:e}function X_(e,t,n){let r=Y_(e.key,n);if(!(t===`vertical`&&[`ArrowLeft`,`ArrowRight`].includes(r))&&!(t===`horizontal`&&[`ArrowUp`,`ArrowDown`].includes(r)))return J_[r]}var Z_=P({__name:`VisuallyHidden`,props:{feature:{type:String,required:!1,default:`focusable`},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){return(e,t)=>(L(),z(M(K),{as:e.as,"as-child":e.asChild,"aria-hidden":e.feature===`focusable`?`true`:void 0,"data-hidden":e.feature===`fully-hidden`?``:void 0,tabindex:e.feature===`fully-hidden`?`-1`:void 0,style:{position:`absolute`,border:0,width:`1px`,height:`1px`,padding:0,margin:`-1px`,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,clipPath:`inset(50%)`,whiteSpace:`nowrap`,wordWrap:`normal`,top:`-1px`,left:`-1px`}},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`,`aria-hidden`,`data-hidden`,`tabindex`]))}}),Q_=P({inheritAttrs:!1,__name:`VisuallyHiddenInputBubble`,props:{name:{type:String,required:!0},value:{type:null,required:!0},checked:{type:Boolean,required:!1,default:void 0},required:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},feature:{type:String,required:!1,default:`fully-hidden`}},setup(e){let t=e,{primitiveElement:n,currentElement:r}=Zg();return pi(W(()=>t.checked??t.value),(e,t)=>{if(!r.value)return;let n=r.value,i=window.HTMLInputElement.prototype,a=Object.getOwnPropertyDescriptor(i,`value`).set;if(a&&e!==t){let t=new Event(`input`,{bubbles:!0}),r=new Event(`change`,{bubbles:!0});a.call(n,e),n.dispatchEvent(t),n.dispatchEvent(r)}}),(e,r)=>(L(),z(Z_,U({ref_key:`primitiveElement`,ref:n},{...t,...e.$attrs},{as:`input`}),null,16))}}),$_=P({inheritAttrs:!1,__name:`VisuallyHiddenInput`,props:{name:{type:String,required:!0},value:{type:null,required:!0},checked:{type:Boolean,required:!1,default:void 0},required:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},feature:{type:String,required:!1,default:`fully-hidden`}},setup(e){let t=e,n=W(()=>typeof t.value==`object`&&Array.isArray(t.value)&&t.value.length===0&&t.required),r=W(()=>typeof t.value==`string`||typeof t.value==`number`||typeof t.value==`boolean`||t.value===null||t.value===void 0?[{name:t.name,value:t.value}]:typeof t.value==`object`&&Array.isArray(t.value)?t.value.flatMap((e,n)=>typeof e==`object`?Object.entries(e).map(([e,r])=>({name:`${t.name}[${n}][${e}]`,value:r})):{name:`${t.name}[${n}]`,value:e}):t.value!==null&&typeof t.value==`object`&&!Array.isArray(t.value)?Object.entries(t.value).map(([e,n])=>({name:`${t.name}[${e}]`,value:n})):[]);return(e,i)=>(L(),R(I,null,[H(` We render single input if it's required `),n.value?(L(),z(Q_,U({key:e.name},{...t,...e.$attrs},{name:e.name,value:e.value}),null,16,[`name`,`value`])):(L(!0),R(I,{key:1},qa(r.value,n=>(L(),z(Q_,U({key:n.name},{ref_for:!0},{...t,...e.$attrs},{name:n.name,value:n.value}),null,16,[`name`,`value`]))),128))],2112))}}),[ev,tv]=bh(`PopperRoot`),nv=P({inheritAttrs:!1,__name:`PopperRoot`,setup(e){let t=j();return tv({anchor:t,onAnchorChange:e=>t.value=e}),(e,t)=>F(e.$slots,`default`)}}),rv=P({__name:`PopperAnchor`,props:{reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,{forwardRef:n,currentElement:r}=G(),i=ev();return di(()=>{i.onAnchorChange(t.reference??r.value)}),(e,t)=>(L(),z(M(K),{ref:M(n),as:e.as,"as-child":e.asChild},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`]))}}),iv={key:0,d:`M0 0L6 6L12 0`},av={key:1,d:`M0 0L4.58579 4.58579C5.36683 5.36683 6.63316 5.36684 7.41421 4.58579L12 0`},ov=P({__name:`Arrow`,props:{width:{type:Number,required:!1,default:10},height:{type:Number,required:!1,default:5},rounded:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`svg`}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(K),U(t,{width:e.width,height:e.height,viewBox:e.asChild?void 0:`0 0 12 6`,preserveAspectRatio:e.asChild?void 0:`none`}),{default:N(()=>[F(e.$slots,`default`,{},()=>[e.rounded?(L(),R(`path`,av)):(L(),R(`path`,iv))])]),_:3},16,[`width`,`height`,`viewBox`,`preserveAspectRatio`]))}});function sv(e){return e!==null}function cv(e){return{name:`transformOrigin`,options:e,fn(t){let{placement:n,rects:r,middlewareData:i}=t,a=i.arrow?.centerOffset!==0,o=a?0:e.arrowWidth,s=a?0:e.arrowHeight,[c,l]=lv(n),u={start:`0%`,center:`50%`,end:`100%`}[l],d=(i.arrow?.x??0)+o/2,f=(i.arrow?.y??0)+s/2,p=``,m=``;return c===`bottom`?(p=a?u:`${d}px`,m=`${-s}px`):c===`top`?(p=a?u:`${d}px`,m=`${r.floating.height+s}px`):c===`right`?(p=`${-s}px`,m=a?u:`${f}px`):c===`left`&&(p=`${r.floating.width+s}px`,m=a?u:`${f}px`),{data:{x:p,y:m}}}}}function lv(e){let[t,n=`center`]=e.split(`-`);return[t,n]}var uv=[`top`,`right`,`bottom`,`left`],dv=Math.min,fv=Math.max,pv=Math.round,mv=Math.floor,hv=e=>({x:e,y:e}),gv={left:`right`,right:`left`,bottom:`top`,top:`bottom`},_v={start:`end`,end:`start`};function vv(e,t,n){return fv(e,dv(t,n))}function yv(e,t){return typeof e==`function`?e(t):e}function bv(e){return e.split(`-`)[0]}function xv(e){return e.split(`-`)[1]}function Sv(e){return e===`x`?`y`:`x`}function Cv(e){return e===`y`?`height`:`width`}function wv(e){return[`top`,`bottom`].includes(bv(e))?`y`:`x`}function Tv(e){return Sv(wv(e))}function Ev(e,t,n){n===void 0&&(n=!1);let r=xv(e),i=Tv(e),a=Cv(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=jv(o)),[o,jv(o)]}function Dv(e){let t=jv(e);return[Ov(e),t,Ov(t)]}function Ov(e){return e.replace(/start|end/g,e=>_v[e])}function kv(e,t,n){let r=[`left`,`right`],i=[`right`,`left`],a=[`top`,`bottom`],o=[`bottom`,`top`];switch(e){case`top`:case`bottom`:return n?t?i:r:t?r:i;case`left`:case`right`:return t?a:o;default:return[]}}function Av(e,t,n,r){let i=xv(e),a=kv(bv(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map(Ov)))),a}function jv(e){return e.replace(/left|right|bottom|top/g,e=>gv[e])}function Mv(e){return{top:0,right:0,bottom:0,left:0,...e}}function Nv(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:Mv(e)}function Pv(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function Fv(e,t,n){let{reference:r,floating:i}=e,a=wv(t),o=Tv(t),s=Cv(o),c=bv(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}switch(xv(t)){case`start`:p[o]-=f*(n&&l?-1:1);break;case`end`:p[o]+=f*(n&&l?-1:1);break}return p}var Iv=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=a.filter(Boolean),c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=Fv(l,r,c),f=r,p={},m=0;for(let n=0;n({name:`arrow`,options:e,async fn(t){let{x:n,y:r,placement:i,rects:a,platform:o,elements:s,middlewareData:c}=t,{element:l,padding:u=0}=yv(e,t)||{};if(l==null)return{};let d=Nv(u),f={x:n,y:r},p=Tv(i),m=Cv(p),h=await o.getDimensions(l),g=p===`y`,_=g?`top`:`left`,v=g?`bottom`:`right`,y=g?`clientHeight`:`clientWidth`,b=a.reference[m]+a.reference[p]-f[p]-a.floating[m],x=f[p]-a.reference[p],ee=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l)),S=ee?ee[y]:0;(!S||!await(o.isElement==null?void 0:o.isElement(ee)))&&(S=s.floating[y]||a.floating[m]);let C=b/2-x/2,w=S/2-h[m]/2-1,te=dv(d[_],w),ne=dv(d[v],w),re=te,T=S-h[m]-ne,ie=S/2-h[m]/2+C,ae=vv(re,ie,T),oe=!c.arrow&&xv(i)!=null&&ie!==ae&&a.reference[m]/2-(iee<=0)){let e=(i.flip?.index||0)+1,t=ee[e];if(t){let n=u===`alignment`?_!==wv(t):!1,r=w[0]?.overflows[0]>0;if(!n||r)return{data:{index:e,overflows:w},reset:{placement:t}}}let n=w.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=w.filter(e=>{if(x){let t=wv(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o;break}if(r!==n)return{reset:{placement:n}}}return{}}}};function Bv(e,t){return{top:e.top-t.height,right:e.right-t.width,bottom:e.bottom-t.height,left:e.left-t.width}}function Vv(e){return uv.some(t=>e[t]>=0)}var Hv=function(e){return e===void 0&&(e={}),{name:`hide`,options:e,async fn(t){let{rects:n}=t,{strategy:r=`referenceHidden`,...i}=yv(e,t);switch(r){case`referenceHidden`:{let e=Bv(await Lv(t,{...i,elementContext:`reference`}),n.reference);return{data:{referenceHiddenOffsets:e,referenceHidden:Vv(e)}}}case`escaped`:{let e=Bv(await Lv(t,{...i,altBoundary:!0}),n.floating);return{data:{escapedOffsets:e,escaped:Vv(e)}}}default:return{}}}}};async function Uv(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=bv(n),s=xv(n),c=wv(n)===`y`,l=[`left`,`top`].includes(o)?-1:1,u=a&&c?-1:1,d=yv(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}var Wv=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await Uv(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},Gv=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i}=t,{mainAxis:a=!0,crossAxis:o=!1,limiter:s={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...c}=yv(e,t),l={x:n,y:r},u=await Lv(t,c),d=wv(bv(i)),f=Sv(d),p=l[f],m=l[d];if(a){let e=f===`y`?`top`:`left`,t=f===`y`?`bottom`:`right`,n=p+u[e],r=p-u[t];p=vv(n,p,r)}if(o){let e=d===`y`?`top`:`left`,t=d===`y`?`bottom`:`right`,n=m+u[e],r=m-u[t];m=vv(n,m,r)}let h=s.fn({...t,[f]:p,[d]:m});return{...h,data:{x:h.x-n,y:h.y-r,enabled:{[f]:a,[d]:o}}}}}},Kv=function(e){return e===void 0&&(e={}),{options:e,fn(t){let{x:n,y:r,placement:i,rects:a,middlewareData:o}=t,{offset:s=0,mainAxis:c=!0,crossAxis:l=!0}=yv(e,t),u={x:n,y:r},d=wv(i),f=Sv(d),p=u[f],m=u[d],h=yv(s,t),g=typeof h==`number`?{mainAxis:h,crossAxis:0}:{mainAxis:0,crossAxis:0,...h};if(c){let e=f===`y`?`height`:`width`,t=a.reference[f]-a.floating[e]+g.mainAxis,n=a.reference[f]+a.reference[e]-g.mainAxis;pn&&(p=n)}if(l){let e=f===`y`?`width`:`height`,t=[`top`,`left`].includes(bv(i)),n=a.reference[d]-a.floating[e]+(t&&o.offset?.[d]||0)+(t?0:g.crossAxis),r=a.reference[d]+a.reference[e]+(t?0:o.offset?.[d]||0)-(t?g.crossAxis:0);mr&&(m=r)}return{[f]:p,[d]:m}}}},qv=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){var n,r;let{placement:i,rects:a,platform:o,elements:s}=t,{apply:c=()=>{},...l}=yv(e,t),u=await Lv(t,l),d=bv(i),f=xv(i),p=wv(i)===`y`,{width:m,height:h}=a.floating,g,_;d===`top`||d===`bottom`?(g=d,_=f===(await(o.isRTL==null?void 0:o.isRTL(s.floating))?`start`:`end`)?`left`:`right`):(_=d,g=f===`end`?`top`:`bottom`);let v=h-u.top-u.bottom,y=m-u.left-u.right,b=dv(h-u[g],v),x=dv(m-u[_],y),ee=!t.middlewareData.shift,S=b,C=x;if((n=t.middlewareData.shift)!=null&&n.enabled.x&&(C=y),(r=t.middlewareData.shift)!=null&&r.enabled.y&&(S=v),ee&&!f){let e=fv(u.left,0),t=fv(u.right,0),n=fv(u.top,0),r=fv(u.bottom,0);p?C=m-2*(e!==0||t!==0?e+t:fv(u.left,u.right)):S=h-2*(n!==0||r!==0?n+r:fv(u.top,u.bottom))}await c({...t,availableWidth:C,availableHeight:S});let w=await o.getDimensions(s.floating);return m!==w.width||h!==w.height?{reset:{rects:!0}}:{}}}};function Jv(){return typeof window<`u`}function Yv(e){return Qv(e)?(e.nodeName||``).toLowerCase():`#document`}function Xv(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function Zv(e){return((Qv(e)?e.ownerDocument:e.document)||window.document)?.documentElement}function Qv(e){return Jv()?e instanceof Node||e instanceof Xv(e).Node:!1}function $v(e){return Jv()?e instanceof Element||e instanceof Xv(e).Element:!1}function ey(e){return Jv()?e instanceof HTMLElement||e instanceof Xv(e).HTMLElement:!1}function ty(e){return!Jv()||typeof ShadowRoot>`u`?!1:e instanceof ShadowRoot||e instanceof Xv(e).ShadowRoot}function ny(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=ly(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&![`inline`,`contents`].includes(i)}function ry(e){return[`table`,`td`,`th`].includes(Yv(e))}function iy(e){return[`:popover-open`,`:modal`].some(t=>{try{return e.matches(t)}catch{return!1}})}function ay(e){let t=sy(),n=$v(e)?ly(e):e;return[`transform`,`translate`,`scale`,`rotate`,`perspective`].some(e=>n[e]?n[e]!==`none`:!1)||(n.containerType?n.containerType!==`normal`:!1)||!t&&(n.backdropFilter?n.backdropFilter!==`none`:!1)||!t&&(n.filter?n.filter!==`none`:!1)||[`transform`,`translate`,`scale`,`rotate`,`perspective`,`filter`].some(e=>(n.willChange||``).includes(e))||[`paint`,`layout`,`strict`,`content`].some(e=>(n.contain||``).includes(e))}function oy(e){let t=dy(e);for(;ey(t)&&!cy(t);){if(ay(t))return t;if(iy(t))return null;t=dy(t)}return null}function sy(){return typeof CSS>`u`||!CSS.supports?!1:CSS.supports(`-webkit-backdrop-filter`,`none`)}function cy(e){return[`html`,`body`,`#document`].includes(Yv(e))}function ly(e){return Xv(e).getComputedStyle(e)}function uy(e){return $v(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function dy(e){if(Yv(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||ty(e)&&e.host||Zv(e);return ty(t)?t.host:t}function fy(e){let t=dy(e);return cy(t)?e.ownerDocument?e.ownerDocument.body:e.body:ey(t)&&ny(t)?t:fy(t)}function py(e,t,n){t===void 0&&(t=[]),n===void 0&&(n=!0);let r=fy(e),i=r===e.ownerDocument?.body,a=Xv(r);if(i){let e=my(a);return t.concat(a,a.visualViewport||[],ny(r)?r:[],e&&n?py(e):[])}return t.concat(r,py(r,[],n))}function my(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function hy(e){let t=ly(e),n=parseFloat(t.width)||0,r=parseFloat(t.height)||0,i=ey(e),a=i?e.offsetWidth:n,o=i?e.offsetHeight:r,s=pv(n)!==a||pv(r)!==o;return s&&(n=a,r=o),{width:n,height:r,$:s}}function gy(e){return $v(e)?e:e.contextElement}function _y(e){let t=gy(e);if(!ey(t))return hv(1);let n=t.getBoundingClientRect(),{width:r,height:i,$:a}=hy(t),o=(a?pv(n.width):n.width)/r,s=(a?pv(n.height):n.height)/i;return(!o||!Number.isFinite(o))&&(o=1),(!s||!Number.isFinite(s))&&(s=1),{x:o,y:s}}var vy=hv(0);function yy(e){let t=Xv(e);return!sy()||!t.visualViewport?vy:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function by(e,t,n){return t===void 0&&(t=!1),!n||t&&n!==Xv(e)?!1:t}function xy(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=gy(e),o=hv(1);t&&(r?$v(r)&&(o=_y(r)):o=_y(e));let s=by(a,n,r)?yy(a):hv(0),c=(i.left+s.x)/o.x,l=(i.top+s.y)/o.y,u=i.width/o.x,d=i.height/o.y;if(a){let e=Xv(a),t=r&&$v(r)?Xv(r):r,n=e,i=my(n);for(;i&&r&&t!==n;){let e=_y(i),t=i.getBoundingClientRect(),r=ly(i),a=t.left+(i.clientLeft+parseFloat(r.paddingLeft))*e.x,o=t.top+(i.clientTop+parseFloat(r.paddingTop))*e.y;c*=e.x,l*=e.y,u*=e.x,d*=e.y,c+=a,l+=o,n=Xv(i),i=my(n)}}return Pv({width:u,height:d,x:c,y:l})}function Sy(e,t){let n=uy(e).scrollLeft;return t?t.left+n:xy(Zv(e)).left+n}function Cy(e,t,n){n===void 0&&(n=!1);let r=e.getBoundingClientRect();return{x:r.left+t.scrollLeft-(n?0:Sy(e,r)),y:r.top+t.scrollTop}}function wy(e){let{elements:t,rect:n,offsetParent:r,strategy:i}=e,a=i===`fixed`,o=Zv(r),s=t?iy(t.floating):!1;if(r===o||s&&a)return n;let c={scrollLeft:0,scrollTop:0},l=hv(1),u=hv(0),d=ey(r);if((d||!d&&!a)&&((Yv(r)!==`body`||ny(o))&&(c=uy(r)),ey(r))){let e=xy(r);l=_y(r),u.x=e.x+r.clientLeft,u.y=e.y+r.clientTop}let f=o&&!d&&!a?Cy(o,c,!0):hv(0);return{width:n.width*l.x,height:n.height*l.y,x:n.x*l.x-c.scrollLeft*l.x+u.x+f.x,y:n.y*l.y-c.scrollTop*l.y+u.y+f.y}}function Ty(e){return Array.from(e.getClientRects())}function Ey(e){let t=Zv(e),n=uy(e),r=e.ownerDocument.body,i=fv(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=fv(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+Sy(e),s=-n.scrollTop;return ly(r).direction===`rtl`&&(o+=fv(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}function Dy(e,t){let n=Xv(e),r=Zv(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=sy();(!e||e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}return{width:a,height:o,x:s,y:c}}function Oy(e,t){let n=xy(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=ey(e)?_y(e):hv(1);return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}function ky(e,t,n){let r;if(t===`viewport`)r=Dy(e,n);else if(t===`document`)r=Ey(Zv(e));else if($v(t))r=Oy(t,n);else{let n=yy(e);r={x:t.x-n.x,y:t.y-n.y,width:t.width,height:t.height}}return Pv(r)}function Ay(e,t){let n=dy(e);return n===t||!$v(n)||cy(n)?!1:ly(n).position===`fixed`||Ay(n,t)}function jy(e,t){let n=t.get(e);if(n)return n;let r=py(e,[],!1).filter(e=>$v(e)&&Yv(e)!==`body`),i=null,a=ly(e).position===`fixed`,o=a?dy(e):e;for(;$v(o)&&!cy(o);){let t=ly(o),n=ay(o);!n&&t.position===`fixed`&&(i=null),(a?!n&&!i:!n&&t.position===`static`&&i&&[`absolute`,`fixed`].includes(i.position)||ny(o)&&!n&&Ay(e,o))?r=r.filter(e=>e!==o):i=t,o=dy(o)}return t.set(e,r),r}function My(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?iy(t)?[]:jy(t,this._c):[].concat(n),r],o=a[0],s=a.reduce((e,n)=>{let r=ky(t,n,i);return e.top=fv(r.top,e.top),e.right=dv(r.right,e.right),e.bottom=dv(r.bottom,e.bottom),e.left=fv(r.left,e.left),e},ky(t,o,i));return{width:s.right-s.left,height:s.bottom-s.top,x:s.left,y:s.top}}function Ny(e){let{width:t,height:n}=hy(e);return{width:t,height:n}}function Py(e,t,n){let r=ey(t),i=Zv(t),a=n===`fixed`,o=xy(e,!0,a,t),s={scrollLeft:0,scrollTop:0},c=hv(0);function l(){c.x=Sy(i)}if(r||!r&&!a)if((Yv(t)!==`body`||ny(i))&&(s=uy(t)),r){let e=xy(t,!0,a,t);c.x=e.x+t.clientLeft,c.y=e.y+t.clientTop}else i&&l();a&&!r&&i&&l();let u=i&&!r&&!a?Cy(i,s):hv(0);return{x:o.left+s.scrollLeft-c.x-u.x,y:o.top+s.scrollTop-c.y-u.y,width:o.width,height:o.height}}function Fy(e){return ly(e).position===`static`}function Iy(e,t){if(!ey(e)||ly(e).position===`fixed`)return null;if(t)return t(e);let n=e.offsetParent;return Zv(e)===n&&(n=n.ownerDocument.body),n}function Ly(e,t){let n=Xv(e);if(iy(e))return n;if(!ey(e)){let t=dy(e);for(;t&&!cy(t);){if($v(t)&&!Fy(t))return t;t=dy(t)}return n}let r=Iy(e,t);for(;r&&ry(r)&&Fy(r);)r=Iy(r,t);return r&&cy(r)&&Fy(r)&&!ay(r)?n:r||oy(e)||n}var Ry=async function(e){let t=this.getOffsetParent||Ly,n=this.getDimensions,r=await n(e.floating);return{reference:Py(e.reference,await t(e.floating),e.strategy),floating:{x:0,y:0,width:r.width,height:r.height}}};function zy(e){return ly(e).direction===`rtl`}var By={convertOffsetParentRelativeRectToViewportRelativeRect:wy,getDocumentElement:Zv,getClippingRect:My,getOffsetParent:Ly,getElementRects:Ry,getClientRects:Ty,getDimensions:Ny,getScale:_y,isElement:$v,isRTL:zy};function Vy(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function Hy(e,t){let n=null,r,i=Zv(e);function a(){var e;clearTimeout(r),(e=n)==null||e.disconnect(),n=null}function o(s,c){s===void 0&&(s=!1),c===void 0&&(c=1),a();let l=e.getBoundingClientRect(),{left:u,top:d,width:f,height:p}=l;if(s||t(),!f||!p)return;let m=mv(d),h=mv(i.clientWidth-(u+f)),g=mv(i.clientHeight-(d+p)),_=mv(u),v={rootMargin:-m+`px `+-h+`px `+-g+`px `+-_+`px`,threshold:fv(0,dv(1,c))||1},y=!0;function b(t){let n=t[0].intersectionRatio;if(n!==c){if(!y)return o();n?o(!1,n):r=setTimeout(()=>{o(!1,1e-7)},1e3)}n===1&&!Vy(l,e.getBoundingClientRect())&&o(),y=!1}try{n=new IntersectionObserver(b,{...v,root:i.ownerDocument})}catch{n=new IntersectionObserver(b,v)}n.observe(e)}return o(!0),a}function Uy(e,t,n,r){r===void 0&&(r={});let{ancestorScroll:i=!0,ancestorResize:a=!0,elementResize:o=typeof ResizeObserver==`function`,layoutShift:s=typeof IntersectionObserver==`function`,animationFrame:c=!1}=r,l=gy(e),u=i||a?[...l?py(l):[],...py(t)]:[];u.forEach(e=>{i&&e.addEventListener(`scroll`,n,{passive:!0}),a&&e.addEventListener(`resize`,n)});let d=l&&s?Hy(l,n):null,f=-1,p=null;o&&(p=new ResizeObserver(e=>{let[r]=e;r&&r.target===l&&p&&(p.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var e;(e=p)==null||e.observe(t)})),n()}),l&&!c&&p.observe(l),p.observe(t));let m,h=c?xy(e):null;c&&g();function g(){let t=xy(e);h&&!Vy(h,t)&&n(),h=t,m=requestAnimationFrame(g)}return n(),()=>{var e;u.forEach(e=>{i&&e.removeEventListener(`scroll`,n),a&&e.removeEventListener(`resize`,n)}),d?.(),(e=p)==null||e.disconnect(),p=null,c&&cancelAnimationFrame(m)}}var Wy=Wv,Gy=Gv,Ky=zv,qy=qv,Jy=Hv,Yy=Rv,Xy=Kv,Zy=(e,t,n)=>{let r=new Map,i={platform:By,...n},a={...i.platform,_c:r};return Iv(e,t,{...i,platform:a})};function Qy(e){return typeof e==`object`&&!!e&&`$el`in e}function $y(e){if(Qy(e)){let t=e.$el;return Qv(t)&&Yv(t)===`#comment`?null:t}return e}function eb(e){return typeof e==`function`?e():M(e)}function tb(e){return{name:`arrow`,options:e,fn(t){let n=$y(eb(e.element));return n==null?{}:Yy({element:n,padding:e.padding}).fn(t)}}}function nb(e){return typeof window>`u`?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function rb(e,t){let n=nb(e);return Math.round(t*n)/n}function ib(e,t,n){n===void 0&&(n={});let r=n.whileElementsMounted,i=W(()=>eb(n.open)??!0),a=W(()=>eb(n.middleware)),o=W(()=>eb(n.placement)??`bottom`),s=W(()=>eb(n.strategy)??`absolute`),c=W(()=>eb(n.transform)??!0),l=W(()=>$y(e.value)),u=W(()=>$y(t.value)),d=j(0),f=j(0),p=j(s.value),m=j(o.value),h=Zn({}),g=j(!1),_=W(()=>{let e={position:p.value,left:`0`,top:`0`};if(!u.value)return e;let t=rb(u.value,d.value),n=rb(u.value,f.value);return c.value?{...e,transform:`translate(`+t+`px, `+n+`px)`,...nb(u.value)>=1.5&&{willChange:`transform`}}:{position:p.value,left:t+`px`,top:n+`px`}}),v;function y(){if(l.value==null||u.value==null)return;let e=i.value;Zy(l.value,u.value,{middleware:a.value,placement:o.value,strategy:s.value}).then(t=>{d.value=t.x,f.value=t.y,p.value=t.strategy,m.value=t.placement,h.value=t.middlewareData,g.value=e!==!1})}function b(){typeof v==`function`&&(v(),v=void 0)}function x(){if(b(),r===void 0){y();return}if(l.value!=null&&u.value!=null){v=r(l.value,u.value,y);return}}function ee(){i.value||(g.value=!1)}return pi([a,o,s,i],y,{flush:`sync`}),pi([l,u],x,{flush:`sync`}),pi(i,ee,{flush:`sync`}),yt()&&bt(b),{x:Bn(d),y:Bn(f),strategy:Bn(p),placement:Bn(m),middlewareData:Bn(h),isPositioned:Bn(g),floatingStyles:_,update:y}}var ab={side:`bottom`,sideOffset:0,sideFlip:!0,align:`center`,alignOffset:0,alignFlip:!0,arrowPadding:0,avoidCollisions:!0,collisionBoundary:()=>[],collisionPadding:0,sticky:`partial`,hideWhenDetached:!1,positionStrategy:`fixed`,updatePositionStrategy:`optimized`,prioritizePosition:!1},[ob,sb]=bh(`PopperContent`),cb=P({inheritAttrs:!1,__name:`PopperContent`,props:mo({side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},{...ab}),emits:[`placed`],setup(e,{emit:t}){let n=e,r=t,i=ev(),{forwardRef:a,currentElement:o}=G(),s=j(),c=j(),{width:l,height:u}=Vg(c),d=W(()=>n.side+(n.align===`center`?``:`-${n.align}`)),f=W(()=>typeof n.collisionPadding==`number`?n.collisionPadding:{top:0,right:0,bottom:0,left:0,...n.collisionPadding}),p=W(()=>Array.isArray(n.collisionBoundary)?n.collisionBoundary:[n.collisionBoundary]),m=W(()=>({padding:f.value,boundary:p.value.filter(sv),altBoundary:p.value.length>0})),h=W(()=>({mainAxis:n.sideFlip,crossAxis:n.alignFlip})),g=Oh(()=>[Wy({mainAxis:n.sideOffset+u.value,alignmentAxis:n.alignOffset}),n.prioritizePosition&&n.avoidCollisions&&Ky({...m.value,...h.value}),n.avoidCollisions&&Gy({mainAxis:!0,crossAxis:!!n.prioritizePosition,limiter:n.sticky===`partial`?Xy():void 0,...m.value}),!n.prioritizePosition&&n.avoidCollisions&&Ky({...m.value,...h.value}),qy({...m.value,apply:({elements:e,rects:t,availableWidth:n,availableHeight:r})=>{let{width:i,height:a}=t.reference,o=e.floating.style;o.setProperty(`--reka-popper-available-width`,`${n}px`),o.setProperty(`--reka-popper-available-height`,`${r}px`),o.setProperty(`--reka-popper-anchor-width`,`${i}px`),o.setProperty(`--reka-popper-anchor-height`,`${a}px`)}}),c.value&&tb({element:c.value,padding:n.arrowPadding}),cv({arrowWidth:l.value,arrowHeight:u.value}),n.hideWhenDetached&&Jy({strategy:`referenceHidden`,...m.value})]),{floatingStyles:_,placement:v,isPositioned:y,middlewareData:b,update:x}=ib(W(()=>n.reference??i.anchor.value),s,{strategy:n.positionStrategy,placement:d,whileElementsMounted:(...e)=>Uy(...e,{layoutShift:!n.disableUpdateOnLayoutShift,animationFrame:n.updatePositionStrategy===`always`}),middleware:g}),ee=W(()=>lv(v.value)[0]),S=W(()=>lv(v.value)[1]);di(()=>{y.value&&r(`placed`)});let C=W(()=>b.value.arrow?.centerOffset!==0),w=j(``);return ui(()=>{o.value&&(w.value=window.getComputedStyle(o.value).zIndex)}),sb({placedSide:ee,onArrowChange:e=>c.value=e,arrowX:W(()=>b.value.arrow?.x??0),arrowY:W(()=>b.value.arrow?.y??0),shouldHideArrow:C}),(e,t)=>(L(),R(`div`,{ref_key:`floatingRef`,ref:s,"data-reka-popper-content-wrapper":``,style:$e({...M(_),transform:M(y)?M(_).transform:`translate(0, -200%)`,minWidth:`max-content`,zIndex:w.value,"--reka-popper-transform-origin":[M(b).transformOrigin?.x,M(b).transformOrigin?.y].join(` `),...M(b).hide?.referenceHidden&&{visibility:`hidden`,pointerEvents:`none`}})},[V(M(K),U({ref:M(a)},e.$attrs,{"as-child":n.asChild,as:e.as,"data-side":ee.value,"data-align":S.value,style:{animation:M(y)?void 0:`none`}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`as-child`,`as`,`data-side`,`data-align`,`style`])],4))}}),lb={top:`bottom`,right:`left`,bottom:`top`,left:`right`},ub=P({inheritAttrs:!1,__name:`PopperArrow`,props:{width:{type:Number,required:!1},height:{type:Number,required:!1},rounded:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`svg`}},setup(e){let{forwardRef:t}=G(),n=ob(),r=W(()=>lb[n.placedSide.value]);return(e,i)=>(L(),R(`span`,{ref:e=>{M(n).onArrowChange(e)},style:$e({position:`absolute`,left:M(n).arrowX?.value?`${M(n).arrowX?.value}px`:void 0,top:M(n).arrowY?.value?`${M(n).arrowY?.value}px`:void 0,[r.value]:0,transformOrigin:{top:``,right:`0 0`,bottom:`center 0`,left:`100% 0`}[M(n).placedSide.value],transform:{top:`translateY(100%)`,right:`translateY(50%) rotate(90deg) translateX(-50%)`,bottom:`rotate(180deg)`,left:`translateY(50%) rotate(-90deg) translateX(50%)`}[M(n).placedSide.value],visibility:M(n).shouldHideArrow.value?`hidden`:void 0})},[V(ov,U(e.$attrs,{ref:M(t),style:{display:`block`},as:e.as,"as-child":e.asChild,rounded:e.rounded,width:e.width,height:e.height}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`as`,`as-child`,`rounded`,`width`,`height`])],4))}}),db=P({__name:`ComboboxAnchor`,props:{reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let{forwardRef:t}=G();return(e,n)=>(L(),z(M(rv),{"as-child":``,reference:e.reference},{default:N(()=>[V(M(K),U({ref:M(t),"as-child":e.asChild,as:e.as},e.$attrs),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`as-child`,`as`])]),_:3},8,[`reference`]))}});function fb(e){return e?.querySelector(`[data-state=checked]`)}function pb(e,t,n){return e===void 0?!1:Array.isArray(e)?e.some(e=>mb(e,t,n)):mb(e,t,n)}function mb(e,t,n){return e===void 0||t===void 0?!1:typeof e==`string`?e===t:typeof n==`function`?n(e,t):typeof n==`string`?e?.[n]===t?.[n]:Pf(e,t)}var[hb,gb]=bh(`ListboxRoot`),_b=P({__name:`ListboxRoot`,props:{modelValue:{type:null,required:!1},defaultValue:{type:null,required:!1},multiple:{type:Boolean,required:!1},orientation:{type:String,required:!1,default:`vertical`},dir:{type:String,required:!1},disabled:{type:Boolean,required:!1},selectionBehavior:{type:String,required:!1,default:`toggle`},highlightOnHover:{type:Boolean,required:!1},by:{type:[String,Function],required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`,`highlight`,`entryFocus`,`leave`],setup(e,{expose:t,emit:n}){let r=e,i=n,{multiple:a,highlightOnHover:o,orientation:s,disabled:c,selectionBehavior:l,dir:u}=or(r),{getItems:d}=q_({isProvider:!0}),{handleTypeaheadSearch:f}=Ug(),{primitiveElement:p,currentElement:m}=Zg(),h=Bg(),g=fg(u),_=vg(m),v=j(),y=j(!1),b=j(!0),x=sg(r,`modelValue`,i,{defaultValue:r.defaultValue??(a.value?[]:void 0),passive:r.modelValue===void 0,deep:!0});function ee(e){if(y.value=!0,r.multiple){let t=Array.isArray(x.value)?[...x.value]:[],n=t.findIndex(t=>mb(t,e,r.by));r.selectionBehavior===`toggle`?(n===-1?t.push(e):t.splice(n,1),x.value=t):(x.value=[e],v.value=e)}else r.selectionBehavior===`toggle`&&mb(x.value,e,r.by)?x.value=void 0:x.value=e;setTimeout(()=>{y.value=!1},1)}let S=j(null),C=j(null),w=j(!1),te=j(!1),ne=jh(),re=jh(),T=jh();function ie(){return d().map(e=>e.ref).filter(e=>e.dataset.disabled!==``)}function ae(e,t=!0){e&&(S.value=e,b.value&&S.value.focus(),t&&S.value.scrollIntoView({block:`nearest`}),i(`highlight`,d().find(t=>t.ref===e)))}function oe(e){if(w.value)T.trigger(e);else{let t=d().find(t=>mb(t.value,e,r.by));t&&(S.value=t.ref,ae(t.ref))}}function se(e){S.value&&S.value.isConnected&&(e.preventDefault(),e.stopPropagation(),te.value||S.value.click())}function E(e){if(b.value){if(y.value=!0,w.value)re.trigger(e);else{let t=e.altKey||e.ctrlKey||e.metaKey;if(t&&e.key===`a`&&a.value){let t=d();x.value=[...t.map(e=>e.value)],e.preventDefault(),ae(t[t.length-1].ref)}else if(!t){let t=f(e.key,d());t&&ae(t)}}setTimeout(()=>{y.value=!1},1)}}function ce(){te.value=!0}function le(){zr(()=>{te.value=!1})}function ue(){zr(()=>{fe(new KeyboardEvent(`keydown`,{key:`PageUp`}))})}function D(e){let t=S.value;t?.isConnected&&(C.value=t),S.value=null,i(`leave`,e)}function de(e){let t=new CustomEvent(`listbox.entryFocus`,{bubbles:!1,cancelable:!0});if(e.currentTarget?.dispatchEvent(t),i(`entryFocus`,t),!t.defaultPrevented)if(C.value)ae(C.value);else{let e=ie()?.[0];ae(e)}}function fe(e){let t=X_(e,s.value,g.value);if(!t)return;let n=ie();if(S.value){if(t===`last`)n.reverse();else if(t===`prev`||t===`next`){t===`prev`&&n.reverse();let e=n.indexOf(S.value);n=n.slice(e+1)}pe(e,n[0])}if(n.length){let e=!S.value&&t===`prev`?n.length-1:0;ae(n[e])}if(w.value)return re.trigger(e)}function pe(e,t){if(!(w.value||r.selectionBehavior!==`replace`||!a.value||!Array.isArray(x.value))&&!((e.altKey||e.ctrlKey||e.metaKey)&&!e.shiftKey)&&e.shiftKey){let n=d().filter(e=>e.ref.dataset.disabled!==``),r=n.find(e=>e.ref===t)?.value;if(e.key===h.END?r=n[n.length-1].value:e.key===h.HOME&&(r=n[0].value),!r||!v.value)return;x.value=vh(n.map(e=>e.value),v.value,r)}}async function me(e){if(await zr(),w.value)ne.trigger(e);else{let e=ie(),t=e.find(e=>e.dataset.state===`checked`);t?ae(t):e.length&&ae(e[0])}}return pi(x,()=>{y.value||zr(()=>{me()})},{immediate:!0,deep:!0}),t({highlightedElement:S,highlightItem:oe,highlightFirstItem:ue,highlightSelected:me,getItems:d}),gb({modelValue:x,onValueChange:ee,multiple:a,orientation:s,dir:g,disabled:c,highlightOnHover:o,highlightedElement:S,isVirtual:w,virtualFocusHook:ne,virtualKeydownHook:re,virtualHighlightHook:T,by:r.by,firstValue:v,selectionBehavior:l,focusable:b,onLeave:D,onEnter:de,changeHighlight:ae,onKeydownEnter:se,onKeydownNavigation:fe,onKeydownTypeAhead:E,onCompositionStart:ce,onCompositionEnd:le,highlightFirstItem:ue}),(e,t)=>(L(),z(M(K),{ref_key:`primitiveElement`,ref:p,as:e.as,"as-child":e.asChild,dir:M(g),"data-disabled":M(c)?``:void 0,onPointerleave:D,onFocusout:t[0]||=async e=>{let t=e.relatedTarget||e.target;await zr(),S.value&&M(m)&&!M(m).contains(t)&&D(e)}},{default:N(()=>[F(e.$slots,`default`,{modelValue:M(x)}),M(_)&&e.name?(L(),z(M($_),{key:0,name:e.name,value:M(x),disabled:M(c),required:e.required},null,8,[`name`,`value`,`disabled`,`required`])):H(`v-if`,!0)]),_:3},8,[`as`,`as-child`,`dir`,`data-disabled`]))}}),vb=P({__name:`ListboxContent`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let{CollectionSlot:t}=q_(),n=hb(),r=Uh(!1,10);return(e,i)=>(L(),z(M(t),null,{default:N(()=>[V(M(K),{role:`listbox`,as:e.as,"as-child":e.asChild,tabindex:M(n).focusable.value?M(n).highlightedElement.value?`-1`:`0`:`-1`,"aria-orientation":M(n).orientation.value,"aria-multiselectable":!!M(n).multiple.value,"data-orientation":M(n).orientation.value,onMousedown:i[0]||=Mu(e=>r.value=!0,[`left`]),onFocus:i[1]||=e=>{M(r)||M(n).onEnter(e)},onKeydown:[i[2]||=Pu(e=>{M(n).orientation.value===`vertical`&&(e.key===`ArrowLeft`||e.key===`ArrowRight`)||M(n).orientation.value===`horizontal`&&(e.key===`ArrowUp`||e.key===`ArrowDown`)||(e.preventDefault(),M(n).focusable.value&&M(n).onKeydownNavigation(e))},[`down`,`up`,`left`,`right`,`home`,`end`]),Pu(M(n).onKeydownEnter,[`enter`]),M(n).onKeydownTypeAhead]},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`,`tabindex`,`aria-orientation`,`aria-multiselectable`,`data-orientation`,`onKeydown`])]),_:3}))}}),yb=P({__name:`ListboxFilter`,props:{modelValue:{type:String,required:!1},autoFocus:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`input`}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=sg(n,`modelValue`,t,{defaultValue:``,passive:n.modelValue===void 0}),i=hb(),{primitiveElement:a,currentElement:o}=Zg(),s=W(()=>n.disabled||i.disabled.value||!1),c=j();return fi(()=>c.value=i.highlightedElement.value?.id),Aa(()=>{i.focusable.value=!1,setTimeout(()=>{n.autoFocus&&o.value?.focus()},1)}),Pa(()=>{i.focusable.value=!0}),(e,t)=>(L(),z(M(K),{ref_key:`primitiveElement`,ref:a,as:e.as,"as-child":e.asChild,value:M(r),disabled:s.value?``:void 0,"data-disabled":s.value?``:void 0,"aria-disabled":s.value??void 0,"aria-activedescendant":c.value,type:`text`,onKeydown:[Pu(Mu(M(i).onKeydownNavigation,[`prevent`]),[`down`,`up`,`home`,`end`]),Pu(M(i).onKeydownEnter,[`enter`])],onInput:t[0]||=e=>{r.value=e.target.value,M(i).highlightFirstItem()},onCompositionstart:M(i).onCompositionStart,onCompositionend:M(i).onCompositionEnd},{default:N(()=>[F(e.$slots,`default`,{modelValue:M(r)})]),_:3},8,[`as`,`as-child`,`value`,`disabled`,`data-disabled`,`aria-disabled`,`aria-activedescendant`,`onKeydown`,`onCompositionstart`,`onCompositionend`]))}}),[bb,xb]=bh(`ListboxGroup`),Sb=P({__name:`ListboxGroup`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,n=zg(void 0,`reka-listbox-group`);return xb({id:n}),(e,r)=>(L(),z(M(K),U({role:`group`},t,{"aria-labelledby":M(n)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`aria-labelledby`]))}}),Cb=`listbox.select`,[wb,Tb]=bh(`ListboxItem`),Eb=P({__name:`ListboxItem`,props:{value:{type:null,required:!0},disabled:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`div`}},emits:[`select`],setup(e,{emit:t}){let n=e,r=t,i=zg(void 0,`reka-listbox-item`),{CollectionItem:a}=q_(),{forwardRef:o,currentElement:s}=G(),c=hb(),l=W(()=>s.value===c.highlightedElement.value),u=W(()=>pb(c.modelValue.value,n.value,c.by)),d=W(()=>c.disabled.value||n.disabled);async function f(e){r(`select`,e),!e?.defaultPrevented&&!d.value&&e&&(c.onValueChange(n.value),c.changeHighlight(s.value))}function p(e){Sh(Cb,f,{originalEvent:e,value:n.value})}return Tb({isSelected:u}),(e,t)=>(L(),z(M(a),{value:e.value},{default:N(()=>[Pc([l.value,u.value],()=>V(M(K),U({id:M(i)},e.$attrs,{ref:M(o),role:`option`,tabindex:M(c).focusable.value?l.value?`0`:`-1`:-1,"aria-selected":u.value,as:e.as,"as-child":e.asChild,disabled:d.value?``:void 0,"data-disabled":d.value?``:void 0,"data-highlighted":l.value?``:void 0,"data-state":u.value?`checked`:`unchecked`,onClick:p,onKeydown:Pu(Mu(p,[`prevent`]),[`space`]),onPointermove:t[0]||=()=>{M(c).highlightedElement.value!==M(s)&&M(c).highlightOnHover.value&&!M(c).focusable.value&&M(c).changeHighlight(M(s),!1)}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`,`tabindex`,`aria-selected`,`as`,`as-child`,`disabled`,`data-disabled`,`data-highlighted`,`data-state`,`onKeydown`]),t,1)]),_:3},8,[`value`]))}}),Db=P({__name:`ListboxItemIndicator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let t=e;G();let n=wb();return(e,r)=>M(n).isSelected.value?(L(),z(M(K),U({key:0,"aria-hidden":`true`},t),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)):H(`v-if`,!0)}});function Ob(e,t,n){let r=n.initialDeps??[],i,a=!0;function o(){let o;n.key&&n.debug?.call(n)&&(o=Date.now());let s=e();if(!(s.length!==r.length||s.some((e,t)=>r[t]!==e)))return i;r=s;let c;if(n.key&&n.debug?.call(n)&&(c=Date.now()),i=t(...s),n.key&&n.debug?.call(n)){let e=Math.round((Date.now()-o)*100)/100,t=Math.round((Date.now()-c)*100)/100,r=t/16,i=(e,t)=>{for(e=String(e);e.length{r=e},o}function kb(e,t){if(e===void 0)throw Error(`Unexpected undefined${t?`: ${t}`:``}`);return e}var Ab=(e,t)=>Math.abs(e-t)<1.01,jb=(e,t,n)=>{let r;return function(...i){e.clearTimeout(r),r=e.setTimeout(()=>t.apply(this,i),n)}},Mb=e=>{let{offsetWidth:t,offsetHeight:n}=e;return{width:t,height:n}},Nb=e=>e,Pb=e=>{let t=Math.max(e.startIndex-e.overscan,0),n=Math.min(e.endIndex+e.overscan,e.count-1),r=[];for(let e=t;e<=n;e++)r.push(e);return r},Fb=(e,t)=>{let n=e.scrollElement;if(!n)return;let r=e.targetWindow;if(!r)return;let i=e=>{let{width:n,height:r}=e;t({width:Math.round(n),height:Math.round(r)})};if(i(Mb(n)),!r.ResizeObserver)return()=>{};let a=new r.ResizeObserver(t=>{let r=()=>{let e=t[0];if(e?.borderBoxSize){let t=e.borderBoxSize[0];if(t){i({width:t.inlineSize,height:t.blockSize});return}}i(Mb(n))};e.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(r):r()});return a.observe(n,{box:`border-box`}),()=>{a.unobserve(n)}},Ib={passive:!0},Lb=typeof window>`u`?!0:`onscrollend`in window,Rb=(e,t)=>{let n=e.scrollElement;if(!n)return;let r=e.targetWindow;if(!r)return;let i=0,a=e.options.useScrollendEvent&&Lb?()=>void 0:jb(r,()=>{t(i,!1)},e.options.isScrollingResetDelay),o=r=>()=>{let{horizontal:o,isRtl:s}=e.options;i=o?n.scrollLeft*(s&&-1||1):n.scrollTop,a(),t(i,r)},s=o(!0),c=o(!1);c(),n.addEventListener(`scroll`,s,Ib);let l=e.options.useScrollendEvent&&Lb;return l&&n.addEventListener(`scrollend`,c,Ib),()=>{n.removeEventListener(`scroll`,s),l&&n.removeEventListener(`scrollend`,c)}},zb=(e,t,n)=>{if(t?.borderBoxSize){let e=t.borderBoxSize[0];if(e)return Math.round(e[n.options.horizontal?`inlineSize`:`blockSize`])}return e[n.options.horizontal?`offsetWidth`:`offsetHeight`]},Bb=(e,{adjustments:t=0,behavior:n},r)=>{var i,a;let o=e+t;(a=(i=r.scrollElement)?.scrollTo)==null||a.call(i,{[r.options.horizontal?`left`:`top`]:o,behavior:n})},Vb=class{constructor(e){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.measurementsCache=[],this.itemSizeCache=new Map,this.laneAssignments=new Map,this.pendingMeasuredCacheIndexes=[],this.prevLanes=void 0,this.lanesChangedFlag=!1,this.lanesSettling=!1,this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this.elementsCache=new Map,this.observer=(()=>{let e=null,t=()=>e||(!this.targetWindow||!this.targetWindow.ResizeObserver?null:e=new this.targetWindow.ResizeObserver(e=>{e.forEach(e=>{let t=()=>{this._measureElement(e.target,e)};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(t):t()})}));return{disconnect:()=>{var n;(n=t())==null||n.disconnect(),e=null},observe:e=>t()?.observe(e,{box:`border-box`}),unobserve:e=>t()?.unobserve(e)}})(),this.range=null,this.setOptions=e=>{Object.entries(e).forEach(([t,n])=>{n===void 0&&delete e[t]}),this.options={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:Nb,rangeExtractor:Pb,onChange:()=>{},measureElement:zb,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:`data-index`,initialMeasurementsCache:[],lanes:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,...e}},this.notify=e=>{var t,n;(n=(t=this.options).onChange)==null||n.call(t,this,e)},this.maybeNotify=Ob(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),e=>{this.notify(e)},{key:!1,debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(e=>e()),this.unsubs=[],this.observer.disconnect(),this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{let e=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==e){if(this.cleanup(),!e){this.maybeNotify();return}this.scrollElement=e,this.scrollElement&&`ownerDocument`in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=this.scrollElement?.window??null,this.elementsCache.forEach(e=>{this.observer.observe(e)}),this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0}),this.unsubs.push(this.options.observeElementRect(this,e=>{this.scrollRect=e,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(e,t)=>{this.scrollAdjustments=0,this.scrollDirection=t?this.getScrollOffset()this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?`width`:`height`]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??(typeof this.options.initialOffset==`function`?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(e,t)=>{let n=new Map,r=new Map;for(let i=t-1;i>=0;i--){let t=e[i];if(n.has(t.lane))continue;let a=r.get(t.lane);if(a==null||t.end>a.end?r.set(t.lane,t):t.ende.end===t.end?e.index-t.index:e.end-t.end)[0]:void 0},this.getMeasurementOptions=Ob(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled,this.options.lanes],(e,t,n,r,i,a)=>(this.prevLanes!==void 0&&this.prevLanes!==a&&(this.lanesChangedFlag=!0),this.prevLanes=a,this.pendingMeasuredCacheIndexes=[],{count:e,paddingStart:t,scrollMargin:n,getItemKey:r,enabled:i,lanes:a}),{key:!1,skipInitialOnChange:!0,onChange:()=>{this.notify(this.isScrolling)}}),this.getMeasurements=Ob(()=>[this.getMeasurementOptions(),this.itemSizeCache],({count:e,paddingStart:t,scrollMargin:n,getItemKey:r,enabled:i,lanes:a},o)=>{if(!i)return this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),[];if(this.laneAssignments.size>e)for(let t of this.laneAssignments.keys())t>=e&&this.laneAssignments.delete(t);this.lanesChangedFlag&&(this.lanesChangedFlag=!1,this.lanesSettling=!0,this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),this.pendingMeasuredCacheIndexes=[]),this.measurementsCache.length===0&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(e=>{this.itemSizeCache.set(e.key,e.size)}));let s=this.lanesSettling?0:this.pendingMeasuredCacheIndexes.length>0?Math.min(...this.pendingMeasuredCacheIndexes):0;this.pendingMeasuredCacheIndexes=[],this.lanesSettling&&this.measurementsCache.length===e&&(this.lanesSettling=!1);let c=this.measurementsCache.slice(0,s),l=Array(a).fill(void 0);for(let e=0;e1){s=a;let e=l[s],r=e===void 0?void 0:c[e];u=r?r.end+this.options.gap:t+n}else{let e=this.options.lanes===1?c[i-1]:this.getFurthestMeasurement(c,i);u=e?e.end+this.options.gap:t+n,s=e?e.lane:i%this.options.lanes,this.options.lanes>1&&this.laneAssignments.set(i,s)}let d=o.get(e),f=typeof d==`number`?d:this.options.estimateSize(i),p=u+f;c[i]={index:i,start:u,size:f,end:p,key:e,lane:s},l[s]=i}return this.measurementsCache=c,c},{key:!1,debug:()=>this.options.debug}),this.calculateRange=Ob(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(e,t,n,r)=>this.range=e.length>0&&t>0?Ub({measurements:e,outerSize:t,scrollOffset:n,lanes:r}):null,{key:!1,debug:()=>this.options.debug}),this.getVirtualIndexes=Ob(()=>{let e=null,t=null,n=this.calculateRange();return n&&(e=n.startIndex,t=n.endIndex),this.maybeNotify.updateDeps([this.isScrolling,e,t]),[this.options.rangeExtractor,this.options.overscan,this.options.count,e,t]},(e,t,n,r,i)=>r===null||i===null?[]:e({startIndex:r,endIndex:i,overscan:t,count:n}),{key:!1,debug:()=>this.options.debug}),this.indexFromElement=e=>{let t=this.options.indexAttribute,n=e.getAttribute(t);return n?parseInt(n,10):(console.warn(`Missing attribute name '${t}={index}' on measured element.`),-1)},this._measureElement=(e,t)=>{let n=this.indexFromElement(e),r=this.measurementsCache[n];if(!r)return;let i=r.key,a=this.elementsCache.get(i);a!==e&&(a&&this.observer.unobserve(a),this.observer.observe(e),this.elementsCache.set(i,e)),e.isConnected&&this.resizeItem(n,this.options.measureElement(e,t,this))},this.resizeItem=(e,t)=>{let n=this.measurementsCache[e];if(!n)return;let r=t-(this.itemSizeCache.get(n.key)??n.size);r!==0&&((this.shouldAdjustScrollPositionOnItemSizeChange===void 0?n.start{if(!e){this.elementsCache.forEach((e,t)=>{e.isConnected||(this.observer.unobserve(e),this.elementsCache.delete(t))});return}this._measureElement(e,void 0)},this.getVirtualItems=Ob(()=>[this.getVirtualIndexes(),this.getMeasurements()],(e,t)=>{let n=[];for(let r=0,i=e.length;rthis.options.debug}),this.getVirtualItemForOffset=e=>{let t=this.getMeasurements();if(t.length!==0)return kb(t[Hb(0,t.length-1,e=>kb(t[e]).start,e)])},this.getOffsetForAlignment=(e,t,n=0)=>{let r=this.getSize(),i=this.getScrollOffset();t===`auto`&&(t=e>=i+r?`end`:`start`),t===`center`?e+=(n-r)/2:t===`end`&&(e-=r);let a=this.getTotalSize()+this.options.scrollMargin-r;return Math.max(Math.min(a,e),0)},this.getOffsetForIndex=(e,t=`auto`)=>{e=Math.max(0,Math.min(e,this.options.count-1));let n=this.measurementsCache[e];if(!n)return;let r=this.getSize(),i=this.getScrollOffset();if(t===`auto`)if(n.end>=i+r-this.options.scrollPaddingEnd)t=`end`;else if(n.start<=i+this.options.scrollPaddingStart)t=`start`;else return[i,t];let a=t===`end`?n.end+this.options.scrollPaddingEnd:n.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(a,t,n.size),t]},this.isDynamicMode=()=>this.elementsCache.size>0,this.scrollToOffset=(e,{align:t=`start`,behavior:n}={})=>{n===`smooth`&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getOffsetForAlignment(e,t),{adjustments:void 0,behavior:n})},this.scrollToIndex=(e,{align:t=`auto`,behavior:n}={})=>{n===`smooth`&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),e=Math.max(0,Math.min(e,this.options.count-1));let r=0,i=t=>{if(!this.targetWindow)return;let r=this.getOffsetForIndex(e,t);if(!r){console.warn(`Failed to get offset for index:`,e);return}let[i,o]=r;this._scrollToOffset(i,{adjustments:void 0,behavior:n}),this.targetWindow.requestAnimationFrame(()=>{let t=this.getScrollOffset(),n=this.getOffsetForIndex(e,o);if(!n){console.warn(`Failed to get offset for index:`,e);return}Ab(n[0],t)||a(o)})},a=t=>{this.targetWindow&&(r++,r<10?this.targetWindow.requestAnimationFrame(()=>i(t)):console.warn(`Failed to scroll to index ${e} after 10 attempts.`))};i(t)},this.scrollBy=(e,{behavior:t}={})=>{t===`smooth`&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getScrollOffset()+e,{adjustments:void 0,behavior:t})},this.getTotalSize=()=>{let e=this.getMeasurements(),t;if(e.length===0)t=this.options.paddingStart;else if(this.options.lanes===1)t=e[e.length-1]?.end??0;else{let n=Array(this.options.lanes).fill(null),r=e.length-1;for(;r>=0&&n.some(e=>e===null);){let t=e[r];n[t.lane]===null&&(n[t.lane]=t.end),r--}t=Math.max(...n.filter(e=>e!==null))}return Math.max(t-this.options.scrollMargin+this.options.paddingEnd,0)},this._scrollToOffset=(e,{adjustments:t,behavior:n})=>{this.options.scrollToFn(e,{behavior:n,adjustments:t},this)},this.measure=()=>{this.itemSizeCache=new Map,this.laneAssignments=new Map,this.notify(!1)},this.setOptions(e)}},Hb=(e,t,n,r)=>{for(;e<=t;){let i=(e+t)/2|0,a=n(i);if(ar)t=i-1;else return i}return e>0?e-1:0};function Ub({measurements:e,outerSize:t,scrollOffset:n,lanes:r}){let i=e.length-1,a=t=>e[t].start;if(e.length<=r)return{startIndex:0,endIndex:i};let o=Hb(0,i,a,n),s=o;if(r===1)for(;s1){let a=Array(r).fill(0);for(;se=0&&c.some(e=>e>=n);){let t=e[o];c[t.lane]=t.start,o--}o=Math.max(0,o-o%r),s=Math.min(i,s+(r-1-s%r))}return{startIndex:o,endIndex:s}}function Wb(e){let t=new Vb(M(e)),n=Zn(t),r=t._didMount();return pi(()=>M(e).getScrollElement(),e=>{e&&t._willUpdate()},{immediate:!0}),pi(()=>M(e),e=>{t.setOptions({...e,onChange:(t,r)=>{var i;er(n),(i=e.onChange)==null||i.call(e,t,r)}}),t._willUpdate(),er(n)},{immediate:!0}),bt(r),n}function Gb(e){return Wb(W(()=>({observeElementRect:Fb,observeElementOffset:Rb,scrollToFn:Bb,...M(e)})))}var Kb=P({__name:`ListboxVirtualizer`,props:{options:{type:Array,required:!0},overscan:{type:Number,required:!1},estimateSize:{type:Number,required:!1},textContent:{type:Function,required:!1}},setup(e){let t=e,n=lo(),r=hb(),i=og(),{getItems:a}=q_();r.isVirtual.value=!0;let o=W(()=>{let e=i.value;if(e){let t=window.getComputedStyle(e);return{start:Number.parseFloat(t.paddingBlockStart||t.paddingTop),end:Number.parseFloat(t.paddingBlockEnd||t.paddingBottom)}}else return{start:0,end:0}}),s=Gb({get scrollPaddingStart(){return o.value.start},get scrollPaddingEnd(){return o.value.end},get count(){return t.options.length},get horizontal(){return r.orientation.value===`horizontal`},estimateSize(){return t.estimateSize??28},getScrollElement(){return i.value},overscan:t.overscan??12}),c=W(()=>s.value.getVirtualItems().map(e=>{let r=n.default({option:t.options[e.index],virtualizer:s.value,virtualItem:e})[0];return{item:e,is:tc(r.type===I&&Array.isArray(r.children)?r.children[0]:r,{key:`${e.key}`,"data-index":e.index,"aria-setsize":t.options.length,"aria-posinset":e.index+1,style:{position:`absolute`,top:0,left:0,transform:`translateY(${e.start}px)`,overflowAnchor:`none`}})}}));r.virtualFocusHook.on(e=>{let n=t.options.findIndex(e=>Array.isArray(r.modelValue.value)?mb(e,r.modelValue.value[0],r.by):mb(e,r.modelValue.value,r.by));n===-1?r.highlightFirstItem():(e?.preventDefault(),s.value.scrollToIndex(n,{align:`start`}),requestAnimationFrame(()=>{let t=fb(i.value);t&&(r.changeHighlight(t),e&&t?.focus())}))}),r.virtualHighlightHook.on(e=>{let n=t.options.findIndex(t=>mb(t,e,r.by));s.value.scrollToIndex(n,{align:`start`}),requestAnimationFrame(()=>{let e=fb(i.value);e&&r.changeHighlight(e)})});let l=Uh(``,1e3),u=W(()=>{let e=e=>t.textContent?t.textContent(e):e?.toString().toLowerCase();return t.options.map((t,n)=>({index:n,textContent:e(t)}))});function d(e,n){if(!r.firstValue?.value||!r.multiple.value||!Array.isArray(r.modelValue.value))return;let i=a().filter(e=>e.ref.dataset.disabled!==``).find(e=>e.ref===r.highlightedElement.value)?.value;if(!i)return;let o=null;switch(n){case`prev`:case`next`:o=vh(t.options,r.firstValue.value,i);break;case`first`:o=vh(t.options,r.firstValue.value,t.options?.[0]);break;case`last`:o=vh(t.options,r.firstValue.value,t.options?.[t.options.length-1]);break}r.modelValue.value=o}return r.virtualKeydownHook.on(e=>{let n=e.altKey||e.ctrlKey||e.metaKey;if(e.key===`Tab`&&!n)return;let o=J_[e.key];if(n&&e.key===`a`&&r.multiple.value?(e.preventDefault(),r.modelValue.value=[...t.options],o=`last`):e.shiftKey&&o&&d(e,o),[`first`,`last`].includes(o)){e.preventDefault();let n=o===`first`?0:t.options.length-1;s.value.scrollToIndex(n),requestAnimationFrame(()=>{let e=a(),t=o===`first`?e[0]:e[e.length-1];t&&r.changeHighlight(t.ref)})}else if(!o&&!n){l.value+=e.key;let t=Number(xh()?.getAttribute(`data-index`)),n=u.value[t].textContent,a=Gg(u.value.map(e=>e.textContent??``),l.value,n),o=u.value.find(e=>e.textContent===a);o&&(s.value.scrollToIndex(o.index,{align:`start`}),requestAnimationFrame(()=>{let e=i.value.querySelector(`[data-index="${o.index}"]`);e instanceof HTMLElement&&r.changeHighlight(e)}))}}),(e,t)=>(L(),R(`div`,{"data-reka-virtualizer":``,style:$e({position:`relative`,width:`100%`,height:`${M(s).getTotalSize()}px`})},[(L(!0),R(I,null,qa(c.value,({is:e,item:t})=>(L(),z(Ua(e),{key:t.index}))),128))],4))}}),[qb,Jb]=bh(`ComboboxRoot`),Yb=P({__name:`ComboboxRoot`,props:{open:{type:Boolean,required:!1,default:void 0},defaultOpen:{type:Boolean,required:!1},resetSearchTermOnBlur:{type:Boolean,required:!1,default:!0},resetSearchTermOnSelect:{type:Boolean,required:!1,default:!0},openOnFocus:{type:Boolean,required:!1,default:!1},openOnClick:{type:Boolean,required:!1,default:!1},ignoreFilter:{type:Boolean,required:!1},resetModelValueOnClear:{type:Boolean,required:!1,default:!1},modelValue:{type:null,required:!1},defaultValue:{type:null,required:!1},multiple:{type:Boolean,required:!1},dir:{type:String,required:!1},disabled:{type:Boolean,required:!1},highlightOnHover:{type:Boolean,required:!1,default:!0},by:{type:[String,Function],required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`,`highlight`,`update:open`],setup(e,{expose:t,emit:n}){let r=e,i=n,{primitiveElement:a,currentElement:o}=Zg(),{multiple:s,disabled:c,ignoreFilter:l,resetSearchTermOnSelect:u,openOnFocus:d,openOnClick:f,dir:p,resetModelValueOnClear:m,highlightOnHover:h}=or(r),g=fg(p),_=sg(r,`modelValue`,i,{defaultValue:r.defaultValue??(s.value?[]:void 0),passive:r.modelValue===void 0,deep:!0}),v=sg(r,`open`,i,{defaultValue:r.defaultOpen,passive:r.open===void 0});async function y(e){v.value=e,T.value=``,e?(await zr(),a.value?.highlightSelected(),x.value=!0,S.value?.focus()):(x.value=!1,setTimeout(()=>{!e&&r.resetSearchTermOnBlur&&b.trigger()},1))}let b=jh(),x=j(!1),ee=j(!1),S=j(),C=j(),w=W(()=>a.value?.highlightedElement??void 0),te=j(new Map),ne=j(new Map),{contains:re}=mg({sensitivity:`base`}),T=j(``),ie=W(e=>{if(!T.value||r.ignoreFilter||ee.value)return{count:te.value.size,items:e?.items??new Map,groups:e?.groups??new Set(ne.value.keys())};let t=0,n=new Map,i=new Set;for(let[e,r]of te.value){let i=re(r,T.value);n.set(e,i?1:0),i&&t++}for(let[e,t]of ne.value)for(let r of t)if(n.get(r)>0){i.add(e);break}return{count:t,items:n,groups:i}}),ae=fc();return Aa(()=>{ae?.exposed&&(ae.exposed.highlightItem=a.value?.highlightItem,ae.exposed.highlightFirstItem=a.value?.highlightFirstItem,ae.exposed.highlightSelected=a.value?.highlightSelected)}),t({filtered:ie,highlightedElement:w,highlightItem:a.value?.highlightItem,highlightFirstItem:a.value?.highlightFirstItem,highlightSelected:a.value?.highlightSelected}),Jb({modelValue:_,multiple:s,disabled:c,open:v,onOpenChange:y,contentId:``,isUserInputted:x,isVirtual:ee,inputElement:S,highlightedElement:w,onInputElementChange:e=>S.value=e,triggerElement:C,onTriggerElementChange:e=>C.value=e,parentElement:o,resetSearchTermOnSelect:u,onResetSearchTerm:b.on,allItems:te,allGroups:ne,filterSearch:T,filterState:ie,ignoreFilter:l,openOnFocus:d,openOnClick:f,resetModelValueOnClear:m}),(e,t)=>(L(),z(M(nv),null,{default:N(()=>[V(M(_b),U({ref_key:`primitiveElement`,ref:a},e.$attrs,{modelValue:M(_),"onUpdate:modelValue":t[0]||=e=>Xn(_)?_.value=e:null,style:{pointerEvents:M(v)?`auto`:void 0},as:e.as,"as-child":e.asChild,dir:M(g),multiple:M(s),name:e.name,required:e.required,disabled:M(c),"highlight-on-hover":M(h),by:r.by,onHighlight:t[1]||=e=>i(`highlight`,e)}),{default:N(()=>[F(e.$slots,`default`,{open:M(v),modelValue:M(_)})]),_:3},16,[`modelValue`,`style`,`as`,`as-child`,`dir`,`multiple`,`name`,`required`,`disabled`,`highlight-on-hover`,`by`])]),_:3}))}}),[Xb,Zb]=bh(`ComboboxContent`),Qb=P({__name:`ComboboxContentImpl`,props:{position:{type:String,required:!1,default:`inline`},bodyLock:{type:Boolean,required:!1},side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`],setup(e,{emit:t}){let n=e,r=t,{position:i}=or(n),a=qb(),{forwardRef:o,currentElement:s}=G();lg(n.bodyLock),Lg(a.parentElement);let c=yg(W(()=>n.position===`popper`?n:{}).value),l={boxSizing:`border-box`,"--reka-combobox-content-transform-origin":`var(--reka-popper-transform-origin)`,"--reka-combobox-content-available-width":`var(--reka-popper-available-width)`,"--reka-combobox-content-available-height":`var(--reka-popper-available-height)`,"--reka-combobox-trigger-width":`var(--reka-popper-anchor-width)`,"--reka-combobox-trigger-height":`var(--reka-popper-anchor-height)`};Zb({position:i});let u=j(!1);return Aa(()=>{a.inputElement.value&&(u.value=s.value.contains(a.inputElement.value),u.value&&a.inputElement.value.focus())}),Pa(()=>{u.value&&a.triggerElement.value?.focus()}),(e,t)=>(L(),z(M(vb),{"as-child":``},{default:N(()=>[V(M(p_),{"as-child":``,"disable-outside-pointer-events":e.disableOutsidePointerEvents,onDismiss:t[0]||=e=>M(a).onOpenChange(!1),onFocusOutside:t[1]||=e=>{M(a).parentElement.value?.contains(e.target)&&e.preventDefault(),r(`focusOutside`,e)},onInteractOutside:t[2]||=e=>r(`interactOutside`,e),onEscapeKeyDown:t[3]||=e=>r(`escapeKeyDown`,e),onPointerDownOutside:t[4]||=e=>{M(a).parentElement.value?.contains(e.target)&&e.preventDefault(),r(`pointerDownOutside`,e)}},{default:N(()=>[(L(),z(Ua(M(i)===`popper`?M(cb):M(K)),U({...e.$attrs,...M(c)},{id:M(a).contentId,ref:M(o),"data-state":M(a).open.value?`open`:`closed`,style:{display:`flex`,flexDirection:`column`,outline:`none`,...M(i)===`popper`?l:{}}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`,`data-state`,`style`]))]),_:3},8,[`disable-outside-pointer-events`])]),_:3}))}}),$b=P({__name:`ComboboxArrow`,props:{width:{type:Number,required:!1,default:10},height:{type:Number,required:!1,default:5},rounded:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`svg`}},setup(e){let t=e,n=qb(),r=Xb();return G(),(e,i)=>M(n).open.value&&M(r).position.value===`popper`?(L(),z(M(ub),it(U({key:0},t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)):H(`v-if`,!0)}}),ex=P({__name:`ComboboxContent`,props:{forceMount:{type:Boolean,required:!1},position:{type:String,required:!1},bodyLock:{type:Boolean,required:!1},side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},disableOutsidePointerEvents:{type:Boolean,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`,`focusOutside`,`interactOutside`],setup(e,{emit:t}){let n=bg(e,t),{forwardRef:r}=G(),i=qb();return i.contentId||=zg(void 0,`reka-combobox-content`),(e,t)=>(L(),z(M(Jg),{present:e.forceMount||M(i).open.value},{default:N(()=>[V(Qb,U({...M(n),...e.$attrs},{ref:M(r)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)]),_:3},8,[`present`]))}}),tx=P({__name:`ComboboxEmpty`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,n=qb(),r=W(()=>n.ignoreFilter.value?n.allItems.value.size===0:n.filterState.value.count===0);return(e,n)=>r.value?(L(),z(M(K),it(U({key:0},t)),{default:N(()=>[F(e.$slots,`default`,{},()=>[n[0]||=nc(`No options`)])]),_:3},16)):H(`v-if`,!0)}}),[nx,rx]=bh(`ComboboxGroup`),ix=P({__name:`ComboboxGroup`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,n=zg(void 0,`reka-combobox-group`),r=qb(),i=W(()=>r.ignoreFilter.value?!0:r.filterSearch.value?r.filterState.value.groups.has(n):!0),a=rx({id:n,labelId:``});return Aa(()=>{r.allGroups.value.has(n)||r.allGroups.value.set(n,new Set)}),Pa(()=>{r.allGroups.value.delete(n)}),(e,r)=>(L(),z(M(Sb),U({id:M(n),"aria-labelledby":M(a).labelId},t,{hidden:i.value?void 0:!0}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`,`aria-labelledby`,`hidden`]))}}),ax=P({__name:`ComboboxInput`,props:{displayValue:{type:Function,required:!1},modelValue:{type:String,required:!1},autoFocus:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`input`}},emits:[`update:modelValue`],setup(e,{emit:t}){let n=e,r=t,i=qb(),a=hb(),{primitiveElement:o,currentElement:s}=Zg(),c=sg(n,`modelValue`,r,{passive:n.modelValue===void 0});Aa(()=>{s.value&&i.onInputElementChange(s.value)});function l(e){i.open.value||i.onOpenChange(!0)}function u(e){let t=e.target;i.open.value?i.filterSearch.value=t.value:(i.onOpenChange(!0),zr(()=>{t.value&&(i.filterSearch.value=t.value,a.highlightFirstItem())}))}function d(){i.openOnFocus.value&&!i.open.value&&i.onOpenChange(!0)}function f(){i.openOnClick.value&&!i.open.value&&i.onOpenChange(!0)}function p(){let e=i.modelValue.value;n.displayValue?c.value=n.displayValue(e):!i.multiple.value&&e&&!Array.isArray(e)?typeof e==`object`?c.value=``:c.value=e.toString():c.value=``,zr(()=>{c.value=c.value})}return i.onResetSearchTerm(()=>{p()}),pi(i.modelValue,async()=>{!i.isUserInputted.value&&i.resetSearchTermOnSelect.value&&p()},{immediate:!0,deep:!0}),pi(i.filterState,(e,t)=>{!i.isVirtual.value&&t.count===0&&a.highlightFirstItem()}),(e,t)=>(L(),z(M(yb),{ref_key:`primitiveElement`,ref:o,modelValue:M(c),"onUpdate:modelValue":t[0]||=e=>Xn(c)?c.value=e:null,as:e.as,"as-child":e.asChild,"auto-focus":e.autoFocus,disabled:e.disabled,"aria-expanded":M(i).open.value,"aria-controls":M(i).contentId,"aria-autocomplete":`list`,role:`combobox`,autocomplete:`off`,onClick:f,onInput:u,onKeydown:Pu(Mu(l,[`prevent`]),[`down`,`up`]),onFocus:d},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`modelValue`,`as`,`as-child`,`auto-focus`,`disabled`,`aria-expanded`,`aria-controls`,`onKeydown`]))}}),ox=P({__name:`ComboboxItem`,props:{textValue:{type:String,required:!1},value:{type:null,required:!0},disabled:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`select`],setup(e,{emit:t}){let n=e,r=t,i=zg(void 0,`reka-combobox-item`),a=qb(),o=nx(null),{primitiveElement:s,currentElement:c}=Zg();if(n.value===``)throw Error(`A must have a value prop that is not an empty string. This is because the Combobox value can be set to an empty string to clear the selection and show the placeholder.`);let l=W(()=>{if(a.isVirtual.value||a.ignoreFilter.value||!a.filterSearch.value)return!0;{let e=a.filterState.value.items.get(i);return e===void 0?!0:e>0}});return Aa(()=>{a.allItems.value.set(i,n.textValue||c.value.textContent||c.value.innerText);let e=o?.id;e&&(a.allGroups.value.has(e)?a.allGroups.value.get(e)?.add(i):a.allGroups.value.set(e,new Set([i])))}),Pa(()=>{a.allItems.value.delete(i)}),(e,t)=>l.value?(L(),z(M(Eb),U({key:0},n,{id:M(i),ref_key:`primitiveElement`,ref:s,disabled:M(a).disabled.value||e.disabled,onSelect:t[0]||=t=>{r(`select`,t),!t.defaultPrevented&&!M(a).multiple.value&&!e.disabled&&!M(a).disabled.value&&(t.preventDefault(),M(a).onOpenChange(!1),M(a).modelValue.value=n.value)}}),{default:N(()=>[F(e.$slots,`default`,{},()=>[nc(ft(e.value),1)])]),_:3},16,[`id`,`disabled`])):H(`v-if`,!0)}}),sx=P({__name:`ComboboxItemIndicator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let t=e;return(e,n)=>(L(),z(M(Db),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),cx=P({__name:`ComboboxLabel`,props:{for:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`div`}},setup(e){let t=e;G();let n=nx({id:``,labelId:``});return n.labelId||=zg(void 0,`reka-combobox-group-label`),(e,r)=>(L(),z(M(K),U(t,{id:M(n).labelId}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`]))}}),lx=P({__name:`ComboboxPortal`,props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(H_),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),ux=P({__name:`ComboboxSeparator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(K),U(t,{"aria-hidden":`true`}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),dx=P({__name:`ComboboxTrigger`,props:{disabled:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e,{forwardRef:n,currentElement:r}=G(),i=qb(),a=W(()=>t.disabled||i.disabled.value||!1);return Aa(()=>{r.value&&i.onTriggerElementChange(r.value)}),(e,r)=>(L(),z(M(K),U(t,{ref:M(n),type:e.as===`button`?`button`:void 0,tabindex:`-1`,"aria-label":`Show popup`,"aria-haspopup":`listbox`,"aria-expanded":M(i).open.value,"aria-controls":M(i).contentId,"data-state":M(i).open.value?`open`:`closed`,disabled:a.value,"data-disabled":a.value?``:void 0,"aria-disabled":a.value??void 0,onClick:r[0]||=e=>M(i).onOpenChange(!M(i).open.value)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`type`,`aria-expanded`,`aria-controls`,`data-state`,`disabled`,`data-disabled`,`aria-disabled`]))}}),fx=P({__name:`ComboboxVirtualizer`,props:{options:{type:Array,required:!0},overscan:{type:Number,required:!1},estimateSize:{type:Number,required:!1},textContent:{type:Function,required:!1}},setup(e){let t=e,n=qb();return n.isVirtual.value=!0,(e,n)=>(L(),z(Kb,it(ec(t)),{default:N(t=>[F(e.$slots,`default`,it(ec(t)))]),_:3},16))}}),px=P({__name:`Label`,props:{for:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`label`}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(K),U(t,{onMousedown:n[0]||=e=>{!e.defaultPrevented&&e.detail>1&&e.preventDefault()}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),mx=100,[hx,gx]=bh(`ProgressRoot`),_x=e=>typeof e==`number`;function vx(e,t){return Ch(e)||_x(e)&&!Number.isNaN(e)&&e<=t&&e>=0?e:(console.error(`Invalid prop \`value\` of value \`${e}\` supplied to \`ProgressRoot\`. The \`value\` prop must be: + - a positive number + - less than the value passed to \`max\` (or ${mx} if no \`max\` prop is set) + - \`null\` or \`undefined\` if the progress is indeterminate. + +Defaulting to \`null\`.`),null)}function yx(e){return _x(e)&&!Number.isNaN(e)&&e>0?e:(console.error(`Invalid prop \`max\` of value \`${e}\` supplied to \`ProgressRoot\`. Only numbers greater than 0 are valid max values. Defaulting to \`${mx}\`.`),mx)}var bx=P({__name:`ProgressRoot`,props:{modelValue:{type:[Number,null],required:!1},max:{type:Number,required:!1,default:mx},getValueLabel:{type:Function,required:!1,default:(e,t)=>_x(e)?`${Math.round(e/t*mx)}%`:void 0},getValueText:{type:Function,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:modelValue`,`update:max`],setup(e,{emit:t}){let n=e,r=t;G();let i=sg(n,`modelValue`,r,{passive:n.modelValue===void 0}),a=sg(n,`max`,r,{passive:n.max===void 0});pi(()=>i.value,async e=>{let t=vx(e,n.max);t!==e&&(await zr(),i.value=t)},{immediate:!0}),pi(()=>n.max,e=>{let t=yx(n.max);t!==e&&(a.value=t)},{immediate:!0});let o=W(()=>Ch(i.value)?`indeterminate`:i.value===a.value?`complete`:`loading`);return gx({modelValue:i,max:a,progressState:o}),(e,t)=>(L(),z(M(K),{"as-child":e.asChild,as:e.as,"aria-valuemax":M(a),"aria-valuemin":0,"aria-valuenow":_x(M(i))?M(i):void 0,"aria-valuetext":e.getValueText?.(M(i),M(a)),"aria-label":e.getValueLabel(M(i),M(a)),role:`progressbar`,"data-state":o.value,"data-value":M(i)??void 0,"data-max":M(a)},{default:N(()=>[F(e.$slots,`default`,{modelValue:M(i)})]),_:3},8,[`as-child`,`as`,`aria-valuemax`,`aria-valuenow`,`aria-valuetext`,`aria-label`,`data-state`,`data-value`,`data-max`]))}}),xx=P({__name:`ProgressIndicator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,n=hx();return G(),(e,r)=>(L(),z(M(K),U(t,{"data-state":M(n).progressState.value,"data-value":M(n).modelValue?.value??void 0,"data-max":M(n).max.value}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`data-state`,`data-value`,`data-max`]))}}),Sx=[` `,`Enter`,`ArrowUp`,`ArrowDown`],Cx=[` `,`Enter`];function wx(e,t,n){return e===void 0?!1:Array.isArray(e)?e.some(e=>Tx(e,t,n)):Tx(e,t,n)}function Tx(e,t,n){return e===void 0||t===void 0?!1:typeof e==`string`?e===t:typeof n==`function`?n(e,t):typeof n==`string`?e?.[n]===t?.[n]:Pf(e,t)}function Ex(e){return e==null||e===``||Array.isArray(e)&&e.length===0}var Dx={key:0,value:``},[Ox,kx]=bh(`SelectRoot`),Ax=P({inheritAttrs:!1,__name:`SelectRoot`,props:{open:{type:Boolean,required:!1,default:void 0},defaultOpen:{type:Boolean,required:!1},defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},by:{type:[String,Function],required:!1},dir:{type:String,required:!1},multiple:{type:Boolean,required:!1},autocomplete:{type:String,required:!1},disabled:{type:Boolean,required:!1},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`,`update:open`],setup(e,{emit:t}){let n=e,r=t,{required:i,disabled:a,multiple:o,dir:s}=or(n),c=sg(n,`modelValue`,r,{defaultValue:n.defaultValue??(o.value?[]:void 0),passive:n.modelValue===void 0,deep:!0}),l=sg(n,`open`,r,{defaultValue:n.defaultOpen,passive:n.open===void 0}),u=j(),d=j(),f=j({x:0,y:0}),p=W(()=>o.value&&Array.isArray(c.value)?c.value?.length===0:Ch(c.value));q_({isProvider:!0});let m=fg(s),h=vg(u),g=j(new Set),_=W(()=>Array.from(g.value).map(e=>e.value).join(`;`));function v(e){if(o.value){let t=Array.isArray(c.value)?[...c.value]:[],r=t.findIndex(t=>Tx(t,e,n.by));r===-1?t.push(e):t.splice(r,1),c.value=[...t]}else c.value=e}function y(e){return Array.from(g.value).find(t=>wx(e,t.value,n.by))}return kx({triggerElement:u,onTriggerChange:e=>{u.value=e},valueElement:d,onValueElementChange:e=>{d.value=e},contentId:``,modelValue:c,onValueChange:v,by:n.by,open:l,multiple:o,required:i,onOpenChange:e=>{l.value=e},dir:m,triggerPointerDownPosRef:f,disabled:a,isEmptyModelValue:p,optionsSet:g,onOptionAdd:e=>{let t=y(e.value);t&&g.value.delete(t),g.value.add(e)},onOptionRemove:e=>{let t=y(e.value);t&&g.value.delete(t)}}),(e,t)=>(L(),z(M(nv),null,{default:N(()=>[F(e.$slots,`default`,{modelValue:M(c),open:M(l)}),M(h)?(L(),z(jx,{key:_.value,"aria-hidden":`true`,tabindex:`-1`,multiple:M(o),required:M(i),name:e.name,autocomplete:e.autocomplete,disabled:M(a),value:M(c)},{default:N(()=>[M(Ch)(M(c))?(L(),R(`option`,Dx)):H(`v-if`,!0),(L(!0),R(I,null,qa(Array.from(g.value),e=>(L(),R(`option`,U({key:e.value??``},{ref_for:!0},e),null,16))),128))]),_:1},8,[`multiple`,`required`,`name`,`autocomplete`,`disabled`,`value`])):H(`v-if`,!0)]),_:3}))}}),jx=P({__name:`BubbleSelect`,props:{autocomplete:{type:String,required:!1},autofocus:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},form:{type:String,required:!1},multiple:{type:Boolean,required:!1},name:{type:String,required:!1},required:{type:Boolean,required:!1},size:{type:Number,required:!1},value:{type:null,required:!1}},setup(e){let t=e,n=j(),r=Ox();pi(()=>t.value,(e,t)=>{let r=window.HTMLSelectElement.prototype,i=Object.getOwnPropertyDescriptor(r,`value`).set;if(e!==t&&i&&n.value){let t=new Event(`change`,{bubbles:!0});i.call(n.value,e),n.value.dispatchEvent(t)}});function i(e){r.onValueChange(e.target.value)}return(e,r)=>(L(),z(M(Z_),{"as-child":``},{default:N(()=>[B(`select`,U({ref_key:`selectElement`,ref:n},t,{onInput:i}),[F(e.$slots,`default`)],16)]),_:3}))}}),Mx=P({__name:`SelectPopperPosition`,props:{side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1,default:`start`},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1,default:10},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=yg(e);return(e,n)=>(L(),z(M(cb),U(M(t),{style:{boxSizing:`border-box`,"--reka-select-content-transform-origin":`var(--reka-popper-transform-origin)`,"--reka-select-content-available-width":`var(--reka-popper-available-width)`,"--reka-select-content-available-height":`var(--reka-popper-available-height)`,"--reka-select-trigger-width":`var(--reka-popper-anchor-width)`,"--reka-select-trigger-height":`var(--reka-popper-anchor-height)`}}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),Nx={onViewportChange:()=>{},itemTextRefCallback:()=>{},itemRefCallback:()=>{}},[Px,Fx]=bh(`SelectContent`),Ix=P({__name:`SelectContentImpl`,props:{position:{type:String,required:!1,default:`item-aligned`},bodyLock:{type:Boolean,required:!1,default:!0},side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1,default:`start`},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`closeAutoFocus`,`escapeKeyDown`,`pointerDownOutside`],setup(e,{emit:t}){let n=e,r=t,i=Ox();gg(),lg(n.bodyLock);let{CollectionSlot:a,getItems:o}=q_(),s=j();Lg(s);let{search:c,handleTypeaheadSearch:l}=Ug(),u=j(),d=j(),f=j(),p=j(!1),m=j(!1),h=j(!1);function g(){d.value&&s.value&&P_([d.value,s.value])}pi(p,()=>{g()});let{onOpenChange:_,triggerPointerDownPosRef:v}=i;ui(e=>{if(!s.value)return;let t={x:0,y:0},n=e=>{t={x:Math.abs(Math.round(e.pageX)-(v.value?.x??0)),y:Math.abs(Math.round(e.pageY)-(v.value?.y??0))}},r=e=>{e.pointerType!==`touch`&&(t.x<=10&&t.y<=10?e.preventDefault():s.value?.contains(e.target)||_(!1),document.removeEventListener(`pointermove`,n),v.value=null)};v.value!==null&&(document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r,{capture:!0,once:!0})),e(()=>{document.removeEventListener(`pointermove`,n),document.removeEventListener(`pointerup`,r,{capture:!0})})});function y(e){let t=e.ctrlKey||e.altKey||e.metaKey;if(e.key===`Tab`&&e.preventDefault(),!t&&e.key.length===1&&l(e.key,o()),[`ArrowUp`,`ArrowDown`,`Home`,`End`].includes(e.key)){let t=[...o().map(e=>e.ref)];if([`ArrowUp`,`End`].includes(e.key)&&(t=t.slice().reverse()),[`ArrowUp`,`ArrowDown`].includes(e.key)){let n=e.target,r=t.indexOf(n);t=t.slice(r+1)}setTimeout(()=>P_(t)),e.preventDefault()}}let b=yg(W(()=>n.position===`popper`?n:{}).value);return Fx({content:s,viewport:u,onViewportChange:e=>{u.value=e},itemRefCallback:(e,t,n)=>{let r=!m.value&&!n,a=wx(i.modelValue.value,t,i.by);if(i.multiple.value){if(h.value)return;(a||r)&&(d.value=e,a&&(h.value=!0))}else (a||r)&&(d.value=e);r&&(m.value=!0)},selectedItem:d,selectedItemText:f,onItemLeave:()=>{s.value?.focus()},itemTextRefCallback:(e,t,n)=>{let r=!m.value&&!n;(wx(i.modelValue.value,t,i.by)||r)&&(f.value=e)},focusSelectedItem:g,position:n.position,isPositioned:p,searchRef:c}),(e,t)=>(L(),z(M(a),null,{default:N(()=>[V(M(k_),{"as-child":``,onMountAutoFocus:t[6]||=Mu(()=>{},[`prevent`]),onUnmountAutoFocus:t[7]||=e=>{r(`closeAutoFocus`,e),!e.defaultPrevented&&(M(i).triggerElement.value?.focus({preventScroll:!0}),e.preventDefault())}},{default:N(()=>[V(M(p_),{"as-child":``,"disable-outside-pointer-events":``,onFocusOutside:t[2]||=Mu(()=>{},[`prevent`]),onDismiss:t[3]||=e=>M(i).onOpenChange(!1),onEscapeKeyDown:t[4]||=e=>r(`escapeKeyDown`,e),onPointerDownOutside:t[5]||=e=>r(`pointerDownOutside`,e)},{default:N(()=>[(L(),z(Ua(e.position===`popper`?Mx:zx),U({...e.$attrs,...M(b)},{id:M(i).contentId,ref:e=>{let t=M(Xh)(e);t?.hasAttribute(`data-reka-popper-content-wrapper`)?s.value=t.firstElementChild:s.value=t},role:`listbox`,"data-state":M(i).open.value?`open`:`closed`,dir:M(i).dir.value,style:{display:`flex`,flexDirection:`column`,outline:`none`},onContextmenu:t[0]||=Mu(()=>{},[`prevent`]),onPlaced:t[1]||=e=>p.value=!0,onKeydown:y}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`,`data-state`,`dir`,`onKeydown`]))]),_:3})]),_:3})]),_:3}))}}),[Lx,Rx]=bh(`SelectItemAlignedPosition`),zx=P({inheritAttrs:!1,__name:`SelectItemAlignedPosition`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`placed`],setup(e,{emit:t}){let n=e,r=t,{getItems:i}=q_(),a=Ox(),o=Px(),s=j(!1),c=j(!0),l=j(),{forwardRef:u,currentElement:d}=G(),{viewport:f,selectedItem:p,selectedItemText:m,focusSelectedItem:h}=o;function g(){if(a.triggerElement.value&&a.valueElement.value&&l.value&&d.value&&f?.value&&p?.value&&m?.value){let e=a.triggerElement.value.getBoundingClientRect(),t=d.value.getBoundingClientRect(),n=a.valueElement.value.getBoundingClientRect(),o=m.value.getBoundingClientRect();if(a.dir.value!==`rtl`){let r=o.left-t.left,i=n.left-r,a=e.left-i,s=e.width+a,c=Math.max(s,t.width),u=window.innerWidth-10,d=yh(i,10,Math.max(10,u-c));l.value.style.minWidth=`${s}px`,l.value.style.left=`${d}px`}else{let r=t.right-o.right,i=window.innerWidth-n.right-r,a=window.innerWidth-e.right-i,s=e.width+a,c=Math.max(s,t.width),u=window.innerWidth-10,d=yh(i,10,Math.max(10,u-c));l.value.style.minWidth=`${s}px`,l.value.style.right=`${d}px`}let c=i().map(e=>e.ref),u=window.innerHeight-20,h=f.value.scrollHeight,g=window.getComputedStyle(d.value),_=Number.parseInt(g.borderTopWidth,10),v=Number.parseInt(g.paddingTop,10),y=Number.parseInt(g.borderBottomWidth,10),b=Number.parseInt(g.paddingBottom,10),x=_+v+h+b+y,ee=Math.min(p.value.offsetHeight*5,x),S=window.getComputedStyle(f.value),C=Number.parseInt(S.paddingTop,10),w=Number.parseInt(S.paddingBottom,10),te=e.top+e.height/2-10,ne=u-te,re=p.value.offsetHeight/2,T=p.value.offsetTop+re,ie=_+v+T,ae=x-ie;if(ie<=te){let e=p.value===c[c.length-1];l.value.style.bottom=`0px`;let t=d.value.clientHeight-f.value.offsetTop-f.value.offsetHeight,n=ie+Math.max(ne,re+(e?w:0)+t+y);l.value.style.height=`${n}px`}else{let e=p.value===c[0];l.value.style.top=`0px`;let t=Math.max(te,_+f.value.offsetTop+(e?C:0)+re)+ae;l.value.style.height=`${t}px`,f.value.scrollTop=ie-te+f.value.offsetTop}l.value.style.margin=`10px 0`,l.value.style.minHeight=`${ee}px`,l.value.style.maxHeight=`${u}px`,r(`placed`),requestAnimationFrame(()=>s.value=!0)}}let _=j(``);Aa(async()=>{await zr(),g(),d.value&&(_.value=window.getComputedStyle(d.value).zIndex)});function v(e){e&&c.value===!0&&(g(),h?.(),c.value=!1)}return ag(a.triggerElement,()=>{g()}),Rx({contentWrapper:l,shouldExpandOnScrollRef:s,onScrollButtonChange:v}),(e,t)=>(L(),R(`div`,{ref_key:`contentWrapperElement`,ref:l,style:$e({display:`flex`,flexDirection:`column`,position:`fixed`,zIndex:_.value})},[V(M(K),U({ref:M(u),style:{boxSizing:`border-box`,maxHeight:`100%`}},{...e.$attrs,...n}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)],4))}}),Bx=P({__name:`SelectArrow`,props:{width:{type:Number,required:!1,default:10},height:{type:Number,required:!1,default:5},rounded:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`svg`}},setup(e){let t=e,n=Ox(),r=Px(Nx);return(e,i)=>M(n).open.value&&M(r).position===`popper`?(L(),z(M(ub),it(U({key:0},t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)):H(`v-if`,!0)}}),Vx=P({inheritAttrs:!1,__name:`SelectProvider`,props:{context:{type:Object,required:!0}},setup(e){return kx(e.context),Fx(Nx),(e,t)=>F(e.$slots,`default`)}}),Hx={key:1},Ux=P({inheritAttrs:!1,__name:`SelectContent`,props:{forceMount:{type:Boolean,required:!1},position:{type:String,required:!1},bodyLock:{type:Boolean,required:!1},side:{type:null,required:!1},sideOffset:{type:Number,required:!1},sideFlip:{type:Boolean,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},alignFlip:{type:Boolean,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1},disableUpdateOnLayoutShift:{type:Boolean,required:!1},prioritizePosition:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`closeAutoFocus`,`escapeKeyDown`,`pointerDownOutside`],setup(e,{emit:t}){let n=e,r=bg(n,t),i=Ox(),a=j();Aa(()=>{a.value=new DocumentFragment});let o=j(),s=W(()=>n.forceMount||i.open.value),c=j(s.value);return pi(s,()=>{setTimeout(()=>c.value=s.value)}),(e,t)=>s.value||c.value||o.value?.present?(L(),z(M(Jg),{key:0,ref_key:`presenceRef`,ref:o,present:s.value},{default:N(()=>[V(Ix,it(ec({...M(r),...e.$attrs})),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)]),_:3},8,[`present`])):a.value?(L(),R(`div`,Hx,[(L(),z(Di,{to:a.value},[V(Vx,{context:M(i)},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`context`])],8,[`to`]))])):H(`v-if`,!0)}}),[Wx,Gx]=bh(`SelectGroup`),Kx=P({__name:`SelectGroup`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e,n=zg(void 0,`reka-select-group`);return Gx({id:n}),(e,r)=>(L(),z(M(K),U({role:`group`},t,{"aria-labelledby":M(n)}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`aria-labelledby`]))}}),[qx,Jx]=bh(`SelectItem`),Yx=P({__name:`SelectItem`,props:{value:{type:null,required:!0},disabled:{type:Boolean,required:!1},textValue:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`select`],setup(e,{emit:t}){let n=e,r=t,{disabled:i}=or(n),a=Ox(),o=Px(),{forwardRef:s,currentElement:c}=G(),{CollectionItem:l}=q_(),u=W(()=>wx(a.modelValue?.value,n.value,a.by)),d=j(!1),f=j(n.textValue??``),p=zg(void 0,`reka-select-item-text`);async function m(e){e.defaultPrevented||Sh(`select.select`,h,{originalEvent:e,value:n.value})}async function h(e){await zr(),r(`select`,e),!e.defaultPrevented&&(i.value||(a.onValueChange(n.value),a.multiple.value||a.onOpenChange(!1)))}async function g(e){await zr(),!e.defaultPrevented&&(i.value?o.onItemLeave?.():e.currentTarget?.focus({preventScroll:!0}))}async function _(e){await zr(),!e.defaultPrevented&&e.currentTarget===xh()&&o.onItemLeave?.()}async function v(e){await zr(),!e.defaultPrevented&&(o.searchRef?.value!==``&&e.key===` `||(Cx.includes(e.key)&&m(e),e.key===` `&&e.preventDefault()))}if(n.value===``)throw Error(`A must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder.`);return Aa(()=>{c.value&&o.itemRefCallback(c.value,n.value,n.disabled)}),Jx({value:n.value,disabled:i,textId:p,isSelected:u,onItemTextChange:e=>{f.value=((f.value||e?.textContent)??``).trim()}}),(e,t)=>(L(),z(M(l),{value:{textValue:f.value}},{default:N(()=>[V(M(K),{ref:M(s),role:`option`,"aria-labelledby":M(p),"data-highlighted":d.value?``:void 0,"aria-selected":u.value,"data-state":u.value?`checked`:`unchecked`,"aria-disabled":M(i)||void 0,"data-disabled":M(i)?``:void 0,tabindex:M(i)?void 0:-1,as:e.as,"as-child":e.asChild,onFocus:t[0]||=e=>d.value=!0,onBlur:t[1]||=e=>d.value=!1,onPointerup:m,onPointerdown:t[2]||=e=>{e.currentTarget.focus({preventScroll:!0})},onTouchend:t[3]||=Mu(()=>{},[`prevent`,`stop`]),onPointermove:g,onPointerleave:_,onKeydown:v},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`aria-labelledby`,`data-highlighted`,`aria-selected`,`data-state`,`aria-disabled`,`data-disabled`,`tabindex`,`as`,`as-child`])]),_:3},8,[`value`]))}}),Xx=P({__name:`SelectItemIndicator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let t=e,n=qx();return(e,r)=>M(n).isSelected.value?(L(),z(M(K),U({key:0,"aria-hidden":`true`},t),{default:N(()=>[F(e.$slots,`default`)]),_:3},16)):H(`v-if`,!0)}}),Zx=P({inheritAttrs:!1,__name:`SelectItemText`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let t=e,n=Ox(),r=Px(),i=qx(),{forwardRef:a,currentElement:o}=G(),s=W(()=>({value:i.value,disabled:i.disabled.value,textContent:o.value?.textContent??i.value?.toString()??``}));return Aa(()=>{o.value&&(i.onItemTextChange(o.value),r.itemTextRefCallback(o.value,i.value,i.disabled.value),n.onOptionAdd(s.value))}),Pa(()=>{n.onOptionRemove(s.value)}),(e,n)=>(L(),z(M(K),U({id:M(i).textId,ref:M(a)},{...t,...e.$attrs}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`]))}}),Qx=P({__name:`SelectLabel`,props:{for:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`div`}},setup(e){let t=e,n=Wx({id:``});return(e,r)=>(L(),z(M(K),U(t,{id:M(n).id}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`id`]))}}),$x=P({__name:`SelectPortal`,props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(H_),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),eS=P({__name:`SelectSeparator`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(K),U({"aria-hidden":`true`},t),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),tS=P({__name:`SelectTrigger`,props:{disabled:{type:Boolean,required:!1},reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e,n=Ox(),{forwardRef:r,currentElement:i}=G(),a=W(()=>n.disabled?.value||t.disabled);n.contentId||=zg(void 0,`reka-select-content`),Aa(()=>{n.onTriggerChange(i.value)});let{getItems:o}=q_(),{search:s,handleTypeaheadSearch:c,resetTypeahead:l}=Ug();function u(){a.value||(n.onOpenChange(!0),l())}function d(e){u(),n.triggerPointerDownPosRef.value={x:Math.round(e.pageX),y:Math.round(e.pageY)}}return(e,t)=>(L(),z(M(rv),{"as-child":``,reference:e.reference},{default:N(()=>[V(M(K),{ref:M(r),role:`combobox`,type:e.as===`button`?`button`:void 0,"aria-controls":M(n).contentId,"aria-expanded":M(n).open.value||!1,"aria-required":M(n).required?.value,"aria-autocomplete":`none`,disabled:a.value,dir:M(n)?.dir.value,"data-state":M(n)?.open.value?`open`:`closed`,"data-disabled":a.value?``:void 0,"data-placeholder":M(Ex)(M(n).modelValue?.value)?``:void 0,"as-child":e.asChild,as:e.as,onClick:t[0]||=e=>{(e?.currentTarget)?.focus()},onPointerdown:t[1]||=e=>{if(e.pointerType===`touch`)return e.preventDefault();let t=e.target;t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId),e.button===0&&e.ctrlKey===!1&&(d(e),e.preventDefault())},onPointerup:t[2]||=Mu(e=>{e.pointerType===`touch`&&d(e)},[`prevent`]),onKeydown:t[3]||=e=>{let t=M(s)!==``;!(e.ctrlKey||e.altKey||e.metaKey)&&e.key.length===1&&t&&e.key===` `||(M(c)(e.key,M(o)()),M(Sx).includes(e.key)&&(u(),e.preventDefault()))}},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`type`,`aria-controls`,`aria-expanded`,`aria-required`,`disabled`,`dir`,`data-state`,`data-disabled`,`data-placeholder`,`as-child`,`as`])]),_:3},8,[`reference`]))}}),nS=P({__name:`ToastAnnounceExclude`,props:{altText:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){return(e,t)=>(L(),z(M(K),{as:e.as,"as-child":e.asChild,"data-reka-toast-announce-exclude":``,"data-reka-toast-announce-alt":e.altText||void 0},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`,`data-reka-toast-announce-alt`]))}}),[rS,iS]=bh(`ToastProvider`),aS=P({inheritAttrs:!1,__name:`ToastProvider`,props:{label:{type:String,required:!1,default:`Notification`},duration:{type:Number,required:!1,default:5e3},disableSwipe:{type:Boolean,required:!1},swipeDirection:{type:String,required:!1,default:`right`},swipeThreshold:{type:Number,required:!1,default:50}},setup(e){let t=e,{label:n,duration:r,disableSwipe:i,swipeDirection:a,swipeThreshold:o}=or(t);q_({isProvider:!0});let s=j(),c=j(0),l=j(!1),u=j(!1);if(t.label&&typeof t.label==`string`&&!t.label.trim())throw Error("Invalid prop `label` supplied to `ToastProvider`. Expected non-empty `string`.");return iS({label:n,duration:r,disableSwipe:i,swipeDirection:a,swipeThreshold:o,toastCount:c,viewport:s,onViewportChange(e){s.value=e},onToastAdd(){c.value++},onToastRemove(){c.value--},isFocusedToastEscapeKeyDownRef:l,isClosePausedRef:u}),(e,t)=>F(e.$slots,`default`)}}),oS=P({__name:`ToastAnnounce`,setup(e){let t=rS(),n=qh(1e3),r=j(!1);return ng(()=>{r.value=!0}),(e,i)=>M(n)||r.value?(L(),z(M(Z_),{key:0},{default:N(()=>[nc(ft(M(t).label.value)+` `,1),F(e.$slots,`default`)]),_:3})):H(`v-if`,!0)}}),sS=`toast.viewportPause`,cS=`toast.viewportResume`;function lS(e,t,n){let r=n.originalEvent.currentTarget,i=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&r.addEventListener(e,t,{once:!0}),r.dispatchEvent(i)}function uS(e,t,n=0){let r=Math.abs(e.x),i=Math.abs(e.y),a=r>i;return t===`left`||t===`right`?a&&r>n:!a&&i>n}function dS(e){return e.nodeType===e.ELEMENT_NODE}function fS(e){let t=[];return Array.from(e.childNodes).forEach(e=>{if(e.nodeType===e.TEXT_NODE&&e.textContent&&t.push(e.textContent),dS(e)){let n=e.ariaHidden||e.hidden||e.style.display===`none`,r=e.dataset.rekaToastAnnounceExclude===``;if(!n)if(r){let n=e.dataset.rekaToastAnnounceAlt;n&&t.push(n)}else t.push(...fS(e))}}),t}var[pS,mS]=bh(`ToastRoot`),hS=P({inheritAttrs:!1,__name:`ToastRootImpl`,props:{type:{type:String,required:!1},open:{type:Boolean,required:!1,default:!1},duration:{type:Number,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`li`}},emits:[`close`,`escapeKeyDown`,`pause`,`resume`,`swipeStart`,`swipeMove`,`swipeCancel`,`swipeEnd`],setup(e,{emit:t}){let n=e,r=t,{forwardRef:i,currentElement:a}=G(),{CollectionItem:o}=q_(),s=rS(),c=j(null),l=j(null),u=W(()=>typeof n.duration==`number`?n.duration:s.duration.value),d=j(0),f=j(u.value),p=j(0),m=j(u.value),h=ng(()=>{let e=new Date().getTime()-d.value;m.value=Math.max(f.value-e,0)},{fpsLimit:60});function g(e){e<=0||e===1/0||Ph&&(window.clearTimeout(p.value),d.value=new Date().getTime(),p.value=window.setTimeout(_,e))}function _(e){let t=e?.pointerType===``;a.value?.contains(xh())&&t&&s.viewport.value?.focus(),t&&(s.isClosePausedRef.value=!1),r(`close`)}let v=W(()=>a.value?fS(a.value):null);if(n.type&&![`foreground`,`background`].includes(n.type))throw Error("Invalid prop `type` supplied to `Toast`. Expected `foreground | background`.");return ui(e=>{let t=s.viewport.value;if(t){let e=()=>{g(f.value),h.resume(),r(`resume`)},n=()=>{let e=new Date().getTime()-d.value;f.value-=e,window.clearTimeout(p.value),h.pause(),r(`pause`)};return t.addEventListener(sS,n),t.addEventListener(cS,e),()=>{t.removeEventListener(sS,n),t.removeEventListener(cS,e)}}}),pi(()=>[n.open,u.value],()=>{f.value=u.value,n.open&&!s.isClosePausedRef.value&&g(u.value)},{immediate:!0}),tg(`Escape`,e=>{r(`escapeKeyDown`,e),e.defaultPrevented||(s.isFocusedToastEscapeKeyDownRef.value=!0,_())}),Aa(()=>{s.onToastAdd()}),Pa(()=>{s.onToastRemove()}),mS({onClose:_}),(e,t)=>(L(),R(I,null,[v.value?(L(),z(oS,{key:0,role:`alert`,"aria-live":e.type===`foreground`?`assertive`:`polite`,"aria-atomic":`true`},{default:N(()=>[nc(ft(v.value),1)]),_:1},8,[`aria-live`])):H(`v-if`,!0),M(s).viewport.value?(L(),z(Di,{key:1,to:M(s).viewport.value},[V(M(o),null,{default:N(()=>[V(M(K),U({ref:M(i),role:`alert`,"aria-live":`off`,"aria-atomic":`true`,tabindex:`0`},e.$attrs,{as:e.as,"as-child":e.asChild,"data-state":e.open?`open`:`closed`,"data-swipe-direction":M(s).swipeDirection.value,style:M(s).disableSwipe.value?void 0:{userSelect:`none`,touchAction:`none`},onPointerdown:t[0]||=Mu(e=>{M(s).disableSwipe.value||(c.value={x:e.clientX,y:e.clientY})},[`left`]),onPointermove:t[1]||=e=>{if(M(s).disableSwipe.value||!c.value)return;let t=e.clientX-c.value.x,n=e.clientY-c.value.y,i=!!l.value,a=[`left`,`right`].includes(M(s).swipeDirection.value),o=[`left`,`up`].includes(M(s).swipeDirection.value)?Math.min:Math.max,u=a?o(0,t):0,d=a?0:o(0,n),f=e.pointerType===`touch`?10:2,p={x:u,y:d},m={originalEvent:e,delta:p};i?(l.value=p,M(lS)(M(`toast.swipeMove`),e=>r(`swipeMove`,e),m)):M(uS)(p,M(s).swipeDirection.value,f)?(l.value=p,M(lS)(M(`toast.swipeStart`),e=>r(`swipeStart`,e),m),e.target.setPointerCapture(e.pointerId)):(Math.abs(t)>f||Math.abs(n)>f)&&(c.value=null)},onPointerup:t[2]||=e=>{if(M(s).disableSwipe.value)return;let t=l.value,n=e.target;if(n.hasPointerCapture(e.pointerId)&&n.releasePointerCapture(e.pointerId),l.value=null,c.value=null,t){let n=e.currentTarget,i={originalEvent:e,delta:t};M(uS)(t,M(s).swipeDirection.value,M(s).swipeThreshold.value)?M(lS)(M(`toast.swipeEnd`),e=>r(`swipeEnd`,e),i):M(lS)(M(`toast.swipeCancel`),e=>r(`swipeCancel`,e),i),n?.addEventListener(`click`,e=>e.preventDefault(),{once:!0})}}}),{default:N(()=>[F(e.$slots,`default`,{remaining:m.value,duration:u.value})]),_:3},16,[`as`,`as-child`,`data-state`,`data-swipe-direction`,`style`])]),_:3})],8,[`to`])):H(`v-if`,!0)],64))}}),gS=P({__name:`ToastClose`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e,n=pS(),{forwardRef:r}=G();return(e,i)=>(L(),z(nS,{"as-child":``},{default:N(()=>[V(M(K),U(t,{ref:M(r),type:e.as===`button`?`button`:void 0,onClick:M(n).onClose}),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`type`,`onClick`])]),_:3}))}}),_S=P({__name:`ToastAction`,props:{altText:{type:String,required:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){if(!e.altText)throw Error("Missing prop `altText` expected on `ToastAction`");let{forwardRef:t}=G();return(e,n)=>e.altText?(L(),z(nS,{key:0,"alt-text":e.altText,"as-child":``},{default:N(()=>[V(gS,{ref:M(t),as:e.as,"as-child":e.asChild},{default:N(()=>[F(e.$slots,`default`)]),_:3},8,[`as`,`as-child`])]),_:3},8,[`alt-text`])):H(`v-if`,!0)}}),vS=P({__name:`ToastDescription`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(K),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),yS=P({__name:`ToastPortal`,props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(H_),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),bS=P({__name:`ToastRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!0},forceMount:{type:Boolean,required:!1},type:{type:String,required:!1,default:`foreground`},open:{type:Boolean,required:!1,default:void 0},duration:{type:Number,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`li`}},emits:[`escapeKeyDown`,`pause`,`resume`,`swipeStart`,`swipeMove`,`swipeCancel`,`swipeEnd`,`update:open`],setup(e,{emit:t}){let n=e,r=t,{forwardRef:i}=G(),a=sg(n,`open`,r,{defaultValue:n.defaultOpen,passive:n.open===void 0});return(e,t)=>(L(),z(M(Jg),{present:e.forceMount||M(a)},{default:N(()=>[V(hS,U({ref:M(i),open:M(a),type:e.type,as:e.as,"as-child":e.asChild,duration:e.duration},e.$attrs,{onClose:t[0]||=e=>a.value=!1,onPause:t[1]||=e=>r(`pause`),onResume:t[2]||=e=>r(`resume`),onEscapeKeyDown:t[3]||=e=>r(`escapeKeyDown`,e),onSwipeStart:t[4]||=e=>{r(`swipeStart`,e),e.defaultPrevented||e.currentTarget.setAttribute(`data-swipe`,`start`)},onSwipeMove:t[5]||=e=>{if(r(`swipeMove`,e),!e.defaultPrevented){let{x:t,y:n}=e.detail.delta,r=e.currentTarget;r.setAttribute(`data-swipe`,`move`),r.style.setProperty(`--reka-toast-swipe-move-x`,`${t}px`),r.style.setProperty(`--reka-toast-swipe-move-y`,`${n}px`)}},onSwipeCancel:t[6]||=e=>{if(r(`swipeCancel`,e),!e.defaultPrevented){let t=e.currentTarget;t.setAttribute(`data-swipe`,`cancel`),t.style.removeProperty(`--reka-toast-swipe-move-x`),t.style.removeProperty(`--reka-toast-swipe-move-y`),t.style.removeProperty(`--reka-toast-swipe-end-x`),t.style.removeProperty(`--reka-toast-swipe-end-y`)}},onSwipeEnd:t[7]||=e=>{if(r(`swipeEnd`,e),!e.defaultPrevented){let{x:t,y:n}=e.detail.delta,r=e.currentTarget;r.setAttribute(`data-swipe`,`end`),r.style.removeProperty(`--reka-toast-swipe-move-x`),r.style.removeProperty(`--reka-toast-swipe-move-y`),r.style.setProperty(`--reka-toast-swipe-end-x`,`${t}px`),r.style.setProperty(`--reka-toast-swipe-end-y`,`${n}px`),a.value=!1}}}),{default:N(({remaining:t,duration:n})=>[F(e.$slots,`default`,{remaining:t,duration:n,open:M(a)})]),_:3},16,[`open`,`type`,`as`,`as-child`,`duration`])]),_:3},8,[`present`]))}}),xS=P({__name:`ToastTitle`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(K),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),SS=P({__name:`FocusProxy`,emits:[`focusFromOutsideViewport`],setup(e,{emit:t}){let n=t,r=rS();return(e,t)=>(L(),z(M(Z_),{"aria-hidden":`true`,tabindex:`0`,style:{position:`fixed`},onFocus:t[0]||=e=>{let t=e.relatedTarget;M(r).viewport.value?.contains(t)||n(`focusFromOutsideViewport`)}},{default:N(()=>[F(e.$slots,`default`)]),_:3}))}}),CS=P({inheritAttrs:!1,__name:`ToastViewport`,props:{hotkey:{type:Array,required:!1,default:()=>[`F8`]},label:{type:[String,Function],required:!1,default:`Notifications ({hotkey})`},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`ol`}},setup(e){let{hotkey:t,label:n}=or(e),{forwardRef:r,currentElement:i}=G(),{CollectionSlot:a,getItems:o}=q_(),s=rS(),c=W(()=>s.toastCount.value>0),l=j(),u=j(),d=W(()=>t.value.join(`+`).replace(/Key/g,``).replace(/Digit/g,``));tg(t.value,()=>{i.value.focus()}),Aa(()=>{s.onViewportChange(i.value)}),ui(e=>{let t=i.value;if(c.value&&t){let n=()=>{if(!s.isClosePausedRef.value){let e=new CustomEvent(sS);t.dispatchEvent(e),s.isClosePausedRef.value=!0}},r=()=>{if(s.isClosePausedRef.value){let e=new CustomEvent(cS);t.dispatchEvent(e),s.isClosePausedRef.value=!1}},i=e=>{t.contains(e.relatedTarget)||r()},a=()=>{t.contains(xh())||r()},o=e=>{let n=e.altKey||e.ctrlKey||e.metaKey;if(e.key===`Tab`&&!n){let n=xh(),r=e.shiftKey;if(e.target===t&&r){l.value?.focus();return}let i=f({tabbingDirection:r?`backwards`:`forwards`}),a=i.findIndex(e=>e===n);S_(i.slice(a+1))?e.preventDefault():r?l.value?.focus():u.value?.focus()}};t.addEventListener(`focusin`,n),t.addEventListener(`focusout`,i),t.addEventListener(`pointermove`,n),t.addEventListener(`pointerleave`,a),t.addEventListener(`keydown`,o),window.addEventListener(`blur`,n),window.addEventListener(`focus`,r),e(()=>{t.removeEventListener(`focusin`,n),t.removeEventListener(`focusout`,i),t.removeEventListener(`pointermove`,n),t.removeEventListener(`pointerleave`,a),t.removeEventListener(`keydown`,o),window.removeEventListener(`blur`,n),window.removeEventListener(`focus`,r)})}});function f({tabbingDirection:e}){let t=o().map(e=>e.ref).map(t=>{let n=[t,...w_(t)];return e===`forwards`?n:n.reverse()});return(e===`forwards`?t.reverse():t).flat()}return(e,t)=>(L(),z(M(m_),{role:`region`,"aria-label":typeof M(n)==`string`?M(n).replace(`{hotkey}`,d.value):M(n)(d.value),tabindex:`-1`,style:$e({pointerEvents:c.value?void 0:`none`})},{default:N(()=>[c.value?(L(),z(SS,{key:0,ref:e=>{l.value=M(Xh)(e)},onFocusFromOutsideViewport:t[0]||=()=>{let e=f({tabbingDirection:`forwards`});M(S_)(e)}},null,512)):H(`v-if`,!0),V(M(a),null,{default:N(()=>[V(M(K),U({ref:M(r),tabindex:`-1`,as:e.as,"as-child":e.asChild},e.$attrs),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`as`,`as-child`])]),_:3}),c.value?(L(),z(SS,{key:1,ref:e=>{u.value=M(Xh)(e)},onFocusFromOutsideViewport:t[1]||=()=>{let e=f({tabbingDirection:`backwards`});M(S_)(e)}},null,512)):H(`v-if`,!0)]),_:3},8,[`aria-label`,`style`]))}}),wS=P({__name:`TooltipArrow`,props:{width:{type:Number,required:!1,default:10},height:{type:Number,required:!1,default:5},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`svg`}},setup(e){let t=e;return G(),(e,n)=>(L(),z(M(ub),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),[TS,ES]=bh(`TooltipProvider`),DS=P({inheritAttrs:!1,__name:`TooltipProvider`,props:{delayDuration:{type:Number,required:!1,default:700},skipDelayDuration:{type:Number,required:!1,default:300},disableHoverableContent:{type:Boolean,required:!1,default:!1},disableClosingTrigger:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},ignoreNonKeyboardFocus:{type:Boolean,required:!1,default:!1}},setup(e){let{delayDuration:t,skipDelayDuration:n,disableHoverableContent:r,disableClosingTrigger:i,ignoreNonKeyboardFocus:a,disabled:o}=or(e);G();let s=j(!0),c=j(!1),{start:l,stop:u}=Kh(()=>{s.value=!0},n,{immediate:!1});return ES({isOpenDelayed:s,delayDuration:t,onOpen(){u(),s.value=!1},onClose(){l()},isPointerInTransitRef:c,disableHoverableContent:r,disableClosingTrigger:i,disabled:o,ignoreNonKeyboardFocus:a}),(e,t)=>F(e.$slots,`default`)}}),OS=`tooltip.open`,[kS,AS]=bh(`TooltipRoot`),jS=P({__name:`TooltipRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},delayDuration:{type:Number,required:!1,default:void 0},disableHoverableContent:{type:Boolean,required:!1,default:void 0},disableClosingTrigger:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1,default:void 0},ignoreNonKeyboardFocus:{type:Boolean,required:!1,default:void 0}},emits:[`update:open`],setup(e,{emit:t}){let n=e,r=t;G();let i=TS(),a=W(()=>n.disableHoverableContent??i.disableHoverableContent.value),o=W(()=>n.disableClosingTrigger??i.disableClosingTrigger.value),s=W(()=>n.disabled??i.disabled.value),c=W(()=>n.delayDuration??i.delayDuration.value),l=W(()=>n.ignoreNonKeyboardFocus??i.ignoreNonKeyboardFocus.value),u=sg(n,`open`,r,{defaultValue:n.defaultOpen,passive:n.open===void 0});pi(u,e=>{i.onClose&&(e?(i.onOpen(),document.dispatchEvent(new CustomEvent(OS))):i.onClose())});let d=j(!1),f=j(),p=W(()=>u.value?d.value?`delayed-open`:`instant-open`:`closed`),{start:m,stop:h}=Kh(()=>{d.value=!0,u.value=!0},c,{immediate:!1});function g(){h(),d.value=!1,u.value=!0}function _(){h(),u.value=!1}function v(){m()}return AS({contentId:``,open:u,stateAttribute:p,trigger:f,onTriggerChange(e){f.value=e},onTriggerEnter(){i.isOpenDelayed.value?v():g()},onTriggerLeave(){a.value?_():h()},onOpen:g,onClose:_,disableHoverableContent:a,disableClosingTrigger:o,disabled:s,ignoreNonKeyboardFocus:l}),(e,t)=>(L(),z(M(nv),null,{default:N(()=>[F(e.$slots,`default`,{open:M(u)})]),_:3}))}}),MS=P({__name:`TooltipContentImpl`,props:{ariaLabel:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},side:{type:null,required:!1,default:`top`},sideOffset:{type:Number,required:!1,default:0},align:{type:null,required:!1,default:`center`},alignOffset:{type:Number,required:!1},avoidCollisions:{type:Boolean,required:!1,default:!0},collisionBoundary:{type:null,required:!1,default:()=>[]},collisionPadding:{type:[Number,Object],required:!1,default:0},arrowPadding:{type:Number,required:!1,default:0},sticky:{type:String,required:!1,default:`partial`},hideWhenDetached:{type:Boolean,required:!1,default:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`],setup(e,{emit:t}){let n=e,r=t,i=kS(),{forwardRef:a}=G(),o=lo(),s=W(()=>o.default?.({})),c=W(()=>{if(n.ariaLabel)return n.ariaLabel;let e=``;function t(n){typeof n.children==`string`&&n.type!==Bs?e+=n.children:Array.isArray(n.children)&&n.children.forEach(e=>t(e))}return s.value?.forEach(e=>t(e)),e}),l=W(()=>{let{ariaLabel:e,...t}=n;return t});return Aa(()=>{Zh(window,`scroll`,e=>{e.target?.contains(i.trigger.value)&&i.onClose()}),Zh(window,OS,i.onClose)}),(e,t)=>(L(),z(M(p_),{"as-child":``,"disable-outside-pointer-events":!1,onEscapeKeyDown:t[0]||=e=>r(`escapeKeyDown`,e),onPointerDownOutside:t[1]||=e=>{M(i).disableClosingTrigger.value&&M(i).trigger.value?.contains(e.target)&&e.preventDefault(),r(`pointerDownOutside`,e)},onFocusOutside:t[2]||=Mu(()=>{},[`prevent`]),onDismiss:t[3]||=e=>M(i).onClose()},{default:N(()=>[V(M(cb),U({ref:M(a),"data-state":M(i).stateAttribute.value},{...e.$attrs,...l.value},{style:{"--reka-tooltip-content-transform-origin":`var(--reka-popper-transform-origin)`,"--reka-tooltip-content-available-width":`var(--reka-popper-available-width)`,"--reka-tooltip-content-available-height":`var(--reka-popper-available-height)`,"--reka-tooltip-trigger-width":`var(--reka-popper-anchor-width)`,"--reka-tooltip-trigger-height":`var(--reka-popper-anchor-height)`}}),{default:N(()=>[F(e.$slots,`default`),V(M(Z_),{id:M(i).contentId,role:`tooltip`},{default:N(()=>[nc(ft(c.value),1)]),_:1},8,[`id`])]),_:3},16,[`data-state`])]),_:3}))}}),NS=P({__name:`TooltipContentHoverable`,props:{ariaLabel:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},side:{type:null,required:!1},sideOffset:{type:Number,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1}},setup(e){let t=yg(e),{forwardRef:n,currentElement:r}=G(),{trigger:i,onClose:a}=kS(),o=TS(),{isPointerInTransit:s,onPointerExit:c}=xg(i,r);return o.isPointerInTransitRef=s,c(()=>{a()}),(e,r)=>(L(),z(MS,U({ref:M(n)},M(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),PS=P({__name:`TooltipContent`,props:{forceMount:{type:Boolean,required:!1},ariaLabel:{type:String,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1},side:{type:null,required:!1,default:`top`},sideOffset:{type:Number,required:!1},align:{type:null,required:!1},alignOffset:{type:Number,required:!1},avoidCollisions:{type:Boolean,required:!1},collisionBoundary:{type:null,required:!1},collisionPadding:{type:[Number,Object],required:!1},arrowPadding:{type:Number,required:!1},sticky:{type:String,required:!1},hideWhenDetached:{type:Boolean,required:!1},positionStrategy:{type:String,required:!1},updatePositionStrategy:{type:String,required:!1}},emits:[`escapeKeyDown`,`pointerDownOutside`],setup(e,{emit:t}){let n=e,r=t,i=kS(),a=bg(n,r),{forwardRef:o}=G();return(e,t)=>(L(),z(M(Jg),{present:e.forceMount||M(i).open.value},{default:N(()=>[(L(),z(Ua(M(i).disableHoverableContent.value?MS:NS),U({ref:M(o)},M(a)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))]),_:3},8,[`present`]))}}),FS=P({__name:`TooltipPortal`,props:{to:{type:null,required:!1},disabled:{type:Boolean,required:!1},defer:{type:Boolean,required:!1},forceMount:{type:Boolean,required:!1}},setup(e){let t=e;return(e,n)=>(L(),z(M(H_),it(ec(t)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16))}}),IS=P({__name:`TooltipTrigger`,props:{reference:{type:null,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e,n=kS(),r=TS();n.contentId||=zg(void 0,`reka-tooltip-content`);let{forwardRef:i,currentElement:a}=G(),o=j(!1),s=j(!1),c=W(()=>n.disabled.value?{}:{click:h,focus:p,pointermove:d,pointerleave:f,pointerdown:u,blur:m});Aa(()=>{n.onTriggerChange(a.value)});function l(){setTimeout(()=>{o.value=!1},1)}function u(){n.open&&!n.disableClosingTrigger.value&&n.onClose(),o.value=!0,document.addEventListener(`pointerup`,l,{once:!0})}function d(e){e.pointerType!==`touch`&&!s.value&&!r.isPointerInTransitRef.value&&(n.onTriggerEnter(),s.value=!0)}function f(){n.onTriggerLeave(),s.value=!1}function p(e){o.value||n.ignoreNonKeyboardFocus.value&&!e.target.matches?.(`:focus-visible`)||n.onOpen()}function m(){n.onClose()}function h(){n.disableClosingTrigger.value||n.onClose()}return(e,r)=>(L(),z(M(rv),{"as-child":``,reference:e.reference},{default:N(()=>[V(M(K),U({ref:M(i),"aria-describedby":M(n).open.value?M(n).contentId:void 0,"data-state":M(n).stateAttribute.value,as:e.as,"as-child":t.asChild,"data-grace-area-trigger":``},Xa(c.value)),{default:N(()=>[F(e.$slots,`default`)]),_:3},16,[`aria-describedby`,`data-state`,`as`,`as-child`])]),_:3},8,[`reference`]))}});const LS=Symbol(`nuxt-ui.portal-target`);function RS(e){let t=oi(LS,void 0),n=W(()=>e.value===!0?t?.value:e.value),r=W(()=>typeof n.value==`boolean`?!n.value:!1),i=W(()=>typeof n.value==`boolean`?`body`:n.value);return W(()=>({to:i.value,disabled:r.value}))}const zS=Symbol(`nuxt-ui.toast-max`);function BS(){let e=np(`toasts`,()=>[]),t=oi(zS,void 0),n=j(!1),r=[],i=()=>`${Date.now()}-${Math.random().toString(36).slice(2,9)}`;async function a(){if(!(n.value||r.length===0)){for(n.value=!0;r.length>0;){let n=r.shift();await zr(),e.value=[...e.value,n].slice(-(t?.value??5))}n.value=!1}}function o(e){let t={id:i(),open:!0,...e};return r.push(t),a(),t}function s(t,n){let r=e.value.findIndex(e=>e.id===t);r!==-1&&(e.value[r]={...e.value[r],...n})}function c(t){let n=e.value.findIndex(e=>e.id===t);n!==-1&&(e.value[n]={...e.value[n],open:!1}),setTimeout(()=>{e.value=e.value.filter(e=>e.id!==t)},200)}function l(){e.value=[]}return{toasts:e,add:o,update:s,remove:c,clear:l}}var VS=/\s+/g,HS=e=>typeof e!=`string`||!e?e:e.replace(VS,` `).trim(),US=(...e)=>{let t=[],n=e=>{if(!e&&e!==0&&e!==0n)return;if(Array.isArray(e)){for(let t=0,r=e.length;t0?HS(t.join(` `)):void 0},WS=e=>e===!1?`false`:e===!0?`true`:e===0?`0`:e,GS=e=>{if(!e||typeof e!=`object`)return!0;for(let t in e)return!1;return!0},KS=(e,t)=>{if(e===t)return!0;if(!e||!t)return!1;let n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let i=0;i{for(let n in t)if(Object.prototype.hasOwnProperty.call(t,n)){let r=t[n];n in e?e[n]=US(e[n],r):e[n]=r}return e},JS=(e,t)=>{for(let n=0;n{let t=[];JS(e,t);let n=[];for(let e=0;e{let n={};for(let r in e){let i=e[r];if(r in t){let e=t[r];Array.isArray(i)||Array.isArray(e)?n[r]=YS(e,i):typeof i==`object`&&typeof e==`object`&&i&&e?n[r]=XS(i,e):n[r]=e+` `+i}else n[r]=i}for(let r in t)r in e||(n[r]=t[r]);return n},ZS={twMerge:!0,twMergeConfig:{}};function QS(){let e=null,t={},n=!1;return{get cachedTwMerge(){return e},set cachedTwMerge(t){e=t},get cachedTwMergeConfig(){return t},set cachedTwMergeConfig(e){t=e},get didTwMergeConfigChange(){return n},set didTwMergeConfigChange(e){n=e},reset(){e=null,t={},n=!1}}}var $S=QS(),eC=e=>{let t=(t,n)=>{let{extend:r=null,slots:i={},variants:a={},compoundVariants:o=[],compoundSlots:s=[],defaultVariants:c={}}=t,l={...ZS,...n},u=r?.base?US(r.base,t?.base):t?.base,d=r?.variants&&!GS(r.variants)?XS(a,r.variants):a,f=r?.defaultVariants&&!GS(r.defaultVariants)?{...r.defaultVariants,...c}:c;!GS(l.twMergeConfig)&&!KS(l.twMergeConfig,$S.cachedTwMergeConfig)&&($S.didTwMergeConfigChange=!0,$S.cachedTwMergeConfig=l.twMergeConfig);let p=GS(r?.slots),m=GS(i)?{}:{base:US(t?.base,p&&r?.base),...i},h=p?m:qS({...r?.slots},GS(m)?{base:t?.base}:m),g=GS(r?.compoundVariants)?o:YS(r?.compoundVariants,o),_=t=>{if(GS(d)&&GS(i)&&p)return e(u,t?.class,t?.className)(l);if(g&&!Array.isArray(g))throw TypeError(`The "compoundVariants" prop must be an array. Received: ${typeof g}`);if(s&&!Array.isArray(s))throw TypeError(`The "compoundSlots" prop must be an array. Received: ${typeof s}`);let n=(e,n=d,r=null,i=null)=>{let a=n[e];if(!a||GS(a))return null;let o=i?.[e]??t?.[e];if(o===null)return null;let s=WS(o);if(typeof s==`object`)return null;let c=f?.[e];return a[(s??WS(c))||`false`]},r=()=>{if(!d)return null;let e=Object.keys(d),t=[];for(let r=0;r{if(!d||typeof d!=`object`)return null;let r=[];for(let i in d){let a=n(i,d,e,t),o=e===`base`&&typeof a==`string`?a:a&&a[e];o&&r.push(o)}return r},o={};for(let e in t){let n=t[e];n!==void 0&&(o[e]=n)}let c=(e,n)=>{let r=typeof t?.[e]==`object`?{[e]:t[e]?.initial}:{};return{...f,...o,...r,...n}},m=(e=[],t)=>{let n=[],r=e.length;for(let i=0;i{let n=m(g,t);if(!Array.isArray(n))return n;let r={},i=e;for(let e=0;e{if(s.length<1)return null;let t={},n=c(null,e);for(let e=0;e{let r=_(t),i=v(t);return n(h[e],a(e,t),r?r[e]:void 0,i?i[e]:void 0,t?.class,t?.className)(l)}}return t}return e(u,r(),m(g),t?.class,t?.className)(l)};return _.variantKeys=(()=>{if(!(!d||typeof d!=`object`))return Object.keys(d)})(),_.extend=r,_.base=u,_.slots=h,_.variants=d,_.defaultVariants=f,_.compoundSlots=s,_.compoundVariants=g,_};return{tv:t,createTV:e=>(n,r)=>t(n,r?XS(e,r):e)}},tC=(e,t)=>{let n=Array(e.length+t.length);for(let t=0;t({classGroupId:e,validator:t}),rC=(e=new Map,t=null,n)=>({nextPart:e,validators:t,classGroupId:n}),iC=`-`,aC=[],oC=`arbitrary..`,sC=e=>{let t=uC(e),{conflictingClassGroups:n,conflictingClassGroupModifiers:r}=e;return{getClassGroupId:e=>{if(e.startsWith(`[`)&&e.endsWith(`]`))return lC(e);let n=e.split(iC);return cC(n,n[0]===``&&n.length>1?1:0,t)},getConflictingClassGroupIds:(e,t)=>{if(t){let t=r[e],i=n[e];return t?i?tC(i,t):t:i||aC}return n[e]||aC}}},cC=(e,t,n)=>{if(e.length-t===0)return n.classGroupId;let r=e[t],i=n.nextPart.get(r);if(i){let n=cC(e,t+1,i);if(n)return n}let a=n.validators;if(a===null)return;let o=t===0?e.join(iC):e.slice(t).join(iC),s=a.length;for(let e=0;ee.slice(1,-1).indexOf(`:`)===-1?void 0:(()=>{let t=e.slice(1,-1),n=t.indexOf(`:`),r=t.slice(0,n);return r?oC+r:void 0})(),uC=e=>{let{theme:t,classGroups:n}=e;return dC(n,t)},dC=(e,t)=>{let n=rC();for(let r in e){let i=e[r];fC(i,n,r,t)}return n},fC=(e,t,n,r)=>{let i=e.length;for(let a=0;a{if(typeof e==`string`){mC(e,t,n);return}if(typeof e==`function`){hC(e,t,n,r);return}gC(e,t,n,r)},mC=(e,t,n)=>{let r=e===``?t:_C(t,e);r.classGroupId=n},hC=(e,t,n,r)=>{if(vC(e)){fC(e(r),t,n,r);return}t.validators===null&&(t.validators=[]),t.validators.push(nC(n,e))},gC=(e,t,n,r)=>{let i=Object.entries(e),a=i.length;for(let e=0;e{let n=e,r=t.split(iC),i=r.length;for(let e=0;e`isThemeGetter`in e&&e.isThemeGetter===!0,yC=e=>{if(e<1)return{get:()=>void 0,set:()=>{}};let t=0,n=Object.create(null),r=Object.create(null),i=(i,a)=>{n[i]=a,t++,t>e&&(t=0,r=n,n=Object.create(null))};return{get(e){let t=n[e];if(t!==void 0)return t;if((t=r[e])!==void 0)return i(e,t),t},set(e,t){e in n?n[e]=t:i(e,t)}}},bC=`!`,xC=`:`,SC=[],CC=(e,t,n,r,i)=>({modifiers:e,hasImportantModifier:t,baseClassName:n,maybePostfixModifierPosition:r,isExternal:i}),wC=e=>{let{prefix:t,experimentalParseClassName:n}=e,r=e=>{let t=[],n=0,r=0,i=0,a,o=e.length;for(let s=0;si?a-i:void 0;return CC(t,l,c,u)};if(t){let e=t+xC,n=r;r=t=>t.startsWith(e)?n(t.slice(e.length)):CC(SC,!1,t,void 0,!0)}if(n){let e=r;r=t=>n({className:t,parseClassName:e})}return r},TC=e=>{let t=new Map;return e.orderSensitiveModifiers.forEach((e,n)=>{t.set(e,1e6+n)}),e=>{let n=[],r=[];for(let i=0;i0&&(r.sort(),n.push(...r),r=[]),n.push(a)):r.push(a)}return r.length>0&&(r.sort(),n.push(...r)),n}},EC=e=>({cache:yC(e.cacheSize),parseClassName:wC(e),sortModifiers:TC(e),...sC(e)}),DC=/\s+/,OC=(e,t)=>{let{parseClassName:n,getClassGroupId:r,getConflictingClassGroupIds:i,sortModifiers:a}=t,o=[],s=e.trim().split(DC),c=``;for(let e=s.length-1;e>=0;--e){let t=s[e],{isExternal:l,modifiers:u,hasImportantModifier:d,baseClassName:f,maybePostfixModifierPosition:p}=n(t);if(l){c=t+(c.length>0?` `+c:c);continue}let m=!!p,h=r(m?f.substring(0,p):f);if(!h){if(!m){c=t+(c.length>0?` `+c:c);continue}if(h=r(f),!h){c=t+(c.length>0?` `+c:c);continue}m=!1}let g=u.length===0?``:u.length===1?u[0]:a(u).join(`:`),_=d?g+bC:g,v=_+h;if(o.indexOf(v)>-1)continue;o.push(v);let y=i(h,m);for(let e=0;e0?` `+c:c)}return c},kC=(...e)=>{let t=0,n,r,i=``;for(;t{if(typeof e==`string`)return e;let t,n=``;for(let r=0;r{let n,r,i,a,o=o=>(n=EC(t.reduce((e,t)=>t(e),e())),r=n.cache.get,i=n.cache.set,a=s,s(o)),s=e=>{let t=r(e);if(t)return t;let a=OC(e,n);return i(e,a),a};return a=o,(...e)=>a(kC(...e))},MC=[],NC=e=>{let t=t=>t[e]||MC;return t.isThemeGetter=!0,t},PC=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,FC=/^\((?:(\w[\w-]*):)?(.+)\)$/i,IC=/^\d+\/\d+$/,LC=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,RC=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,zC=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,BC=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,VC=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,HC=e=>IC.test(e),UC=e=>!!e&&!Number.isNaN(Number(e)),WC=e=>!!e&&Number.isInteger(Number(e)),GC=e=>e.endsWith(`%`)&&UC(e.slice(0,-1)),KC=e=>LC.test(e),qC=()=>!0,JC=e=>RC.test(e)&&!zC.test(e),YC=()=>!1,XC=e=>BC.test(e),ZC=e=>VC.test(e),QC=e=>!q(e)&&!J(e),$C=e=>dw(e,hw,YC),q=e=>PC.test(e),ew=e=>dw(e,gw,JC),tw=e=>dw(e,_w,UC),nw=e=>dw(e,pw,YC),rw=e=>dw(e,mw,ZC),iw=e=>dw(e,yw,XC),J=e=>FC.test(e),aw=e=>fw(e,gw),ow=e=>fw(e,vw),sw=e=>fw(e,pw),cw=e=>fw(e,hw),lw=e=>fw(e,mw),uw=e=>fw(e,yw,!0),dw=(e,t,n)=>{let r=PC.exec(e);return r?r[1]?t(r[1]):n(r[2]):!1},fw=(e,t,n=!1)=>{let r=FC.exec(e);return r?r[1]?t(r[1]):n:!1},pw=e=>e===`position`||e===`percentage`,mw=e=>e===`image`||e===`url`,hw=e=>e===`length`||e===`size`||e===`bg-size`,gw=e=>e===`length`,_w=e=>e===`number`,vw=e=>e===`family-name`,yw=e=>e===`shadow`,bw=()=>{let e=NC(`color`),t=NC(`font`),n=NC(`text`),r=NC(`font-weight`),i=NC(`tracking`),a=NC(`leading`),o=NC(`breakpoint`),s=NC(`container`),c=NC(`spacing`),l=NC(`radius`),u=NC(`shadow`),d=NC(`inset-shadow`),f=NC(`text-shadow`),p=NC(`drop-shadow`),m=NC(`blur`),h=NC(`perspective`),g=NC(`aspect`),_=NC(`ease`),v=NC(`animate`),y=()=>[`auto`,`avoid`,`all`,`avoid-page`,`page`,`left`,`right`,`column`],b=()=>[`center`,`top`,`bottom`,`left`,`right`,`top-left`,`left-top`,`top-right`,`right-top`,`bottom-right`,`right-bottom`,`bottom-left`,`left-bottom`],x=()=>[...b(),J,q],ee=()=>[`auto`,`hidden`,`clip`,`visible`,`scroll`],S=()=>[`auto`,`contain`,`none`],C=()=>[J,q,c],w=()=>[HC,`full`,`auto`,...C()],te=()=>[WC,`none`,`subgrid`,J,q],ne=()=>[`auto`,{span:[`full`,WC,J,q]},WC,J,q],re=()=>[WC,`auto`,J,q],T=()=>[`auto`,`min`,`max`,`fr`,J,q],ie=()=>[`start`,`end`,`center`,`between`,`around`,`evenly`,`stretch`,`baseline`,`center-safe`,`end-safe`],ae=()=>[`start`,`end`,`center`,`stretch`,`center-safe`,`end-safe`],oe=()=>[`auto`,...C()],se=()=>[HC,`auto`,`full`,`dvw`,`dvh`,`lvw`,`lvh`,`svw`,`svh`,`min`,`max`,`fit`,...C()],E=()=>[e,J,q],ce=()=>[...b(),sw,nw,{position:[J,q]}],le=()=>[`no-repeat`,{repeat:[``,`x`,`y`,`space`,`round`]}],ue=()=>[`auto`,`cover`,`contain`,cw,$C,{size:[J,q]}],D=()=>[GC,aw,ew],de=()=>[``,`none`,`full`,l,J,q],fe=()=>[``,UC,aw,ew],pe=()=>[`solid`,`dashed`,`dotted`,`double`],me=()=>[`normal`,`multiply`,`screen`,`overlay`,`darken`,`lighten`,`color-dodge`,`color-burn`,`hard-light`,`soft-light`,`difference`,`exclusion`,`hue`,`saturation`,`color`,`luminosity`],he=()=>[UC,GC,sw,nw],ge=()=>[``,`none`,m,J,q],_e=()=>[`none`,UC,J,q],ve=()=>[`none`,UC,J,q],ye=()=>[UC,J,q],be=()=>[HC,`full`,...C()];return{cacheSize:500,theme:{animate:[`spin`,`ping`,`pulse`,`bounce`],aspect:[`video`],blur:[KC],breakpoint:[KC],color:[qC],container:[KC],"drop-shadow":[KC],ease:[`in`,`out`,`in-out`],font:[QC],"font-weight":[`thin`,`extralight`,`light`,`normal`,`medium`,`semibold`,`bold`,`extrabold`,`black`],"inset-shadow":[KC],leading:[`none`,`tight`,`snug`,`normal`,`relaxed`,`loose`],perspective:[`dramatic`,`near`,`normal`,`midrange`,`distant`,`none`],radius:[KC],shadow:[KC],spacing:[`px`,UC],text:[KC],"text-shadow":[KC],tracking:[`tighter`,`tight`,`normal`,`wide`,`wider`,`widest`]},classGroups:{aspect:[{aspect:[`auto`,`square`,HC,q,J,g]}],container:[`container`],columns:[{columns:[UC,q,J,s]}],"break-after":[{"break-after":y()}],"break-before":[{"break-before":y()}],"break-inside":[{"break-inside":[`auto`,`avoid`,`avoid-page`,`avoid-column`]}],"box-decoration":[{"box-decoration":[`slice`,`clone`]}],box:[{box:[`border`,`content`]}],display:[`block`,`inline-block`,`inline`,`flex`,`inline-flex`,`table`,`inline-table`,`table-caption`,`table-cell`,`table-column`,`table-column-group`,`table-footer-group`,`table-header-group`,`table-row-group`,`table-row`,`flow-root`,`grid`,`inline-grid`,`contents`,`list-item`,`hidden`],sr:[`sr-only`,`not-sr-only`],float:[{float:[`right`,`left`,`none`,`start`,`end`]}],clear:[{clear:[`left`,`right`,`both`,`none`,`start`,`end`]}],isolation:[`isolate`,`isolation-auto`],"object-fit":[{object:[`contain`,`cover`,`fill`,`none`,`scale-down`]}],"object-position":[{object:x()}],overflow:[{overflow:ee()}],"overflow-x":[{"overflow-x":ee()}],"overflow-y":[{"overflow-y":ee()}],overscroll:[{overscroll:S()}],"overscroll-x":[{"overscroll-x":S()}],"overscroll-y":[{"overscroll-y":S()}],position:[`static`,`fixed`,`absolute`,`relative`,`sticky`],inset:[{inset:w()}],"inset-x":[{"inset-x":w()}],"inset-y":[{"inset-y":w()}],start:[{start:w()}],end:[{end:w()}],top:[{top:w()}],right:[{right:w()}],bottom:[{bottom:w()}],left:[{left:w()}],visibility:[`visible`,`invisible`,`collapse`],z:[{z:[WC,`auto`,J,q]}],basis:[{basis:[HC,`full`,`auto`,s,...C()]}],"flex-direction":[{flex:[`row`,`row-reverse`,`col`,`col-reverse`]}],"flex-wrap":[{flex:[`nowrap`,`wrap`,`wrap-reverse`]}],flex:[{flex:[UC,HC,`auto`,`initial`,`none`,q]}],grow:[{grow:[``,UC,J,q]}],shrink:[{shrink:[``,UC,J,q]}],order:[{order:[WC,`first`,`last`,`none`,J,q]}],"grid-cols":[{"grid-cols":te()}],"col-start-end":[{col:ne()}],"col-start":[{"col-start":re()}],"col-end":[{"col-end":re()}],"grid-rows":[{"grid-rows":te()}],"row-start-end":[{row:ne()}],"row-start":[{"row-start":re()}],"row-end":[{"row-end":re()}],"grid-flow":[{"grid-flow":[`row`,`col`,`dense`,`row-dense`,`col-dense`]}],"auto-cols":[{"auto-cols":T()}],"auto-rows":[{"auto-rows":T()}],gap:[{gap:C()}],"gap-x":[{"gap-x":C()}],"gap-y":[{"gap-y":C()}],"justify-content":[{justify:[...ie(),`normal`]}],"justify-items":[{"justify-items":[...ae(),`normal`]}],"justify-self":[{"justify-self":[`auto`,...ae()]}],"align-content":[{content:[`normal`,...ie()]}],"align-items":[{items:[...ae(),{baseline:[``,`last`]}]}],"align-self":[{self:[`auto`,...ae(),{baseline:[``,`last`]}]}],"place-content":[{"place-content":ie()}],"place-items":[{"place-items":[...ae(),`baseline`]}],"place-self":[{"place-self":[`auto`,...ae()]}],p:[{p:C()}],px:[{px:C()}],py:[{py:C()}],ps:[{ps:C()}],pe:[{pe:C()}],pt:[{pt:C()}],pr:[{pr:C()}],pb:[{pb:C()}],pl:[{pl:C()}],m:[{m:oe()}],mx:[{mx:oe()}],my:[{my:oe()}],ms:[{ms:oe()}],me:[{me:oe()}],mt:[{mt:oe()}],mr:[{mr:oe()}],mb:[{mb:oe()}],ml:[{ml:oe()}],"space-x":[{"space-x":C()}],"space-x-reverse":[`space-x-reverse`],"space-y":[{"space-y":C()}],"space-y-reverse":[`space-y-reverse`],size:[{size:se()}],w:[{w:[s,`screen`,...se()]}],"min-w":[{"min-w":[s,`screen`,`none`,...se()]}],"max-w":[{"max-w":[s,`screen`,`none`,`prose`,{screen:[o]},...se()]}],h:[{h:[`screen`,`lh`,...se()]}],"min-h":[{"min-h":[`screen`,`lh`,`none`,...se()]}],"max-h":[{"max-h":[`screen`,`lh`,...se()]}],"font-size":[{text:[`base`,n,aw,ew]}],"font-smoothing":[`antialiased`,`subpixel-antialiased`],"font-style":[`italic`,`not-italic`],"font-weight":[{font:[r,J,tw]}],"font-stretch":[{"font-stretch":[`ultra-condensed`,`extra-condensed`,`condensed`,`semi-condensed`,`normal`,`semi-expanded`,`expanded`,`extra-expanded`,`ultra-expanded`,GC,q]}],"font-family":[{font:[ow,q,t]}],"fvn-normal":[`normal-nums`],"fvn-ordinal":[`ordinal`],"fvn-slashed-zero":[`slashed-zero`],"fvn-figure":[`lining-nums`,`oldstyle-nums`],"fvn-spacing":[`proportional-nums`,`tabular-nums`],"fvn-fraction":[`diagonal-fractions`,`stacked-fractions`],tracking:[{tracking:[i,J,q]}],"line-clamp":[{"line-clamp":[UC,`none`,J,tw]}],leading:[{leading:[a,...C()]}],"list-image":[{"list-image":[`none`,J,q]}],"list-style-position":[{list:[`inside`,`outside`]}],"list-style-type":[{list:[`disc`,`decimal`,`none`,J,q]}],"text-alignment":[{text:[`left`,`center`,`right`,`justify`,`start`,`end`]}],"placeholder-color":[{placeholder:E()}],"text-color":[{text:E()}],"text-decoration":[`underline`,`overline`,`line-through`,`no-underline`],"text-decoration-style":[{decoration:[...pe(),`wavy`]}],"text-decoration-thickness":[{decoration:[UC,`from-font`,`auto`,J,ew]}],"text-decoration-color":[{decoration:E()}],"underline-offset":[{"underline-offset":[UC,`auto`,J,q]}],"text-transform":[`uppercase`,`lowercase`,`capitalize`,`normal-case`],"text-overflow":[`truncate`,`text-ellipsis`,`text-clip`],"text-wrap":[{text:[`wrap`,`nowrap`,`balance`,`pretty`]}],indent:[{indent:C()}],"vertical-align":[{align:[`baseline`,`top`,`middle`,`bottom`,`text-top`,`text-bottom`,`sub`,`super`,J,q]}],whitespace:[{whitespace:[`normal`,`nowrap`,`pre`,`pre-line`,`pre-wrap`,`break-spaces`]}],break:[{break:[`normal`,`words`,`all`,`keep`]}],wrap:[{wrap:[`break-word`,`anywhere`,`normal`]}],hyphens:[{hyphens:[`none`,`manual`,`auto`]}],content:[{content:[`none`,J,q]}],"bg-attachment":[{bg:[`fixed`,`local`,`scroll`]}],"bg-clip":[{"bg-clip":[`border`,`padding`,`content`,`text`]}],"bg-origin":[{"bg-origin":[`border`,`padding`,`content`]}],"bg-position":[{bg:ce()}],"bg-repeat":[{bg:le()}],"bg-size":[{bg:ue()}],"bg-image":[{bg:[`none`,{linear:[{to:[`t`,`tr`,`r`,`br`,`b`,`bl`,`l`,`tl`]},WC,J,q],radial:[``,J,q],conic:[WC,J,q]},lw,rw]}],"bg-color":[{bg:E()}],"gradient-from-pos":[{from:D()}],"gradient-via-pos":[{via:D()}],"gradient-to-pos":[{to:D()}],"gradient-from":[{from:E()}],"gradient-via":[{via:E()}],"gradient-to":[{to:E()}],rounded:[{rounded:de()}],"rounded-s":[{"rounded-s":de()}],"rounded-e":[{"rounded-e":de()}],"rounded-t":[{"rounded-t":de()}],"rounded-r":[{"rounded-r":de()}],"rounded-b":[{"rounded-b":de()}],"rounded-l":[{"rounded-l":de()}],"rounded-ss":[{"rounded-ss":de()}],"rounded-se":[{"rounded-se":de()}],"rounded-ee":[{"rounded-ee":de()}],"rounded-es":[{"rounded-es":de()}],"rounded-tl":[{"rounded-tl":de()}],"rounded-tr":[{"rounded-tr":de()}],"rounded-br":[{"rounded-br":de()}],"rounded-bl":[{"rounded-bl":de()}],"border-w":[{border:fe()}],"border-w-x":[{"border-x":fe()}],"border-w-y":[{"border-y":fe()}],"border-w-s":[{"border-s":fe()}],"border-w-e":[{"border-e":fe()}],"border-w-t":[{"border-t":fe()}],"border-w-r":[{"border-r":fe()}],"border-w-b":[{"border-b":fe()}],"border-w-l":[{"border-l":fe()}],"divide-x":[{"divide-x":fe()}],"divide-x-reverse":[`divide-x-reverse`],"divide-y":[{"divide-y":fe()}],"divide-y-reverse":[`divide-y-reverse`],"border-style":[{border:[...pe(),`hidden`,`none`]}],"divide-style":[{divide:[...pe(),`hidden`,`none`]}],"border-color":[{border:E()}],"border-color-x":[{"border-x":E()}],"border-color-y":[{"border-y":E()}],"border-color-s":[{"border-s":E()}],"border-color-e":[{"border-e":E()}],"border-color-t":[{"border-t":E()}],"border-color-r":[{"border-r":E()}],"border-color-b":[{"border-b":E()}],"border-color-l":[{"border-l":E()}],"divide-color":[{divide:E()}],"outline-style":[{outline:[...pe(),`none`,`hidden`]}],"outline-offset":[{"outline-offset":[UC,J,q]}],"outline-w":[{outline:[``,UC,aw,ew]}],"outline-color":[{outline:E()}],shadow:[{shadow:[``,`none`,u,uw,iw]}],"shadow-color":[{shadow:E()}],"inset-shadow":[{"inset-shadow":[`none`,d,uw,iw]}],"inset-shadow-color":[{"inset-shadow":E()}],"ring-w":[{ring:fe()}],"ring-w-inset":[`ring-inset`],"ring-color":[{ring:E()}],"ring-offset-w":[{"ring-offset":[UC,ew]}],"ring-offset-color":[{"ring-offset":E()}],"inset-ring-w":[{"inset-ring":fe()}],"inset-ring-color":[{"inset-ring":E()}],"text-shadow":[{"text-shadow":[`none`,f,uw,iw]}],"text-shadow-color":[{"text-shadow":E()}],opacity:[{opacity:[UC,J,q]}],"mix-blend":[{"mix-blend":[...me(),`plus-darker`,`plus-lighter`]}],"bg-blend":[{"bg-blend":me()}],"mask-clip":[{"mask-clip":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]},`mask-no-clip`],"mask-composite":[{mask:[`add`,`subtract`,`intersect`,`exclude`]}],"mask-image-linear-pos":[{"mask-linear":[UC]}],"mask-image-linear-from-pos":[{"mask-linear-from":he()}],"mask-image-linear-to-pos":[{"mask-linear-to":he()}],"mask-image-linear-from-color":[{"mask-linear-from":E()}],"mask-image-linear-to-color":[{"mask-linear-to":E()}],"mask-image-t-from-pos":[{"mask-t-from":he()}],"mask-image-t-to-pos":[{"mask-t-to":he()}],"mask-image-t-from-color":[{"mask-t-from":E()}],"mask-image-t-to-color":[{"mask-t-to":E()}],"mask-image-r-from-pos":[{"mask-r-from":he()}],"mask-image-r-to-pos":[{"mask-r-to":he()}],"mask-image-r-from-color":[{"mask-r-from":E()}],"mask-image-r-to-color":[{"mask-r-to":E()}],"mask-image-b-from-pos":[{"mask-b-from":he()}],"mask-image-b-to-pos":[{"mask-b-to":he()}],"mask-image-b-from-color":[{"mask-b-from":E()}],"mask-image-b-to-color":[{"mask-b-to":E()}],"mask-image-l-from-pos":[{"mask-l-from":he()}],"mask-image-l-to-pos":[{"mask-l-to":he()}],"mask-image-l-from-color":[{"mask-l-from":E()}],"mask-image-l-to-color":[{"mask-l-to":E()}],"mask-image-x-from-pos":[{"mask-x-from":he()}],"mask-image-x-to-pos":[{"mask-x-to":he()}],"mask-image-x-from-color":[{"mask-x-from":E()}],"mask-image-x-to-color":[{"mask-x-to":E()}],"mask-image-y-from-pos":[{"mask-y-from":he()}],"mask-image-y-to-pos":[{"mask-y-to":he()}],"mask-image-y-from-color":[{"mask-y-from":E()}],"mask-image-y-to-color":[{"mask-y-to":E()}],"mask-image-radial":[{"mask-radial":[J,q]}],"mask-image-radial-from-pos":[{"mask-radial-from":he()}],"mask-image-radial-to-pos":[{"mask-radial-to":he()}],"mask-image-radial-from-color":[{"mask-radial-from":E()}],"mask-image-radial-to-color":[{"mask-radial-to":E()}],"mask-image-radial-shape":[{"mask-radial":[`circle`,`ellipse`]}],"mask-image-radial-size":[{"mask-radial":[{closest:[`side`,`corner`],farthest:[`side`,`corner`]}]}],"mask-image-radial-pos":[{"mask-radial-at":b()}],"mask-image-conic-pos":[{"mask-conic":[UC]}],"mask-image-conic-from-pos":[{"mask-conic-from":he()}],"mask-image-conic-to-pos":[{"mask-conic-to":he()}],"mask-image-conic-from-color":[{"mask-conic-from":E()}],"mask-image-conic-to-color":[{"mask-conic-to":E()}],"mask-mode":[{mask:[`alpha`,`luminance`,`match`]}],"mask-origin":[{"mask-origin":[`border`,`padding`,`content`,`fill`,`stroke`,`view`]}],"mask-position":[{mask:ce()}],"mask-repeat":[{mask:le()}],"mask-size":[{mask:ue()}],"mask-type":[{"mask-type":[`alpha`,`luminance`]}],"mask-image":[{mask:[`none`,J,q]}],filter:[{filter:[``,`none`,J,q]}],blur:[{blur:ge()}],brightness:[{brightness:[UC,J,q]}],contrast:[{contrast:[UC,J,q]}],"drop-shadow":[{"drop-shadow":[``,`none`,p,uw,iw]}],"drop-shadow-color":[{"drop-shadow":E()}],grayscale:[{grayscale:[``,UC,J,q]}],"hue-rotate":[{"hue-rotate":[UC,J,q]}],invert:[{invert:[``,UC,J,q]}],saturate:[{saturate:[UC,J,q]}],sepia:[{sepia:[``,UC,J,q]}],"backdrop-filter":[{"backdrop-filter":[``,`none`,J,q]}],"backdrop-blur":[{"backdrop-blur":ge()}],"backdrop-brightness":[{"backdrop-brightness":[UC,J,q]}],"backdrop-contrast":[{"backdrop-contrast":[UC,J,q]}],"backdrop-grayscale":[{"backdrop-grayscale":[``,UC,J,q]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[UC,J,q]}],"backdrop-invert":[{"backdrop-invert":[``,UC,J,q]}],"backdrop-opacity":[{"backdrop-opacity":[UC,J,q]}],"backdrop-saturate":[{"backdrop-saturate":[UC,J,q]}],"backdrop-sepia":[{"backdrop-sepia":[``,UC,J,q]}],"border-collapse":[{border:[`collapse`,`separate`]}],"border-spacing":[{"border-spacing":C()}],"border-spacing-x":[{"border-spacing-x":C()}],"border-spacing-y":[{"border-spacing-y":C()}],"table-layout":[{table:[`auto`,`fixed`]}],caption:[{caption:[`top`,`bottom`]}],transition:[{transition:[``,`all`,`colors`,`opacity`,`shadow`,`transform`,`none`,J,q]}],"transition-behavior":[{transition:[`normal`,`discrete`]}],duration:[{duration:[UC,`initial`,J,q]}],ease:[{ease:[`linear`,`initial`,_,J,q]}],delay:[{delay:[UC,J,q]}],animate:[{animate:[`none`,v,J,q]}],backface:[{backface:[`hidden`,`visible`]}],perspective:[{perspective:[h,J,q]}],"perspective-origin":[{"perspective-origin":x()}],rotate:[{rotate:_e()}],"rotate-x":[{"rotate-x":_e()}],"rotate-y":[{"rotate-y":_e()}],"rotate-z":[{"rotate-z":_e()}],scale:[{scale:ve()}],"scale-x":[{"scale-x":ve()}],"scale-y":[{"scale-y":ve()}],"scale-z":[{"scale-z":ve()}],"scale-3d":[`scale-3d`],skew:[{skew:ye()}],"skew-x":[{"skew-x":ye()}],"skew-y":[{"skew-y":ye()}],transform:[{transform:[J,q,``,`none`,`gpu`,`cpu`]}],"transform-origin":[{origin:x()}],"transform-style":[{transform:[`3d`,`flat`]}],translate:[{translate:be()}],"translate-x":[{"translate-x":be()}],"translate-y":[{"translate-y":be()}],"translate-z":[{"translate-z":be()}],"translate-none":[`translate-none`],accent:[{accent:E()}],appearance:[{appearance:[`none`,`auto`]}],"caret-color":[{caret:E()}],"color-scheme":[{scheme:[`normal`,`dark`,`light`,`light-dark`,`only-dark`,`only-light`]}],cursor:[{cursor:[`auto`,`default`,`pointer`,`wait`,`text`,`move`,`help`,`not-allowed`,`none`,`context-menu`,`progress`,`cell`,`crosshair`,`vertical-text`,`alias`,`copy`,`no-drop`,`grab`,`grabbing`,`all-scroll`,`col-resize`,`row-resize`,`n-resize`,`e-resize`,`s-resize`,`w-resize`,`ne-resize`,`nw-resize`,`se-resize`,`sw-resize`,`ew-resize`,`ns-resize`,`nesw-resize`,`nwse-resize`,`zoom-in`,`zoom-out`,J,q]}],"field-sizing":[{"field-sizing":[`fixed`,`content`]}],"pointer-events":[{"pointer-events":[`auto`,`none`]}],resize:[{resize:[`none`,``,`y`,`x`]}],"scroll-behavior":[{scroll:[`auto`,`smooth`]}],"scroll-m":[{"scroll-m":C()}],"scroll-mx":[{"scroll-mx":C()}],"scroll-my":[{"scroll-my":C()}],"scroll-ms":[{"scroll-ms":C()}],"scroll-me":[{"scroll-me":C()}],"scroll-mt":[{"scroll-mt":C()}],"scroll-mr":[{"scroll-mr":C()}],"scroll-mb":[{"scroll-mb":C()}],"scroll-ml":[{"scroll-ml":C()}],"scroll-p":[{"scroll-p":C()}],"scroll-px":[{"scroll-px":C()}],"scroll-py":[{"scroll-py":C()}],"scroll-ps":[{"scroll-ps":C()}],"scroll-pe":[{"scroll-pe":C()}],"scroll-pt":[{"scroll-pt":C()}],"scroll-pr":[{"scroll-pr":C()}],"scroll-pb":[{"scroll-pb":C()}],"scroll-pl":[{"scroll-pl":C()}],"snap-align":[{snap:[`start`,`end`,`center`,`align-none`]}],"snap-stop":[{snap:[`normal`,`always`]}],"snap-type":[{snap:[`none`,`x`,`y`,`both`]}],"snap-strictness":[{snap:[`mandatory`,`proximity`]}],touch:[{touch:[`auto`,`none`,`manipulation`]}],"touch-x":[{"touch-pan":[`x`,`left`,`right`]}],"touch-y":[{"touch-pan":[`y`,`up`,`down`]}],"touch-pz":[`touch-pinch-zoom`],select:[{select:[`none`,`text`,`all`,`auto`]}],"will-change":[{"will-change":[`auto`,`scroll`,`contents`,`transform`,J,q]}],fill:[{fill:[`none`,...E()]}],"stroke-w":[{stroke:[UC,aw,ew,tw]}],stroke:[{stroke:[`none`,...E()]}],"forced-color-adjust":[{"forced-color-adjust":[`auto`,`none`]}]},conflictingClassGroups:{overflow:[`overflow-x`,`overflow-y`],overscroll:[`overscroll-x`,`overscroll-y`],inset:[`inset-x`,`inset-y`,`start`,`end`,`top`,`right`,`bottom`,`left`],"inset-x":[`right`,`left`],"inset-y":[`top`,`bottom`],flex:[`basis`,`grow`,`shrink`],gap:[`gap-x`,`gap-y`],p:[`px`,`py`,`ps`,`pe`,`pt`,`pr`,`pb`,`pl`],px:[`pr`,`pl`],py:[`pt`,`pb`],m:[`mx`,`my`,`ms`,`me`,`mt`,`mr`,`mb`,`ml`],mx:[`mr`,`ml`],my:[`mt`,`mb`],size:[`w`,`h`],"font-size":[`leading`],"fvn-normal":[`fvn-ordinal`,`fvn-slashed-zero`,`fvn-figure`,`fvn-spacing`,`fvn-fraction`],"fvn-ordinal":[`fvn-normal`],"fvn-slashed-zero":[`fvn-normal`],"fvn-figure":[`fvn-normal`],"fvn-spacing":[`fvn-normal`],"fvn-fraction":[`fvn-normal`],"line-clamp":[`display`,`overflow`],rounded:[`rounded-s`,`rounded-e`,`rounded-t`,`rounded-r`,`rounded-b`,`rounded-l`,`rounded-ss`,`rounded-se`,`rounded-ee`,`rounded-es`,`rounded-tl`,`rounded-tr`,`rounded-br`,`rounded-bl`],"rounded-s":[`rounded-ss`,`rounded-es`],"rounded-e":[`rounded-se`,`rounded-ee`],"rounded-t":[`rounded-tl`,`rounded-tr`],"rounded-r":[`rounded-tr`,`rounded-br`],"rounded-b":[`rounded-br`,`rounded-bl`],"rounded-l":[`rounded-tl`,`rounded-bl`],"border-spacing":[`border-spacing-x`,`border-spacing-y`],"border-w":[`border-w-x`,`border-w-y`,`border-w-s`,`border-w-e`,`border-w-t`,`border-w-r`,`border-w-b`,`border-w-l`],"border-w-x":[`border-w-r`,`border-w-l`],"border-w-y":[`border-w-t`,`border-w-b`],"border-color":[`border-color-x`,`border-color-y`,`border-color-s`,`border-color-e`,`border-color-t`,`border-color-r`,`border-color-b`,`border-color-l`],"border-color-x":[`border-color-r`,`border-color-l`],"border-color-y":[`border-color-t`,`border-color-b`],translate:[`translate-x`,`translate-y`,`translate-none`],"translate-none":[`translate`,`translate-x`,`translate-y`,`translate-z`],"scroll-m":[`scroll-mx`,`scroll-my`,`scroll-ms`,`scroll-me`,`scroll-mt`,`scroll-mr`,`scroll-mb`,`scroll-ml`],"scroll-mx":[`scroll-mr`,`scroll-ml`],"scroll-my":[`scroll-mt`,`scroll-mb`],"scroll-p":[`scroll-px`,`scroll-py`,`scroll-ps`,`scroll-pe`,`scroll-pt`,`scroll-pr`,`scroll-pb`,`scroll-pl`],"scroll-px":[`scroll-pr`,`scroll-pl`],"scroll-py":[`scroll-pt`,`scroll-pb`],touch:[`touch-x`,`touch-y`,`touch-pz`],"touch-x":[`touch`],"touch-y":[`touch`],"touch-pz":[`touch`]},conflictingClassGroupModifiers:{"font-size":[`leading`]},orderSensitiveModifiers:[`*`,`**`,`after`,`backdrop`,`before`,`details-content`,`file`,`first-letter`,`first-line`,`marker`,`placeholder`,`selection`]}},xw=(e,{cacheSize:t,prefix:n,experimentalParseClassName:r,extend:i={},override:a={}})=>(Sw(e,`cacheSize`,t),Sw(e,`prefix`,n),Sw(e,`experimentalParseClassName`,r),Cw(e.theme,a.theme),Cw(e.classGroups,a.classGroups),Cw(e.conflictingClassGroups,a.conflictingClassGroups),Cw(e.conflictingClassGroupModifiers,a.conflictingClassGroupModifiers),Sw(e,`orderSensitiveModifiers`,a.orderSensitiveModifiers),ww(e.theme,i.theme),ww(e.classGroups,i.classGroups),ww(e.conflictingClassGroups,i.conflictingClassGroups),ww(e.conflictingClassGroupModifiers,i.conflictingClassGroupModifiers),Tw(e,i,`orderSensitiveModifiers`),e),Sw=(e,t,n)=>{n!==void 0&&(e[t]=n)},Cw=(e,t)=>{if(t)for(let n in t)Sw(e,n,t[n])},ww=(e,t)=>{if(t)for(let n in t)Tw(e,t,n)},Tw=(e,t,n)=>{let r=t[n];r!==void 0&&(e[n]=e[n]?e[n].concat(r):r)},Ew=(e,...t)=>typeof e==`function`?jC(bw,e,...t):jC(()=>xw(bw(),e),...t),Dw=jC(bw),Ow=e=>GS(e)?Dw:Ew({...e,extend:{theme:e.theme,classGroups:e.classGroups,conflictingClassGroupModifiers:e.conflictingClassGroupModifiers,conflictingClassGroups:e.conflictingClassGroups,...e.extend}}),kw=(e,t)=>{let n=US(e);return!n||!(t?.twMerge??!0)?n:((!$S.cachedTwMerge||$S.didTwMergeConfigChange)&&($S.didTwMergeConfigChange=!1,$S.cachedTwMerge=Ow($S.cachedTwMergeConfig)),$S.cachedTwMerge(n)||void 0)},{createTV:Aw,tv:jw}=eC((...e)=>t=>kw(e,t));const Mw=Aw(Sf.ui?.tv);var Nw=/^[a-z0-9]+(-[a-z0-9]+)*$/,Pw=(e,t,n,r=``)=>{let i=e.split(`:`);if(e.slice(0,1)===`@`){if(i.length<2||i.length>3)return null;r=i.shift().slice(1)}if(i.length>3||!i.length)return null;if(i.length>1){let e=i.pop(),n=i.pop(),a={provider:i.length>0?i[0]:r,prefix:n,name:e};return t&&!Fw(a)?null:a}let a=i[0],o=a.split(`-`);if(o.length>1){let e={provider:r,prefix:o.shift(),name:o.join(`-`)};return t&&!Fw(e)?null:e}if(n&&r===``){let e={provider:r,prefix:``,name:a};return t&&!Fw(e,n)?null:e}return null},Fw=(e,t)=>e?!!((t&&e.prefix===``||e.prefix)&&e.name):!1,Iw=Object.freeze({left:0,top:0,width:16,height:16}),Lw=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),Rw=Object.freeze({...Iw,...Lw}),zw=Object.freeze({...Rw,body:``,hidden:!1});function Bw(e,t){let n={};!e.hFlip!=!t.hFlip&&(n.hFlip=!0),!e.vFlip!=!t.vFlip&&(n.vFlip=!0);let r=((e.rotate||0)+(t.rotate||0))%4;return r&&(n.rotate=r),n}function Vw(e,t){let n=Bw(e,t);for(let r in zw)r in Lw?r in e&&!(r in n)&&(n[r]=Lw[r]):r in t?n[r]=t[r]:r in e&&(n[r]=e[r]);return n}function Hw(e,t){let n=e.icons,r=e.aliases||Object.create(null),i=Object.create(null);function a(e){if(n[e])return i[e]=[];if(!(e in i)){i[e]=null;let t=r[e]&&r[e].parent,n=t&&a(t);n&&(i[e]=[t].concat(n))}return i[e]}return Object.keys(n).concat(Object.keys(r)).forEach(a),i}function Uw(e,t,n){let r=e.icons,i=e.aliases||Object.create(null),a={};function o(e){a=Vw(r[e]||i[e],a)}return o(t),n.forEach(o),Vw(e,a)}function Ww(e,t){let n=[];if(typeof e!=`object`||typeof e.icons!=`object`)return n;e.not_found instanceof Array&&e.not_found.forEach(e=>{t(e,null),n.push(e)});let r=Hw(e);for(let i in r){let a=r[i];a&&(t(i,Uw(e,i,a)),n.push(i))}return n}var Gw={provider:``,aliases:{},not_found:{},...Iw};function Kw(e,t){for(let n in t)if(n in e&&typeof e[n]!=typeof t[n])return!1;return!0}function qw(e){if(typeof e!=`object`||!e)return null;let t=e;if(typeof t.prefix!=`string`||!e.icons||typeof e.icons!=`object`||!Kw(e,Gw))return null;let n=t.icons;for(let e in n){let t=n[e];if(!e||typeof t.body!=`string`||!Kw(t,zw))return null}let r=t.aliases||Object.create(null);for(let e in r){let t=r[e],i=t.parent;if(!e||typeof i!=`string`||!n[i]&&!r[i]||!Kw(t,zw))return null}return t}var Jw=Object.create(null);function Yw(e,t){return{provider:e,prefix:t,icons:Object.create(null),missing:new Set}}function Xw(e,t){let n=Jw[e]||(Jw[e]=Object.create(null));return n[t]||(n[t]=Yw(e,t))}function Zw(e,t){return qw(t)?Ww(t,(t,n)=>{n?e.icons[t]=n:e.missing.add(t)}):[]}function Qw(e,t,n){try{if(typeof n.body==`string`)return e.icons[t]={...n},!0}catch{}return!1}var $w=!1;function eT(e){return typeof e==`boolean`&&($w=e),$w}function tT(e){let t=typeof e==`string`?Pw(e,!0,$w):e;if(t){let e=Xw(t.provider,t.prefix),n=t.name;return e.icons[n]||(e.missing.has(n)?null:void 0)}}function nT(e,t){let n=Pw(e,!0,$w);if(!n)return!1;let r=Xw(n.provider,n.prefix);return t?Qw(r,n.name,t):(r.missing.add(n.name),!0)}function rT(e,t){if(typeof e!=`object`)return!1;if(typeof t!=`string`&&(t=e.provider||``),$w&&!t&&!e.prefix){let t=!1;return qw(e)&&(e.prefix=``,Ww(e,(e,n)=>{nT(e,n)&&(t=!0)})),t}let n=e.prefix;return Fw({prefix:n,name:`a`})?!!Zw(Xw(t,n),e):!1}var iT=Object.freeze({width:null,height:null}),aT=Object.freeze({...iT,...Lw}),oT=/(-?[0-9.]*[0-9]+[0-9.]*)/g,sT=/^-?[0-9.]*[0-9]+[0-9.]*$/g;function cT(e,t,n){if(t===1)return e;if(n||=100,typeof e==`number`)return Math.ceil(e*t*n)/n;if(typeof e!=`string`)return e;let r=e.split(oT);if(r===null||!r.length)return e;let i=[],a=r.shift(),o=sT.test(a);for(;;){if(o){let e=parseFloat(a);isNaN(e)?i.push(a):i.push(Math.ceil(e*t*n)/n)}else i.push(a);if(a=r.shift(),a===void 0)return i.join(``);o=!o}}function lT(e,t=`defs`){let n=``,r=e.indexOf(`<`+t);for(;r>=0;){let i=e.indexOf(`>`,r),a=e.indexOf(``,a);if(o===-1)break;n+=e.slice(i+1,a).trim(),e=e.slice(0,r).trim()+e.slice(o+1)}return{defs:n,content:e}}function uT(e,t){return e?``+e+``+t:t}function dT(e,t,n){let r=lT(e);return uT(r.defs,t+r.content+n)}var fT=e=>e===`unset`||e===`undefined`||e===`none`;function pT(e,t){let n={...Rw,...e},r={...aT,...t},i={left:n.left,top:n.top,width:n.width,height:n.height},a=n.body;[n,r].forEach(e=>{let t=[],n=e.hFlip,r=e.vFlip,o=e.rotate;n?r?o+=2:(t.push(`translate(`+(i.width+i.left).toString()+` `+(0-i.top).toString()+`)`),t.push(`scale(-1 1)`),i.top=i.left=0):r&&(t.push(`translate(`+(0-i.left).toString()+` `+(i.height+i.top).toString()+`)`),t.push(`scale(1 -1)`),i.top=i.left=0);let s;switch(o<0&&(o-=Math.floor(o/4)*4),o%=4,o){case 1:s=i.height/2+i.top,t.unshift(`rotate(90 `+s.toString()+` `+s.toString()+`)`);break;case 2:t.unshift(`rotate(180 `+(i.width/2+i.left).toString()+` `+(i.height/2+i.top).toString()+`)`);break;case 3:s=i.width/2+i.left,t.unshift(`rotate(-90 `+s.toString()+` `+s.toString()+`)`);break}o%2==1&&(i.left!==i.top&&(s=i.left,i.left=i.top,i.top=s),i.width!==i.height&&(s=i.width,i.width=i.height,i.height=s)),t.length&&(a=dT(a,``,``))});let o=r.width,s=r.height,c=i.width,l=i.height,u,d;o===null?(d=s===null?`1em`:s===`auto`?l:s,u=cT(d,c/l)):(u=o===`auto`?c:o,d=s===null?cT(u,l/c):s===`auto`?l:s);let f={},p=(e,t)=>{fT(t)||(f[e]=t.toString())};p(`width`,u),p(`height`,d);let m=[i.left,i.top,c,l];return f.viewBox=m.join(` `),{attributes:f,viewBox:m,body:a}}var mT=/\sid="(\S+)"/g,hT=`IconifyId`+Date.now().toString(16)+(Math.random()*16777216|0).toString(16),gT=0;function _T(e,t=hT){let n=[],r;for(;r=mT.exec(e);)n.push(r[1]);if(!n.length)return e;let i=`suffix`+(Math.random()*16777216|Date.now()).toString(16);return n.forEach(n=>{let r=typeof t==`function`?t(n):t+(gT++).toString(),a=n.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);e=e.replace(RegExp(`([#;"])(`+a+`)([")]|\\.[a-z])`,`g`),`$1`+r+i+`$3`)}),e=e.replace(new RegExp(i,`g`),``),e}var vT=Object.create(null);function yT(e,t){vT[e]=t}function bT(e){return vT[e]||vT[``]}function xT(e){let t;if(typeof e.resources==`string`)t=[e.resources];else if(t=e.resources,!(t instanceof Array)||!t.length)return null;return{resources:t,path:e.path||`/`,maxURL:e.maxURL||500,rotate:e.rotate||750,timeout:e.timeout||5e3,random:e.random===!0,index:e.index||0,dataAfterTimeout:e.dataAfterTimeout!==!1}}for(var ST=Object.create(null),CT=[`https://api.simplesvg.com`,`https://api.unisvg.com`],wT=[];CT.length>0;)CT.length===1||Math.random()>.5?wT.push(CT.shift()):wT.push(CT.pop());ST[``]=xT({resources:[`https://api.iconify.design`].concat(wT)});function TT(e,t){let n=xT(t);return n===null?!1:(ST[e]=n,!0)}function ET(e){return ST[e]}var DT=(()=>{let e;try{if(e=fetch,typeof e==`function`)return e}catch{}})();function OT(e,t){let n=ET(e);if(!n)return 0;let r;if(!n.maxURL)r=0;else{let e=0;n.resources.forEach(t=>{let n=t;e=Math.max(e,n.length)});let i=t+`.json?icons=`;r=n.maxURL-e-n.path.length-i.length}return r}function kT(e){return e===404}var AT=(e,t,n)=>{let r=[],i=OT(e,t),a=`icons`,o={type:a,provider:e,prefix:t,icons:[]},s=0;return n.forEach((n,c)=>{s+=n.length+1,s>=i&&c>0&&(r.push(o),o={type:a,provider:e,prefix:t,icons:[]},s=n.length),o.icons.push(n)}),r.push(o),r};function jT(e){if(typeof e==`string`){let t=ET(e);if(t)return t.path}return`/`}var MT={prepare:AT,send:(e,t,n)=>{if(!DT){n(`abort`,424);return}let r=jT(t.provider);switch(t.type){case`icons`:{let e=t.prefix,n=t.icons.join(`,`),i=new URLSearchParams({icons:n});r+=e+`.json?`+i.toString();break}case`custom`:{let e=t.uri;r+=e.slice(0,1)===`/`?e.slice(1):e;break}default:n(`abort`,400);return}let i=503;DT(e+r).then(e=>{let t=e.status;if(t!==200){setTimeout(()=>{n(kT(t)?`abort`:`next`,t)});return}return i=501,e.json()}).then(e=>{if(typeof e!=`object`||!e){setTimeout(()=>{e===404?n(`abort`,e):n(`next`,i)});return}setTimeout(()=>{n(`success`,e)})}).catch(()=>{n(`next`,i)})}};function NT(e){let t={loaded:[],missing:[],pending:[]},n=Object.create(null);e.sort((e,t)=>e.provider===t.provider?e.prefix===t.prefix?e.name.localeCompare(t.name):e.prefix.localeCompare(t.prefix):e.provider.localeCompare(t.provider));let r={provider:``,prefix:``,name:``};return e.forEach(e=>{if(r.name===e.name&&r.prefix===e.prefix&&r.provider===e.provider)return;r=e;let i=e.provider,a=e.prefix,o=e.name,s=n[i]||(n[i]=Object.create(null)),c=s[a]||(s[a]=Xw(i,a)),l;l=o in c.icons?t.loaded:a===``||c.missing.has(o)?t.missing:t.pending;let u={provider:i,prefix:a,name:o};l.push(u)}),t}function PT(e,t){e.forEach(e=>{let n=e.loaderCallbacks;n&&(e.loaderCallbacks=n.filter(e=>e.id!==t))})}function FT(e){e.pendingCallbacksFlag||(e.pendingCallbacksFlag=!0,setTimeout(()=>{e.pendingCallbacksFlag=!1;let t=e.loaderCallbacks?e.loaderCallbacks.slice(0):[];if(!t.length)return;let n=!1,r=e.provider,i=e.prefix;t.forEach(t=>{let a=t.icons,o=a.pending.length;a.pending=a.pending.filter(t=>{if(t.prefix!==i)return!0;let o=t.name;if(e.icons[o])a.loaded.push({provider:r,prefix:i,name:o});else if(e.missing.has(o))a.missing.push({provider:r,prefix:i,name:o});else return n=!0,!0;return!1}),a.pending.length!==o&&(n||PT([e],t.id),t.callback(a.loaded.slice(0),a.missing.slice(0),a.pending.slice(0),t.abort))})}))}var IT=0;function LT(e,t,n){let r=IT++,i=PT.bind(null,n,r);if(!t.pending.length)return i;let a={id:r,icons:t,callback:e,abort:i};return n.forEach(e=>{(e.loaderCallbacks||=[]).push(a)}),i}function RT(e,t=!0,n=!1){let r=[];return e.forEach(e=>{let i=typeof e==`string`?Pw(e,t,n):e;i&&r.push(i)}),r}var zT={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function BT(e,t,n,r){let i=e.resources.length,a=e.random?Math.floor(Math.random()*i):e.index,o;if(e.random){let t=e.resources.slice(0);for(o=[];t.length>1;){let e=Math.floor(Math.random()*t.length);o.push(t[e]),t=t.slice(0,e).concat(t.slice(e+1))}o=o.concat(t)}else o=e.resources.slice(a).concat(e.resources.slice(0,a));let s=Date.now(),c=`pending`,l=0,u,d=null,f=[],p=[];typeof r==`function`&&p.push(r);function m(){d&&=(clearTimeout(d),null)}function h(){c===`pending`&&(c=`aborted`),m(),f.forEach(e=>{e.status===`pending`&&(e.status=`aborted`)}),f=[]}function g(e,t){t&&(p=[]),typeof e==`function`&&p.push(e)}function _(){return{startTime:s,payload:t,status:c,queriesSent:l,queriesPending:f.length,subscribe:g,abort:h}}function v(){c=`failed`,p.forEach(e=>{e(void 0,u)})}function y(){f.forEach(e=>{e.status===`pending`&&(e.status=`aborted`)}),f=[]}function b(t,n,r){let i=n!==`success`;switch(f=f.filter(e=>e!==t),c){case`pending`:break;case`failed`:if(i||!e.dataAfterTimeout)return;break;default:return}if(n===`abort`){u=r,v();return}if(i){u=r,f.length||(o.length?x():v());return}if(m(),y(),!e.random){let n=e.resources.indexOf(t.resource);n!==-1&&n!==e.index&&(e.index=n)}c=`completed`,p.forEach(e=>{e(r)})}function x(){if(c!==`pending`)return;m();let r=o.shift();if(r===void 0){if(f.length){d=setTimeout(()=>{m(),c===`pending`&&(y(),v())},e.timeout);return}v();return}let i={status:`pending`,resource:r,callback:(e,t)=>{b(i,e,t)}};f.push(i),l++,d=setTimeout(x,e.rotate),n(r,t,i.callback)}return setTimeout(x),_}function VT(e){let t={...zT,...e},n=[];function r(){n=n.filter(e=>e().status===`pending`)}function i(e,i,a){let o=BT(t,e,i,(e,t)=>{r(),a&&a(e,t)});return n.push(o),o}function a(e){return n.find(t=>e(t))||null}return{query:i,find:a,setIndex:e=>{t.index=e},getIndex:()=>t.index,cleanup:r}}function HT(){}var UT=Object.create(null);function WT(e){if(!UT[e]){let t=ET(e);if(!t)return;UT[e]={config:t,redundancy:VT(t)}}return UT[e]}function GT(e,t,n){let r,i;if(typeof e==`string`){let t=bT(e);if(!t)return n(void 0,424),HT;i=t.send;let a=WT(e);a&&(r=a.redundancy)}else{let t=xT(e);if(t){r=VT(t);let n=bT(e.resources?e.resources[0]:``);n&&(i=n.send)}}return!r||!i?(n(void 0,424),HT):r.query(t,i,n)().abort}function KT(){}function qT(e){e.iconsLoaderFlag||(e.iconsLoaderFlag=!0,setTimeout(()=>{e.iconsLoaderFlag=!1,FT(e)}))}function JT(e){let t=[],n=[];return e.forEach(e=>{(e.match(Nw)?t:n).push(e)}),{valid:t,invalid:n}}function YT(e,t,n){function r(){let n=e.pendingIcons;t.forEach(t=>{n&&n.delete(t),e.icons[t]||e.missing.add(t)})}if(n&&typeof n==`object`)try{if(!Zw(e,n).length){r();return}}catch(e){console.error(e)}r(),qT(e)}function XT(e,t){e instanceof Promise?e.then(e=>{t(e)}).catch(()=>{t(null)}):t(e)}function ZT(e,t){e.iconsToLoad?e.iconsToLoad=e.iconsToLoad.concat(t).sort():e.iconsToLoad=t,e.iconsQueueFlag||(e.iconsQueueFlag=!0,setTimeout(()=>{e.iconsQueueFlag=!1;let{provider:t,prefix:n}=e,r=e.iconsToLoad;if(delete e.iconsToLoad,!r||!r.length)return;let i=e.loadIcon;if(e.loadIcons&&(r.length>1||!i)){XT(e.loadIcons(r,n,t),t=>{YT(e,r,t)});return}if(i){r.forEach(r=>{XT(i(r,n,t),t=>{YT(e,[r],t?{prefix:n,icons:{[r]:t}}:null)})});return}let{valid:a,invalid:o}=JT(r);if(o.length&&YT(e,o,null),!a.length)return;let s=n.match(Nw)?bT(t):null;if(!s){YT(e,a,null);return}s.prepare(t,n,a).forEach(n=>{GT(t,n,t=>{YT(e,n.icons,t)})})}))}var QT=(e,t)=>{let n=NT(RT(e,!0,eT()));if(!n.pending.length){let e=!0;return t&&setTimeout(()=>{e&&t(n.loaded,n.missing,n.pending,KT)}),()=>{e=!1}}let r=Object.create(null),i=[],a,o;return n.pending.forEach(e=>{let{provider:t,prefix:n}=e;if(n===o&&t===a)return;a=t,o=n,i.push(Xw(t,n));let s=r[t]||(r[t]=Object.create(null));s[n]||(s[n]=[])}),n.pending.forEach(e=>{let{provider:t,prefix:n,name:i}=e,a=Xw(t,n),o=a.pendingIcons||=new Set;o.has(i)||(o.add(i),r[t][n].push(i))}),i.forEach(e=>{let t=r[e.provider][e.prefix];t.length&&ZT(e,t)}),t?LT(t,n,i):KT};function $T(e,t){let n={...e};for(let e in t){let r=t[e],i=typeof r;e in iT?(r===null||r&&(i===`string`||i===`number`))&&(n[e]=r):i===typeof n[e]&&(n[e]=e===`rotate`?r%4:r)}return n}var eE=/[\s,]+/;function tE(e,t){t.split(eE).forEach(t=>{switch(t.trim()){case`horizontal`:e.hFlip=!0;break;case`vertical`:e.vFlip=!0;break}})}function nE(e,t=0){let n=e.replace(/^-?[0-9.]*/,``);function r(e){for(;e<0;)e+=4;return e%4}if(n===``){let t=parseInt(e);return isNaN(t)?0:r(t)}else if(n!==e){let t=0;switch(n){case`%`:t=25;break;case`deg`:t=90}if(t){let i=parseFloat(e.slice(0,e.length-n.length));return isNaN(i)?0:(i/=t,i%1==0?r(i):0)}}return t}function rE(e,t){let n=e.indexOf(`xlink:`)===-1?``:` xmlns:xlink="http://www.w3.org/1999/xlink"`;for(let e in t)n+=` `+e+`="`+t[e]+`"`;return``+e+``}function iE(e){return e.replace(/"/g,`'`).replace(/%/g,`%25`).replace(/#/g,`%23`).replace(//g,`%3E`).replace(/\s+/g,` `)}function aE(e){return`data:image/svg+xml,`+iE(e)}function oE(e){return`url("`+aE(e)+`")`}var sE={...aT,inline:!1},cE={xmlns:`http://www.w3.org/2000/svg`,"xmlns:xlink":`http://www.w3.org/1999/xlink`,"aria-hidden":!0,role:`img`},lE={display:`inline-block`},uE={backgroundColor:`currentColor`},dE={backgroundColor:`transparent`},fE={Image:`var(--svg)`,Repeat:`no-repeat`,Size:`100% 100%`},pE={webkitMask:uE,mask:uE,background:dE};for(let e in pE){let t=pE[e];for(let n in fE)t[e+n]=fE[n]}var mE={};[`horizontal`,`vertical`].forEach(e=>{let t=e.slice(0,1)+`Flip`;mE[e+`-flip`]=t,mE[e.slice(0,1)+`-flip`]=t,mE[e+`Flip`]=t});function hE(e){return e+(e.match(/^[-0-9.]+$/)?`px`:``)}var gE=(e,t)=>{let n=$T(sE,t),r={...cE},i=t.mode||`svg`,a={},o=t.style,s=typeof o==`object`&&!(o instanceof Array)?o:{};for(let e in t){let i=t[e];if(i!==void 0)switch(e){case`icon`:case`style`:case`onLoad`:case`mode`:case`ssr`:break;case`inline`:case`hFlip`:case`vFlip`:n[e]=i===!0||i===`true`||i===1;break;case`flip`:typeof i==`string`&&tE(n,i);break;case`color`:a.color=i;break;case`rotate`:typeof i==`string`?n[e]=nE(i):typeof i==`number`&&(n[e]=i);break;case`ariaHidden`:case`aria-hidden`:i!==!0&&i!==`true`&&delete r[`aria-hidden`];break;default:{let t=mE[e];t?(i===!0||i===`true`||i===1)&&(n[t]=!0):sE[e]===void 0&&(r[e]=i)}}}let c=pT(e,n),l=c.attributes;if(n.inline&&(a.verticalAlign=`-0.125em`),i===`svg`){r.style={...a,...s},Object.assign(r,l);let e=0,n=t.id;return typeof n==`string`&&(n=n.replace(/-/g,`_`)),r.innerHTML=_T(c.body,n?()=>n+`ID`+ e++:`iconifyVue`),Mc(`svg`,r)}let{body:u,width:d,height:f}=e,p=i===`mask`||(i===`bg`?!1:u.indexOf(`currentColor`)!==-1),m=rE(u,{...l,width:d+``,height:f+``});return r.style={...a,"--svg":oE(m),width:hE(l.width),height:hE(l.height),...lE,...p?uE:dE,...s},Mc(`span`,r)};if(eT(!0),yT(``,MT),typeof document<`u`&&typeof window<`u`){let e=window;if(e.IconifyPreload!==void 0){let t=e.IconifyPreload,n=`Invalid IconifyPreload syntax.`;typeof t==`object`&&t&&(t instanceof Array?t:[t]).forEach(e=>{try{(typeof e!=`object`||!e||e instanceof Array||typeof e.icons!=`object`||typeof e.prefix!=`string`||!rT(e))&&console.error(n)}catch{console.error(n)}})}if(e.IconifyProviders!==void 0){let t=e.IconifyProviders;if(typeof t==`object`&&t)for(let e in t){let n=`IconifyProviders[`+e+`] is invalid.`;try{let r=t[e];if(typeof r!=`object`||!r||r.resources===void 0)continue;TT(e,r)||console.error(n)}catch{console.error(n)}}}}var _E={...Rw,body:``},vE=P((e,{emit:t})=>{let n=j(null);function r(){n.value&&=(n.value.abort?.(),null)}let i=j(!!e.ssr),a=j(``),o=Zn(null);function s(){let i=e.icon;if(typeof i==`object`&&i&&typeof i.body==`string`)return a.value=``,{data:i};let o;if(typeof i!=`string`||(o=Pw(i,!1,!0))===null)return null;let s=tT(o);if(!s){let e=n.value;return(!e||e.name!==i)&&(s===null?n.value={name:i}:n.value={name:i,abort:QT([o],c)}),null}r(),a.value!==i&&(a.value=i,zr(()=>{t(`load`,i)}));let l=e.customise;if(l){s=Object.assign({},s);let e=l(s.body,o.name,o.prefix,o.provider);typeof e==`string`&&(s.body=e)}let u=[`iconify`];return o.prefix!==``&&u.push(`iconify--`+o.prefix),o.provider!==``&&u.push(`iconify--`+o.provider),{data:s,classes:u}}function c(){let e=s();e?e.data!==o.value?.data&&(o.value=e):o.value=null}return i.value?c():Aa(()=>{i.value=!0,c()}),pi(()=>e.icon,c),Pa(r),()=>{let t=o.value;if(!t)return gE(_E,e);let n=e;return t.classes&&(n={...e,class:t.classes.join(` `)}),gE({...Rw,...t.data},n)}},{props:[`icon`,`mode`,`ssr`,`width`,`height`,`style`,`color`,`inline`,`rotate`,`hFlip`,`horizontalFlip`,`vFlip`,`verticalFlip`,`flip`,`id`,`ariaHidden`,`customise`,`title`],emits:[`load`]}),yE={__name:`Icon`,props:{name:{type:null,required:!0}},setup(e){return(t,n)=>typeof e.name==`string`?(L(),z(M(vE),{key:0,icon:e.name.replace(/^i-/,``)},null,8,[`icon`])):(L(),z(Ua(e.name),{key:1}))}};const bE=Symbol(`nuxt-ui.avatar-group`);function xE(e){let t=oi(bE,void 0),n=W(()=>e.size??t?.value.size);return ai(bE,W(()=>({size:n.value}))),{size:n}}var SE={slots:{root:`relative inline-flex items-center justify-center shrink-0`,base:`rounded-full ring ring-bg flex items-center justify-center text-inverted font-medium whitespace-nowrap`},variants:{color:{primary:`bg-primary`,secondary:`bg-secondary`,success:`bg-success`,info:`bg-info`,warning:`bg-warning`,error:`bg-error`,neutral:`bg-inverted`},size:{"3xs":`h-[4px] min-w-[4px] text-[4px]`,"2xs":`h-[5px] min-w-[5px] text-[5px]`,xs:`h-[6px] min-w-[6px] text-[6px]`,sm:`h-[7px] min-w-[7px] text-[7px]`,md:`h-[8px] min-w-[8px] text-[8px]`,lg:`h-[9px] min-w-[9px] text-[9px]`,xl:`h-[10px] min-w-[10px] text-[10px]`,"2xl":`h-[11px] min-w-[11px] text-[11px]`,"3xl":`h-[12px] min-w-[12px] text-[12px]`},position:{"top-right":`top-0 right-0`,"bottom-right":`bottom-0 right-0`,"top-left":`top-0 left-0`,"bottom-left":`bottom-0 left-0`},inset:{false:``},standalone:{false:`absolute`}},compoundVariants:[{position:`top-right`,inset:!1,class:`-translate-y-1/2 translate-x-1/2 transform`},{position:`bottom-right`,inset:!1,class:`translate-y-1/2 translate-x-1/2 transform`},{position:`top-left`,inset:!1,class:`-translate-y-1/2 -translate-x-1/2 transform`},{position:`bottom-left`,inset:!1,class:`translate-y-1/2 -translate-x-1/2 transform`}],defaultVariants:{size:`md`,color:`primary`,position:`top-right`}},CE=Object.assign({inheritAttrs:!1},{__name:`Chip`,props:ho({as:{type:null,required:!1},text:{type:[String,Number],required:!1},color:{type:null,required:!1},size:{type:null,required:!1},position:{type:null,required:!1},inset:{type:Boolean,required:!1,default:!1},standalone:{type:Boolean,required:!1,default:!1},class:{type:null,required:!1},ui:{type:null,required:!1}},{show:{type:Boolean,default:!0},showModifiers:{}}),emits:[`update:show`],setup(e){let t=e,n=Lo(e,`show`,{type:Boolean,default:!0}),{size:r}=xE(t),i=wf(),a=W(()=>Mw({extend:Mw(SE),...i.ui?.chip||{}})({color:t.color,size:r.value,position:t.position,inset:t.inset,standalone:t.standalone}));return(r,i)=>(L(),z(M(K),{as:e.as,"data-slot":`root`,class:A(a.value.root({class:[t.ui?.root,t.class]}))},{default:N(()=>[V(M(Yg),it(ec(r.$attrs)),{default:N(()=>[F(r.$slots,`default`)]),_:3},16),n.value?(L(),R(`span`,{key:0,"data-slot":`base`,class:A(a.value.base({class:t.ui?.base}))},[F(r.$slots,`content`,{},()=>[nc(ft(e.text),1)])],2)):H(``,!0)]),_:3},8,[`as`,`class`]))}}),wE={slots:{root:`inline-flex items-center justify-center shrink-0 select-none rounded-full align-middle bg-elevated`,image:`h-full w-full rounded-[inherit] object-cover`,fallback:`font-medium leading-none text-muted truncate`,icon:`text-muted shrink-0`},variants:{size:{"3xs":{root:`size-4 text-[8px]`},"2xs":{root:`size-5 text-[10px]`},xs:{root:`size-6 text-xs`},sm:{root:`size-7 text-sm`},md:{root:`size-8 text-base`},lg:{root:`size-9 text-lg`},xl:{root:`size-10 text-xl`},"2xl":{root:`size-11 text-[22px]`},"3xl":{root:`size-12 text-2xl`}}},defaultVariants:{size:`md`}},TE=Object.assign({inheritAttrs:!1},{__name:`Avatar`,props:{as:{type:null,required:!1},src:{type:String,required:!1},alt:{type:String,required:!1},icon:{type:null,required:!1},text:{type:String,required:!1},size:{type:null,required:!1},chip:{type:[Boolean,Object],required:!1},class:{type:null,required:!1},style:{type:null,required:!1},ui:{type:null,required:!1}},setup(e){let t=e,n=W(()=>typeof t.as==`string`||typeof t.as?.render==`function`?{root:t.as}:Af(t.as,{root:`span`})),r=W(()=>t.text||(t.alt||``).split(` `).map(e=>e.charAt(0)).join(``).substring(0,2)),i=wf(),{size:a}=xE(t),o=W(()=>Mw({extend:Mw(wE),...i.ui?.avatar||{}})({size:a.value})),s=W(()=>({"3xs":16,"2xs":20,xs:24,sm:28,md:32,lg:36,xl:40,"2xl":44,"3xl":48})[t.size||`md`]),c=j(!1);pi(()=>t.src,()=>{c.value&&=!1});function l(){c.value=!0}return(i,a)=>(L(),z(Ua(t.chip?CE:M(K)),U({as:n.value.root},t.chip?typeof t.chip==`object`?{inset:!0,...t.chip}:{inset:!0}:{},{"data-slot":`root`,class:o.value.root({class:[t.ui?.root,t.class]}),style:t.style}),{default:N(()=>[e.src&&!c.value?(L(),z(Ua(n.value.img||M(`img`)),U({key:0,src:e.src,alt:e.alt,width:s.value,height:s.value},i.$attrs,{"data-slot":`image`,class:o.value.image({class:t.ui?.image}),onError:l}),null,16,[`src`,`alt`,`width`,`height`,`class`])):(L(),z(M(Yg),it(U({key:1},i.$attrs)),{default:N(()=>[F(i.$slots,`default`,{},()=>[e.icon?(L(),z(yE,{key:0,name:e.icon,"data-slot":`icon`,class:A(o.value.icon({class:t.ui?.icon}))},null,8,[`name`,`class`])):(L(),R(`span`,{key:1,"data-slot":`fallback`,class:A(o.value.fallback({class:t.ui?.fallback}))},ft(r.value||`\xA0`),3))])]),_:3},16))]),_:3},16,[`as`,`class`,`style`]))}});function EE(e){let t=wf(),n=W(()=>tr(e)),r=W(()=>n.value.icon&&n.value.leading||n.value.icon&&!n.value.trailing||n.value.loading&&!n.value.trailing||!!n.value.leadingIcon);return{isLeading:r,isTrailing:W(()=>n.value.icon&&n.value.trailing||n.value.loading&&n.value.trailing||!!n.value.trailingIcon),leadingIconName:W(()=>n.value.loading?n.value.loadingIcon||t.ui.icons.loading:n.value.leadingIcon||n.value.icon),trailingIconName:W(()=>n.value.loading&&!r.value?n.value.loadingIcon||t.ui.icons.loading:n.value.trailingIcon||n.value.icon)}}const DE=Symbol(`nuxt-ui.field-group`);function OE(e){let t=oi(DE,void 0);return{orientation:W(()=>t?.value.orientation),size:W(()=>e?.size??t?.value.size)}}const kE=Symbol(`nuxt-ui.form-options`),AE=Symbol(`nuxt-ui.form-events`),jE=Symbol(`nuxt-ui.form-state`),ME=Symbol(`nuxt-ui.form-field`),NE=Symbol(`nuxt-ui.input-id`),PE=Symbol(`nuxt-ui.form-inputs`),FE=Symbol(`nuxt-ui.form-loading`),IE=Symbol(`nuxt-ui.form-errors`);function LE(e,t){let n=oi(kE,void 0),r=oi(AE,void 0),i=oi(ME,void 0),a=oi(NE,void 0);ai(ME,void 0),i&&a&&(t?.bind===!1?a.value=void 0:e?.id&&(a.value=e?.id));function o(e,t,n){r&&i&&t&&r.emit({type:e,name:t,eager:n})}function s(){o(`blur`,i?.value.name)}function c(){o(`focus`,i?.value.name)}function l(){o(`change`,i?.value.name)}let u=Md(()=>{o(`input`,i?.value.name,!t?.deferInputValidation||i?.value.eagerValidation)},i?.value.validateOnInputDelay??n?.value.validateOnInputDelay??0);return{id:W(()=>e?.id??a?.value),name:W(()=>e?.name??i?.value.name),size:W(()=>e?.size??i?.value.size),color:W(()=>i?.value.error?`error`:e?.color),highlight:W(()=>i?.value.error?!0:e?.highlight),disabled:W(()=>n?.value.disabled||e?.disabled),emitFormBlur:s,emitFormInput:u,emitFormChange:l,emitFormFocus:c,ariaAttrs:W(()=>{if(!i?.value)return;let e=[`error`,`hint`,`description`,`help`].filter(e=>i?.value?.[e]).map(e=>`${i?.value.ariaId}-${e}`)||[],t={"aria-invalid":!!i?.value.error};return e.length>0&&(t[`aria-describedby`]=e.join(` `)),t})}}const RE=`active.activeClass.ariaCurrentValue.as.disabled.download.exact.exactActiveClass.exactHash.exactQuery.external.form.formaction.formenctype.formmethod.formnovalidate.formtarget.href.hreflang.inactiveClass.media.noPrefetch.noRel.onClick.ping.prefetch.prefetchOn.prefetchedClass.referrerpolicy.rel.replace.target.title.to.trailingSlash.type.viewTransition`.split(`.`);function zE(e){let t=Object.keys(e),n=t.filter(e=>e.startsWith(`aria-`)),r=t.filter(e=>e.startsWith(`data-`));return jd(e,...RE,...n,...r)}function BE(e,t){let n=Ff(e,t).reduce((e,t)=>(t.type===`added`&&e.add(t.key),e),new Set);return Pf(Object.fromEntries(Object.entries(e).filter(([e])=>!n.has(e))),Object.fromEntries(Object.entries(t).filter(([e])=>!n.has(e))))}String.fromCharCode;var VE=/^[\s\w\0+.-]{2,}:([/\\]{1,2})/,HE=/^[\s\w\0+.-]{2,}:([/\\]{2})?/,UE=/^([/\\]\s*){2,}[^/\\]/;function WE(e,t={}){return typeof t==`boolean`&&(t={acceptRelative:t}),t.strict?VE.test(e):HE.test(e)||(t.acceptRelative?UE.test(e):!1)}var GE={__name:`LinkBase`,props:{as:{type:String,required:!1,default:`button`},type:{type:String,required:!1,default:`button`},disabled:{type:Boolean,required:!1},onClick:{type:[Function,Array],required:!1},href:{type:String,required:!1},navigate:{type:Function,required:!1},target:{type:[String,Object,null],required:!1},rel:{type:[String,Object,null],required:!1},active:{type:Boolean,required:!1},isExternal:{type:Boolean,required:!1}},setup(e){let t=e;function n(e){if(t.disabled){e.stopPropagation(),e.preventDefault();return}if(t.onClick)for(let n of Array.isArray(t.onClick)?t.onClick:[t.onClick])n(e);t.href&&t.navigate&&!t.isExternal&&t.navigate(e)}return(t,r)=>(L(),z(M(K),U(e.href?{as:`a`,href:e.disabled?void 0:e.href,"aria-disabled":e.disabled?`true`:void 0,role:e.disabled?`link`:void 0,tabindex:e.disabled?-1:void 0}:e.as===`button`?{as:e.as,type:e.type,disabled:e.disabled}:{as:e.as},{rel:e.rel,target:e.target,onClick:n}),{default:N(()=>[F(t.$slots,`default`)]),_:3},16,[`rel`,`target`]))}},KE={base:`focus-visible:outline-primary`,variants:{active:{true:`text-primary`,false:`text-muted`},disabled:{true:`cursor-not-allowed opacity-75`}},compoundVariants:[{active:!1,disabled:!1,class:[`hover:text-default`,`transition-colors`]}]},qE=Object.assign({inheritAttrs:!1},{__name:`Link`,props:{as:{type:null,required:!1,default:`button`},href:{type:null,required:!1},external:{type:Boolean,required:!1},target:{type:[String,Object,null],required:!1},rel:{type:[String,Object,null],required:!1},noRel:{type:Boolean,required:!1},type:{type:null,required:!1,default:`button`},disabled:{type:Boolean,required:!1},active:{type:Boolean,required:!1,default:void 0},exact:{type:Boolean,required:!1},exactQuery:{type:[Boolean,String],required:!1},exactHash:{type:Boolean,required:!1},inactiveClass:{type:String,required:!1},custom:{type:Boolean,required:!1},raw:{type:Boolean,required:!1},class:{type:null,required:!1},activeClass:{type:String,required:!1},exactActiveClass:{type:String,required:!1},ariaCurrentValue:{type:String,required:!1,default:`page`},viewTransition:{type:Boolean,required:!1},to:{type:null,required:!1},replace:{type:Boolean,required:!1}},setup(e){let t=e,n=uh(),r=wf(),i=yg(Ad(t,`as`,`type`,`disabled`,`active`,`exact`,`exactQuery`,`exactHash`,`activeClass`,`inactiveClass`,`to`,`href`,`raw`,`custom`,`class`,`noRel`)),a=W(()=>Mw({extend:Mw(KE),...Af({variants:{active:{true:qf(r.ui?.link?.variants?.active?.true,t.activeClass),false:qf(r.ui?.link?.variants?.active?.false,t.inactiveClass)}}},r.ui?.link||{})})),o=W(()=>t.to??t.href),s=W(()=>t.external?!0:o.value?typeof o.value==`string`&&WE(o.value,{acceptRelative:!0}):!1),c=W(()=>!!t.target&&t.target!==`_self`),l=W(()=>t.noRel?null:t.rel===void 0?s.value||c.value?`noopener noreferrer`:null:t.rel||null);function u({route:e,isActive:r,isExactActive:i}){if(t.active!==void 0)return t.active;if(!o.value)return!1;if(t.exactQuery===`partial`){if(!BE(e.query,n.query))return!1}else if(t.exactQuery===!0&&!Pf(e.query,n.query))return!1;return t.exactHash&&e.hash!==n.hash?!1:!!(t.exact&&i||!t.exact&&r)}function d({route:e,isActive:n,isExactActive:r}={}){let i=u({route:e,isActive:n,isExactActive:r});return t.raw?[t.class,i?t.activeClass:t.inactiveClass]:a.value({class:t.class,active:i,disabled:t.disabled})}return(n,r)=>!s.value&&o.value?(L(),z(M(th),U({key:0},M(i),{to:o.value,custom:``}),{default:N(({href:r,navigate:i,route:a,isActive:o,isExactActive:c})=>[e.custom?F(n.$slots,`default`,it(U({key:0},{...n.$attrs,...e.exact&&c?{"aria-current":t.ariaCurrentValue}:{},as:e.as,type:e.type,disabled:e.disabled,href:r,navigate:i,rel:l.value,target:e.target,isExternal:s.value,active:u({route:a,isActive:o,isExactActive:c})}))):(L(),z(GE,U({key:1},{...n.$attrs,...e.exact&&c?{"aria-current":t.ariaCurrentValue}:{},as:e.as,type:e.type,disabled:e.disabled,href:r,navigate:i,rel:l.value,target:e.target,isExternal:s.value},{class:d({route:a,isActive:o,isExactActive:c})}),{default:N(()=>[F(n.$slots,`default`,{active:u({route:a,isActive:o,isExactActive:c})})]),_:2},1040,[`class`]))]),_:3},16,[`to`])):(L(),R(I,{key:1},[e.custom?F(n.$slots,`default`,it(U({key:0},{...n.$attrs,as:e.as,type:e.type,disabled:e.disabled,href:o.value,rel:l.value,target:e.target,active:e.active,isExternal:s.value}))):(L(),z(GE,U({key:1},{...n.$attrs,as:e.as,type:e.type,disabled:e.disabled,href:o.value,rel:l.value,target:e.target,isExternal:s.value},{class:d()}),{default:N(()=>[F(n.$slots,`default`,{active:e.active})]),_:3},16,[`class`]))],64))}}),JE={slots:{base:[`rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75`,`transition-colors`],label:`truncate`,leadingIcon:`shrink-0`,leadingAvatar:`shrink-0`,leadingAvatarSize:``,trailingIcon:`shrink-0`},variants:{fieldGroup:{horizontal:`not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]`,vertical:`not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]`},color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},variant:{solid:``,outline:``,soft:``,subtle:``,ghost:``,link:``},size:{xs:{base:`px-2 py-1 text-xs gap-1`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`},sm:{base:`px-2.5 py-1.5 text-xs gap-1.5`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`},md:{base:`px-2.5 py-1.5 text-sm gap-1.5`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`},lg:{base:`px-3 py-2 text-sm gap-2`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`},xl:{base:`px-3 py-2 text-base gap-2`,leadingIcon:`size-6`,leadingAvatarSize:`xs`,trailingIcon:`size-6`}},block:{true:{base:`w-full justify-center`,trailingIcon:`ms-auto`}},square:{true:``},leading:{true:``},trailing:{true:``},loading:{true:``},active:{true:{base:``},false:{base:``}}},compoundVariants:[{color:`primary`,variant:`solid`,class:`text-inverted bg-primary hover:bg-primary/75 active:bg-primary/75 disabled:bg-primary aria-disabled:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary`},{color:`secondary`,variant:`solid`,class:`text-inverted bg-secondary hover:bg-secondary/75 active:bg-secondary/75 disabled:bg-secondary aria-disabled:bg-secondary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary`},{color:`success`,variant:`solid`,class:`text-inverted bg-success hover:bg-success/75 active:bg-success/75 disabled:bg-success aria-disabled:bg-success focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success`},{color:`info`,variant:`solid`,class:`text-inverted bg-info hover:bg-info/75 active:bg-info/75 disabled:bg-info aria-disabled:bg-info focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info`},{color:`warning`,variant:`solid`,class:`text-inverted bg-warning hover:bg-warning/75 active:bg-warning/75 disabled:bg-warning aria-disabled:bg-warning focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning`},{color:`error`,variant:`solid`,class:`text-inverted bg-error hover:bg-error/75 active:bg-error/75 disabled:bg-error aria-disabled:bg-error focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-error`},{color:`primary`,variant:`outline`,class:`ring ring-inset ring-primary/50 text-primary hover:bg-primary/10 active:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-primary`},{color:`secondary`,variant:`outline`,class:`ring ring-inset ring-secondary/50 text-secondary hover:bg-secondary/10 active:bg-secondary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-secondary`},{color:`success`,variant:`outline`,class:`ring ring-inset ring-success/50 text-success hover:bg-success/10 active:bg-success/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-success`},{color:`info`,variant:`outline`,class:`ring ring-inset ring-info/50 text-info hover:bg-info/10 active:bg-info/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-info`},{color:`warning`,variant:`outline`,class:`ring ring-inset ring-warning/50 text-warning hover:bg-warning/10 active:bg-warning/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-warning`},{color:`error`,variant:`outline`,class:`ring ring-inset ring-error/50 text-error hover:bg-error/10 active:bg-error/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-error`},{color:`primary`,variant:`soft`,class:`text-primary bg-primary/10 hover:bg-primary/15 active:bg-primary/15 focus:outline-none focus-visible:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10`},{color:`secondary`,variant:`soft`,class:`text-secondary bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 focus:outline-none focus-visible:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10`},{color:`success`,variant:`soft`,class:`text-success bg-success/10 hover:bg-success/15 active:bg-success/15 focus:outline-none focus-visible:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10`},{color:`info`,variant:`soft`,class:`text-info bg-info/10 hover:bg-info/15 active:bg-info/15 focus:outline-none focus-visible:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10`},{color:`warning`,variant:`soft`,class:`text-warning bg-warning/10 hover:bg-warning/15 active:bg-warning/15 focus:outline-none focus-visible:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10`},{color:`error`,variant:`soft`,class:`text-error bg-error/10 hover:bg-error/15 active:bg-error/15 focus:outline-none focus-visible:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/10`},{color:`primary`,variant:`subtle`,class:`text-primary ring ring-inset ring-primary/25 bg-primary/10 hover:bg-primary/15 active:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary`},{color:`secondary`,variant:`subtle`,class:`text-secondary ring ring-inset ring-secondary/25 bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-secondary`},{color:`success`,variant:`subtle`,class:`text-success ring ring-inset ring-success/25 bg-success/10 hover:bg-success/15 active:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-success`},{color:`info`,variant:`subtle`,class:`text-info ring ring-inset ring-info/25 bg-info/10 hover:bg-info/15 active:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-info`},{color:`warning`,variant:`subtle`,class:`text-warning ring ring-inset ring-warning/25 bg-warning/10 hover:bg-warning/15 active:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-warning`},{color:`error`,variant:`subtle`,class:`text-error ring ring-inset ring-error/25 bg-error/10 hover:bg-error/15 active:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-error`},{color:`primary`,variant:`ghost`,class:`text-primary hover:bg-primary/10 active:bg-primary/10 focus:outline-none focus-visible:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`secondary`,variant:`ghost`,class:`text-secondary hover:bg-secondary/10 active:bg-secondary/10 focus:outline-none focus-visible:bg-secondary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`success`,variant:`ghost`,class:`text-success hover:bg-success/10 active:bg-success/10 focus:outline-none focus-visible:bg-success/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`info`,variant:`ghost`,class:`text-info hover:bg-info/10 active:bg-info/10 focus:outline-none focus-visible:bg-info/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`warning`,variant:`ghost`,class:`text-warning hover:bg-warning/10 active:bg-warning/10 focus:outline-none focus-visible:bg-warning/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`error`,variant:`ghost`,class:`text-error hover:bg-error/10 active:bg-error/10 focus:outline-none focus-visible:bg-error/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent`},{color:`primary`,variant:`link`,class:`text-primary hover:text-primary/75 active:text-primary/75 disabled:text-primary aria-disabled:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary`},{color:`secondary`,variant:`link`,class:`text-secondary hover:text-secondary/75 active:text-secondary/75 disabled:text-secondary aria-disabled:text-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary`},{color:`success`,variant:`link`,class:`text-success hover:text-success/75 active:text-success/75 disabled:text-success aria-disabled:text-success focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success`},{color:`info`,variant:`link`,class:`text-info hover:text-info/75 active:text-info/75 disabled:text-info aria-disabled:text-info focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info`},{color:`warning`,variant:`link`,class:`text-warning hover:text-warning/75 active:text-warning/75 disabled:text-warning aria-disabled:text-warning focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning`},{color:`error`,variant:`link`,class:`text-error hover:text-error/75 active:text-error/75 disabled:text-error aria-disabled:text-error focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error`},{color:`neutral`,variant:`solid`,class:`text-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted aria-disabled:bg-inverted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-inverted`},{color:`neutral`,variant:`outline`,class:`ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted`},{color:`neutral`,variant:`soft`,class:`text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 focus:outline-none focus-visible:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated`},{color:`neutral`,variant:`subtle`,class:`ring ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted`},{color:`neutral`,variant:`ghost`,class:`text-default hover:bg-elevated active:bg-elevated focus:outline-none focus-visible:bg-elevated hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent`},{color:`neutral`,variant:`link`,class:`text-muted hover:text-default active:text-default disabled:text-muted aria-disabled:text-muted focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted`},{size:`xs`,square:!0,class:`p-1`},{size:`sm`,square:!0,class:`p-1.5`},{size:`md`,square:!0,class:`p-1.5`},{size:`lg`,square:!0,class:`p-2`},{size:`xl`,square:!0,class:`p-2`},{loading:!0,leading:!0,class:{leadingIcon:`animate-spin`}},{loading:!0,leading:!1,trailing:!0,class:{trailingIcon:`animate-spin`}}],defaultVariants:{color:`primary`,variant:`solid`,size:`md`}},YE={__name:`Button`,props:{label:{type:String,required:!1},color:{type:null,required:!1},activeColor:{type:null,required:!1},variant:{type:null,required:!1},activeVariant:{type:null,required:!1},size:{type:null,required:!1},square:{type:Boolean,required:!1},block:{type:Boolean,required:!1},loadingAuto:{type:Boolean,required:!1},onClick:{type:[Function,Array],required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},trailingIcon:{type:null,required:!1},loading:{type:Boolean,required:!1},loadingIcon:{type:null,required:!1},as:{type:null,required:!1},type:{type:null,required:!1},disabled:{type:Boolean,required:!1},active:{type:Boolean,required:!1},exact:{type:Boolean,required:!1},exactQuery:{type:[Boolean,String],required:!1},exactHash:{type:Boolean,required:!1},inactiveClass:{type:String,required:!1},to:{type:null,required:!1},href:{type:null,required:!1},external:{type:Boolean,required:!1},target:{type:[String,Object,null],required:!1},rel:{type:[String,Object,null],required:!1},noRel:{type:Boolean,required:!1},prefetchedClass:{type:String,required:!1},prefetch:{type:Boolean,required:!1},prefetchOn:{type:[String,Object],required:!1},noPrefetch:{type:Boolean,required:!1},trailingSlash:{type:String,required:!1},activeClass:{type:String,required:!1},exactActiveClass:{type:String,required:!1},ariaCurrentValue:{type:String,required:!1},viewTransition:{type:Boolean,required:!1},replace:{type:Boolean,required:!1}},setup(e){let t=e,n=lo(),r=wf(),{orientation:i,size:a}=OE(t),o=yg(zE(t)),s=j(!1),c=oi(FE,void 0);async function l(e){s.value=!0;let n=Array.isArray(t.onClick)?t.onClick:[t.onClick];try{await Promise.all(n.map(t=>t?.(e)))}finally{s.value=!1}}let u=W(()=>t.loading||t.loadingAuto&&(s.value||c?.value&&t.type===`submit`)),{isLeading:d,isTrailing:f,leadingIconName:p,trailingIconName:m}=EE(W(()=>({...t,loading:u.value}))),h=W(()=>Mw({extend:Mw(JE),...Af({variants:{active:{true:{base:qf(r.ui?.button?.variants?.active?.true?.base,t.activeClass)},false:{base:qf(r.ui?.button?.variants?.active?.false?.base,t.inactiveClass)}}}},r.ui?.button||{})})({color:t.color,variant:t.variant,size:a.value,loading:u.value,block:t.block,square:t.square||!n.default&&!t.label,leading:d.value,trailing:f.value,fieldGroup:i.value}));return(n,r)=>(L(),z(qE,U({type:e.type,disabled:e.disabled||u.value},M(Bf)(M(o),[`type`,`disabled`,`onClick`]),{custom:``}),{default:N(({active:r,...i})=>[V(GE,U(i,{"data-slot":`base`,class:h.value.base({class:[t.ui?.base,t.class],active:r,...r&&e.activeVariant?{variant:e.activeVariant}:{},...r&&e.activeColor?{color:e.activeColor}:{}}),onClick:l}),{default:N(()=>[F(n.$slots,`leading`,{ui:h.value},()=>[M(d)&&M(p)?(L(),z(yE,{key:0,name:M(p),"data-slot":`leadingIcon`,class:A(h.value.leadingIcon({class:t.ui?.leadingIcon,active:r}))},null,8,[`name`,`class`])):e.avatar?(L(),z(TE,U({key:1,size:t.ui?.leadingAvatarSize||h.value.leadingAvatarSize()},e.avatar,{"data-slot":`leadingAvatar`,class:h.value.leadingAvatar({class:t.ui?.leadingAvatar,active:r})}),null,16,[`size`,`class`])):H(``,!0)]),F(n.$slots,`default`,{ui:h.value},()=>[e.label!==void 0&&e.label!==null?(L(),R(`span`,{key:0,"data-slot":`label`,class:A(h.value.label({class:t.ui?.label,active:r}))},ft(e.label),3)):H(``,!0)]),F(n.$slots,`trailing`,{ui:h.value},()=>[M(f)&&M(m)?(L(),z(yE,{key:0,name:M(m),"data-slot":`trailingIcon`,class:A(h.value.trailingIcon({class:t.ui?.trailingIcon,active:r}))},null,8,[`name`,`class`])):H(``,!0)])]),_:2},1040,[`class`])]),_:3},16,[`type`,`disabled`]))}},XE={slots:{root:`gap-2`,base:`relative overflow-hidden rounded-full bg-accented`,indicator:`rounded-full size-full transition-transform duration-200 ease-out`,status:`flex text-dimmed transition-[width] duration-200`,steps:`grid items-end`,step:`truncate text-end row-start-1 col-start-1 transition-opacity`},variants:{animation:{carousel:``,"carousel-inverse":``,swing:``,elastic:``},color:{primary:{indicator:`bg-primary`,steps:`text-primary`},secondary:{indicator:`bg-secondary`,steps:`text-secondary`},success:{indicator:`bg-success`,steps:`text-success`},info:{indicator:`bg-info`,steps:`text-info`},warning:{indicator:`bg-warning`,steps:`text-warning`},error:{indicator:`bg-error`,steps:`text-error`},neutral:{indicator:`bg-inverted`,steps:`text-inverted`}},size:{"2xs":{status:`text-xs`,steps:`text-xs`},xs:{status:`text-xs`,steps:`text-xs`},sm:{status:`text-sm`,steps:`text-sm`},md:{status:`text-sm`,steps:`text-sm`},lg:{status:`text-sm`,steps:`text-sm`},xl:{status:`text-base`,steps:`text-base`},"2xl":{status:`text-base`,steps:`text-base`}},step:{active:{step:`opacity-100`},first:{step:`opacity-100 text-muted`},other:{step:`opacity-0`},last:{step:``}},orientation:{horizontal:{root:`w-full flex flex-col`,base:`w-full`,status:`flex-row items-center justify-end min-w-fit`},vertical:{root:`h-full flex flex-row-reverse`,base:`h-full`,status:`flex-col justify-end min-h-fit`}},inverted:{true:{status:`self-end`}}},compoundVariants:[{inverted:!0,orientation:`horizontal`,class:{step:`text-start`,status:`flex-row-reverse`}},{inverted:!0,orientation:`vertical`,class:{steps:`items-start`,status:`flex-col-reverse`}},{orientation:`horizontal`,size:`2xs`,class:`h-px`},{orientation:`horizontal`,size:`xs`,class:`h-0.5`},{orientation:`horizontal`,size:`sm`,class:`h-1`},{orientation:`horizontal`,size:`md`,class:`h-2`},{orientation:`horizontal`,size:`lg`,class:`h-3`},{orientation:`horizontal`,size:`xl`,class:`h-4`},{orientation:`horizontal`,size:`2xl`,class:`h-5`},{orientation:`vertical`,size:`2xs`,class:`w-px`},{orientation:`vertical`,size:`xs`,class:`w-0.5`},{orientation:`vertical`,size:`sm`,class:`w-1`},{orientation:`vertical`,size:`md`,class:`w-2`},{orientation:`vertical`,size:`lg`,class:`w-3`},{orientation:`vertical`,size:`xl`,class:`w-4`},{orientation:`vertical`,size:`2xl`,class:`w-5`},{orientation:`horizontal`,animation:`carousel`,class:{indicator:`data-[state=indeterminate]:animate-[carousel_2s_ease-in-out_infinite] data-[state=indeterminate]:rtl:animate-[carousel-rtl_2s_ease-in-out_infinite]`}},{orientation:`vertical`,animation:`carousel`,class:{indicator:`data-[state=indeterminate]:animate-[carousel-vertical_2s_ease-in-out_infinite]`}},{orientation:`horizontal`,animation:`carousel-inverse`,class:{indicator:`data-[state=indeterminate]:animate-[carousel-inverse_2s_ease-in-out_infinite] data-[state=indeterminate]:rtl:animate-[carousel-inverse-rtl_2s_ease-in-out_infinite]`}},{orientation:`vertical`,animation:`carousel-inverse`,class:{indicator:`data-[state=indeterminate]:animate-[carousel-inverse-vertical_2s_ease-in-out_infinite]`}},{orientation:`horizontal`,animation:`swing`,class:{indicator:`data-[state=indeterminate]:animate-[swing_2s_ease-in-out_infinite]`}},{orientation:`vertical`,animation:`swing`,class:{indicator:`data-[state=indeterminate]:animate-[swing-vertical_2s_ease-in-out_infinite]`}},{orientation:`horizontal`,animation:`elastic`,class:{indicator:`data-[state=indeterminate]:animate-[elastic_2s_ease-in-out_infinite]`}},{orientation:`vertical`,animation:`elastic`,class:{indicator:`data-[state=indeterminate]:animate-[elastic-vertical_2s_ease-in-out_infinite]`}}],defaultVariants:{animation:`carousel`,color:`primary`,size:`md`}},ZE={__name:`Progress`,props:{as:{type:null,required:!1},max:{type:[Number,Array],required:!1},status:{type:Boolean,required:!1},inverted:{type:Boolean,required:!1,default:!1},size:{type:null,required:!1},color:{type:null,required:!1},orientation:{type:null,required:!1,default:`horizontal`},animation:{type:null,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},getValueLabel:{type:Function,required:!1},getValueText:{type:Function,required:!1},modelValue:{type:[Number,null],required:!1,default:null}},emits:[`update:modelValue`,`update:max`],setup(e,{emit:t}){let n=e,r=t,i=lo(),{dir:a}=$f(),o=wf(),s=bg(jd(n,`getValueLabel`,`getValueText`,`modelValue`),r),c=W(()=>s.value.modelValue===null),l=W(()=>Array.isArray(n.max)),u=W(()=>{if(!(c.value||!n.max))return Array.isArray(n.max)?n.max.length-1:Number(n.max)}),d=W(()=>{if(!c.value)switch(!0){case s.value.modelValue<0:return 0;case s.value.modelValue>(u.value??100):return 100;default:return Math.round(s.value.modelValue/(u.value??100)*100)}}),f=W(()=>{if(d.value!==void 0)return n.orientation===`vertical`?{transform:`translateY(${n.inverted?``:`-`}${100-d.value}%)`}:a.value===`rtl`?{transform:`translateX(${n.inverted?`-`:``}${100-d.value}%)`}:{transform:`translateX(${n.inverted?``:`-`}${100-d.value}%)`}}),p=W(()=>{let e=`${Math.max(d.value??0,0)}%`;return n.orientation===`vertical`?{height:e}:{width:e}});function m(e){return e===Number(n.modelValue)}function h(e){return e===0}function g(e){return e===u.value}function _(e){return e=Number(e),m(e)&&!h(e)?`active`:h(e)&&m(e)?`first`:g(e)&&m(e)?`last`:`other`}let v=W(()=>Mw({extend:Mw(XE),...o.ui?.progress||{}})({animation:n.animation,size:n.size,color:n.color,orientation:n.orientation,inverted:n.inverted}));return(t,r)=>(L(),z(M(K),{as:e.as,"data-orientation":e.orientation,"data-slot":`root`,class:A(v.value.root({class:[n.ui?.root,n.class]}))},{default:N(()=>[!c.value&&(e.status||i.status)?(L(),R(`div`,{key:0,"data-slot":`status`,class:A(v.value.status({class:n.ui?.status})),style:$e(p.value)},[F(t.$slots,`status`,{percent:d.value},()=>[nc(ft(d.value)+`% `,1)])],6)):H(``,!0),V(M(bx),U(M(s),{max:u.value,"data-slot":`base`,class:v.value.base({class:n.ui?.base}),style:{transform:`translateZ(0)`}}),{default:N(()=>[V(M(xx),{"data-slot":`indicator`,class:A(v.value.indicator({class:n.ui?.indicator})),style:$e(f.value)},null,8,[`class`,`style`])]),_:1},16,[`max`,`class`]),l.value?(L(),R(`div`,{key:1,"data-slot":`steps`,class:A(v.value.steps({class:n.ui?.steps}))},[(L(!0),R(I,null,qa(e.max,(e,r)=>(L(),R(`div`,{key:r,"data-slot":`step`,class:A(v.value.step({class:n.ui?.step,step:_(r)}))},[F(t.$slots,`step-${r}`,{step:e},()=>[nc(ft(e),1)])],2))),128))],2)):H(``,!0)]),_:3},8,[`as`,`data-orientation`,`class`]))}},QE={slots:{root:`relative group overflow-hidden bg-default shadow-lg rounded-lg ring ring-default p-4 flex gap-2.5 focus:outline-none`,wrapper:`w-0 flex-1 flex flex-col`,title:`text-sm font-medium text-highlighted`,description:`text-sm text-muted`,icon:`shrink-0 size-5`,avatar:`shrink-0`,avatarSize:`2xl`,actions:`flex gap-1.5 shrink-0`,progress:`absolute inset-x-0 bottom-0`,close:`p-0`},variants:{color:{primary:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary`,icon:`text-primary`},secondary:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary`,icon:`text-secondary`},success:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success`,icon:`text-success`},info:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info`,icon:`text-info`},warning:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning`,icon:`text-warning`},error:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error`,icon:`text-error`},neutral:{root:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted`,icon:`text-highlighted`}},orientation:{horizontal:{root:`items-center`,actions:`items-center`},vertical:{root:`items-start`,actions:`items-start mt-2.5`}},title:{true:{description:`mt-1`}}},defaultVariants:{color:`primary`}},$E={__name:`Toast`,props:{as:{type:null,required:!1},title:{type:[String,Object,Function],required:!1},description:{type:[String,Object,Function],required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},color:{type:null,required:!1},orientation:{type:null,required:!1,default:`vertical`},close:{type:[Boolean,Object],required:!1,default:!0},closeIcon:{type:null,required:!1},actions:{type:Array,required:!1},progress:{type:[Boolean,Object],required:!1,default:!0},class:{type:null,required:!1},ui:{type:null,required:!1},defaultOpen:{type:Boolean,required:!1},open:{type:Boolean,required:!1},type:{type:String,required:!1},duration:{type:Number,required:!1}},emits:[`escapeKeyDown`,`pause`,`resume`,`swipeStart`,`swipeMove`,`swipeCancel`,`swipeEnd`,`update:open`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=lo(),{t:o}=$f(),s=wf(),c=bg(jd(r,`as`,`defaultOpen`,`open`,`duration`,`type`),i),l=W(()=>Mw({extend:Mw(QE),...s.ui?.toast||{}})({color:r.color,orientation:r.orientation,title:!!r.title||!!a.title})),u=qi(`rootRef`),d=j(0);return Aa(()=>{u.value&&zr(()=>{d.value=u.value?.$el?.getBoundingClientRect()?.height})}),t({height:d}),(t,n)=>(L(),z(M(bS),U({ref_key:`rootRef`,ref:u},M(c),{"data-orientation":e.orientation,"data-slot":`root`,class:l.value.root({class:[r.ui?.root,r.class]}),style:{"--height":d.value}}),{default:N(({remaining:i,duration:c,open:u})=>[F(t.$slots,`leading`,{ui:l.value},()=>[e.avatar?(L(),z(TE,U({key:0,size:r.ui?.avatarSize||l.value.avatarSize()},e.avatar,{"data-slot":`avatar`,class:l.value.avatar({class:r.ui?.avatar})}),null,16,[`size`,`class`])):e.icon?(L(),z(yE,{key:1,name:e.icon,"data-slot":`icon`,class:A(l.value.icon({class:r.ui?.icon}))},null,8,[`name`,`class`])):H(``,!0)]),B(`div`,{"data-slot":`wrapper`,class:A(l.value.wrapper({class:r.ui?.wrapper}))},[e.title||a.title?(L(),z(M(xS),{key:0,"data-slot":`title`,class:A(l.value.title({class:r.ui?.title}))},{default:N(()=>[F(t.$slots,`title`,{},()=>[typeof e.title==`function`?(L(),z(Ua(e.title()),{key:0})):typeof e.title==`object`?(L(),z(Ua(e.title),{key:1})):(L(),R(I,{key:2},[nc(ft(e.title),1)],64))])]),_:3},8,[`class`])):H(``,!0),e.description||a.description?(L(),z(M(vS),{key:1,"data-slot":`description`,class:A(l.value.description({class:r.ui?.description}))},{default:N(()=>[F(t.$slots,`description`,{},()=>[typeof e.description==`function`?(L(),z(Ua(e.description()),{key:0})):typeof e.description==`object`?(L(),z(Ua(e.description),{key:1})):(L(),R(I,{key:2},[nc(ft(e.description),1)],64))])]),_:3},8,[`class`])):H(``,!0),e.orientation===`vertical`&&(e.actions?.length||a.actions)?(L(),R(`div`,{key:2,"data-slot":`actions`,class:A(l.value.actions({class:r.ui?.actions}))},[F(t.$slots,`actions`,{},()=>[(L(!0),R(I,null,qa(e.actions,(t,r)=>(L(),z(M(_S),{key:r,"alt-text":t.label||`Action`,"as-child":``,onClick:n[0]||=Mu(()=>{},[`stop`])},{default:N(()=>[V(YE,U({size:`xs`,color:e.color},{ref_for:!0},t),null,16,[`color`])]),_:2},1032,[`alt-text`]))),128))])],2)):H(``,!0)],2),e.orientation===`horizontal`&&(e.actions?.length||a.actions)||e.close?(L(),R(`div`,{key:0,"data-slot":`actions`,class:A(l.value.actions({class:r.ui?.actions,orientation:`horizontal`}))},[e.orientation===`horizontal`&&(e.actions?.length||a.actions)?F(t.$slots,`actions`,{key:0},()=>[(L(!0),R(I,null,qa(e.actions,(t,r)=>(L(),z(M(_S),{key:r,"alt-text":t.label||`Action`,"as-child":``,onClick:n[1]||=Mu(()=>{},[`stop`])},{default:N(()=>[V(YE,U({size:`xs`,color:e.color},{ref_for:!0},t),null,16,[`color`])]),_:2},1032,[`alt-text`]))),128))]):H(``,!0),e.close||a.close?(L(),z(M(gS),{key:1,"as-child":``},{default:N(()=>[F(t.$slots,`close`,{ui:l.value},()=>[e.close?(L(),z(YE,U({key:0,icon:e.closeIcon||M(s).ui.icons.close,color:`neutral`,variant:`link`,"aria-label":M(o)(`toast.close`)},typeof e.close==`object`?e.close:{},{"data-slot":`close`,class:l.value.close({class:r.ui?.close}),onClick:n[2]||=Mu(()=>{},[`stop`])}),null,16,[`icon`,`aria-label`,`class`])):H(``,!0)])]),_:3})):H(``,!0)],2)):H(``,!0),e.progress&&u&&i>0&&c?(L(),z(ZE,U({key:1,"model-value":i/c*100,color:e.color},typeof e.progress==`object`?e.progress:{},{size:`sm`,"data-slot":`progress`,class:l.value.progress({class:r.ui?.progress})}),null,16,[`model-value`,`color`,`class`])):H(``,!0)]),_:3},16,[`data-orientation`,`class`,`style`]))}},eD={slots:{viewport:`fixed flex flex-col w-[calc(100%-2rem)] sm:w-96 z-[100] data-[expanded=true]:h-(--height) focus:outline-none`,base:`pointer-events-auto absolute inset-x-0 z-(--index) transform-(--transform) data-[expanded=false]:data-[front=false]:h-(--front-height) data-[expanded=false]:data-[front=false]:*:opacity-0 data-[front=false]:*:transition-opacity data-[front=false]:*:duration-100 data-[state=closed]:animate-[toast-closed_200ms_ease-in-out] data-[state=closed]:data-[expanded=false]:data-[front=false]:animate-[toast-collapsed-closed_200ms_ease-in-out] data-[swipe=move]:transition-none transition-[transform,translate,height] duration-200 ease-out`},variants:{position:{"top-left":{viewport:`left-4`},"top-center":{viewport:`left-1/2 transform -translate-x-1/2`},"top-right":{viewport:`right-4`},"bottom-left":{viewport:`left-4`},"bottom-center":{viewport:`left-1/2 transform -translate-x-1/2`},"bottom-right":{viewport:`right-4`}},swipeDirection:{up:`data-[swipe=end]:animate-[toast-slide-up_200ms_ease-out]`,right:`data-[swipe=end]:animate-[toast-slide-right_200ms_ease-out]`,down:`data-[swipe=end]:animate-[toast-slide-down_200ms_ease-out]`,left:`data-[swipe=end]:animate-[toast-slide-left_200ms_ease-out]`}},compoundVariants:[{position:[`top-left`,`top-center`,`top-right`],class:{viewport:`top-4`,base:`top-0 data-[state=open]:animate-[slide-in-from-top_200ms_ease-in-out]`}},{position:[`bottom-left`,`bottom-center`,`bottom-right`],class:{viewport:`bottom-4`,base:`bottom-0 data-[state=open]:animate-[slide-in-from-bottom_200ms_ease-in-out]`}},{swipeDirection:[`left`,`right`],class:`data-[swipe=move]:translate-x-(--reka-toast-swipe-move-x) data-[swipe=end]:translate-x-(--reka-toast-swipe-end-x) data-[swipe=cancel]:translate-x-0`},{swipeDirection:[`up`,`down`],class:`data-[swipe=move]:translate-y-(--reka-toast-swipe-move-y) data-[swipe=end]:translate-y-(--reka-toast-swipe-end-y) data-[swipe=cancel]:translate-y-0`}],defaultVariants:{position:`bottom-right`}},tD=Object.assign({name:`Toaster`},{props:{position:{type:null,required:!1},expand:{type:Boolean,required:!1,default:!0},progress:{type:Boolean,required:!1,default:!0},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:!0},max:{type:Number,required:!1,default:5},class:{type:null,required:!1},ui:{type:null,required:!1},label:{type:String,required:!1},duration:{type:Number,required:!1,default:5e3},disableSwipe:{type:Boolean,required:!1},swipeThreshold:{type:Number,required:!1}},setup(e){let t=e,{toasts:n,remove:r}=BS(),i=wf();ai(zS,lr(()=>t.max));let a=yg(jd(t,`duration`,`label`,`swipeThreshold`,`disableSwipe`)),o=RS(lr(()=>t.portal)),s=W(()=>{switch(t.position){case`top-center`:return`up`;case`top-right`:case`bottom-right`:return`right`;case`bottom-center`:return`down`;case`top-left`:case`bottom-left`:return`left`}return`right`}),c=W(()=>Mw({extend:Mw(eD),...i.ui?.toaster||{}})({position:t.position,swipeDirection:s.value}));function l(e,t){e||r(t)}let u=j(!1),d=W(()=>t.expand||u.value),f=j([]),p=W(()=>f.value.reduce((e,{height:t})=>e+t+16,0)),m=W(()=>f.value[f.value.length-1]?.height||0);function h(e){return f.value.slice(e+1).reduce((e,{height:t})=>e+t+16,0)}return(r,i)=>(L(),z(M(aS),U({"swipe-direction":s.value},M(a)),{default:N(()=>[F(r.$slots,`default`),(L(!0),R(I,null,qa(M(n),(r,i)=>(L(),z($E,U({key:r.id,ref_for:!0,ref_key:`refs`,ref:f,progress:e.progress},{ref_for:!0},M(Bf)(r,[`id`,`close`]),{close:r.close,"data-expanded":d.value,"data-front":!d.value&&i===M(n).length-1,style:{"--index":i-M(n).length+M(n).length,"--before":M(n).length-1-i,"--offset":h(i),"--scale":d.value?`1`:`calc(1 - var(--before) * var(--scale-factor))`,"--translate":d.value?`calc(var(--offset) * var(--translate-factor))`:`calc(var(--before) * var(--gap))`,"--transform":`translateY(var(--translate)) scale(var(--scale))`},"data-slot":`base`,class:c.value.base({class:[t.ui?.base,r.onClick?`cursor-pointer`:void 0]}),"onUpdate:open":e=>l(e,r.id),onClick:e=>r.onClick&&r.onClick(r)}),null,16,[`progress`,`close`,`data-expanded`,`data-front`,`style`,`class`,`onUpdate:open`,`onClick`]))),128)),V(M(yS),it(ec(M(o))),{default:N(()=>[V(M(CS),{"data-expanded":d.value,"data-slot":`viewport`,class:A(c.value.viewport({class:[t.ui?.viewport,t.class]})),style:$e({"--scale-factor":`0.05`,"--translate-factor":e.position?.startsWith(`top`)?`1px`:`-1px`,"--gap":e.position?.startsWith(`top`)?`16px`:`-16px`,"--front-height":`${m.value}px`,"--height":`${p.value}px`}),onMouseenter:i[0]||=e=>u.value=!0,onMouseleave:i[1]||=e=>u.value=!1},null,8,[`data-expanded`,`class`,`style`])]),_:1},16)]),_:3},16,[`swipe-direction`]))}});function nD(){let e=Rn([]),t=(t,i)=>{let{props:a,defaultOpen:s,destroyOnClose:c}=i||{},l=Ln({id:Symbol(import.meta.dev?`useOverlay`:``),isOpen:!!s,component:qn(t),isMounted:!!s,destroyOnClose:!!c,originalProps:a||{},props:{...a}});return e.push(l),{...l,open:e=>n(l.id,e),close:e=>r(l.id,e),patch:e=>o(l.id,e)}},n=(e,t)=>{let n=s(e);t?n.props={...n.originalProps,...t}:n.props={...n.originalProps},n.isOpen=!0,n.isMounted=!0;let r=new Promise(e=>n.resolvePromise=e);return Object.assign(r,{id:e,isMounted:n.isMounted,isOpen:n.isOpen,result:r})},r=(e,t)=>{let n=s(e);n.isOpen=!1,n.resolvePromise&&=(n.resolvePromise(t),void 0)},i=()=>{e.forEach(e=>r(e.id))},a=t=>{let n=s(t);if(n.isMounted=!1,n.destroyOnClose){let n=e.findIndex(e=>e.id===t);e.splice(n,1)}},o=(e,t)=>{let n=s(e);n.props={...n.props,...t}},s=t=>{let n=e.find(e=>e.id===t);if(!n)throw Error(`Overlay not found`);return n};return{overlays:e,open:n,close:r,closeAll:i,create:t,patch:o,unmount:a,isOpen:e=>s(e).isOpen}}const rD=Ed(nD);var iD={__name:`OverlayProvider`,setup(e){let{overlays:t,unmount:n,close:r}=rD(),i=W(()=>t.filter(e=>e.isMounted)),a=e=>{r(e),n(e)},o=(e,t)=>{r(e,t)};return(e,t)=>(L(!0),R(I,null,qa(i.value,e=>(L(),z(Ua(e.component),U({key:e.id},{ref_for:!0},e.props,{open:e.isOpen,"onUpdate:open":t=>e.isOpen=t,onClose:t=>o(e.id,t),"onAfter:leave":t=>a(e.id)}),null,16,[`open`,`onUpdate:open`,`onClose`,`onAfter:leave`]))),128))}},aD=Object.assign({name:`App`},{props:{tooltip:{type:Object,required:!1},toaster:{type:[Object,null],required:!1},locale:{type:Object,required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:`body`},dir:{type:String,required:!1},scrollBody:{type:[Boolean,Object],required:!1},nonce:{type:String,required:!1}},setup(e){let t=e,n=yg(jd(t,`scrollBody`)),r=lr(()=>t.tooltip),i=lr(()=>t.toaster),a=lr(()=>t.locale);return ai(Qf,a),ai(LS,lr(()=>t.portal)),(o,s)=>(L(),z(M(Dh),U({"use-id":()=>Gi(),dir:t.dir||a.value?.dir,locale:a.value?.code},M(n)),{default:N(()=>[V(M(DS),it(ec(r.value)),{default:N(()=>[e.toaster===null?F(o.$slots,`default`,{key:1}):(L(),z(tD,it(U({key:0},i.value)),{default:N(()=>[F(o.$slots,`default`)]),_:3},16)),V(iD)]),_:3},16)]),_:3},16,[`use-id`,`dir`,`locale`]))}}),oD={base:`w-full max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8`},sD={__name:`Container`,props:{as:{type:null,required:!1},class:{type:null,required:!1}},setup(e){let t=e,n=wf(),r=W(()=>Mw({extend:Mw(oD),...n.ui?.container||{}}));return(n,i)=>(L(),z(M(K),{as:e.as,class:A(r.value({class:t.class}))},{default:N(()=>[F(n.$slots,`default`)]),_:3},8,[`as`,`class`]))}},cD={slots:{root:``,top:`py-8 lg:py-12`,bottom:`py-8 lg:py-12`,container:`py-8 lg:py-4 lg:flex lg:items-center lg:justify-between lg:gap-x-3`,left:`flex items-center justify-center lg:justify-start lg:flex-1 gap-x-1.5 mt-3 lg:mt-0 lg:order-1`,center:`mt-3 lg:mt-0 lg:order-2 flex items-center justify-center`,right:`lg:flex-1 flex items-center justify-center lg:justify-end gap-x-1.5 lg:order-3`}},lD={__name:`Footer`,props:{as:{type:null,required:!1,default:`footer`},class:{type:null,required:!1},ui:{type:null,required:!1}},setup(e){let t=e,n=lo(),r=wf(),i=W(()=>Mw({extend:Mw(cD),...r.ui?.footer||{}})());return(r,a)=>(L(),z(M(K),{as:e.as,"data-slot":`root`,class:A(i.value.root({class:[t.ui?.root,t.class]}))},{default:N(()=>[n.top?(L(),R(`div`,{key:0,"data-slot":`top`,class:A(i.value.top({class:t.ui?.top}))},[F(r.$slots,`top`)],2)):H(``,!0),V(sD,{"data-slot":`container`,class:A(i.value.container({class:t.ui?.container}))},{default:N(()=>[B(`div`,{"data-slot":`right`,class:A(i.value.right({class:t.ui?.right}))},[F(r.$slots,`right`)],2),B(`div`,{"data-slot":`center`,class:A(i.value.center({class:t.ui?.center}))},[F(r.$slots,`default`)],2),B(`div`,{"data-slot":`left`,class:A(i.value.left({class:t.ui?.left}))},[F(r.$slots,`left`)],2)]),_:3},8,[`class`]),n.bottom?(L(),R(`div`,{key:1,"data-slot":`bottom`,class:A(i.value.bottom({class:t.ui?.bottom}))},[F(r.$slots,`bottom`)],2)):H(``,!0)]),_:3},8,[`as`,`class`]))}},uD=`data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20version='1.1'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20xmlns:svgjs='http://svgjs.dev/svgjs'%20viewBox='0%200%20700%20700'%20width='700'%20height='700'%20opacity='0.2'%3e%3cdefs%3e%3cfilter%20id='nnnoise-filter'%20x='-20%25'%20y='-20%25'%20width='140%25'%20height='140%25'%20filterUnits='objectBoundingBox'%20primitiveUnits='userSpaceOnUse'%20color-interpolation-filters='linearRGB'%3e%3cfeTurbulence%20type='turbulence'%20baseFrequency='0.156'%20numOctaves='4'%20seed='15'%20stitchTiles='stitch'%20x='0%25'%20y='0%25'%20width='100%25'%20height='100%25'%20result='turbulence'/%3e%3cfeSpecularLighting%20surfaceScale='5'%20specularConstant='0.4'%20specularExponent='20'%20lighting-color='%237957A8'%20x='0%25'%20y='0%25'%20width='100%25'%20height='100%25'%20in='turbulence'%20result='specularLighting'%3e%3cfeDistantLight%20azimuth='3'%20elevation='47'/%3e%3c/feSpecularLighting%3e%3cfeColorMatrix%20type='saturate'%20values='0'%20x='0%25'%20y='0%25'%20width='100%25'%20height='100%25'%20in='specularLighting'%20result='colormatrix'/%3e%3c/filter%3e%3c/defs%3e%3crect%20width='700'%20height='700'%20fill='transparent'/%3e%3crect%20width='700'%20height='700'%20fill='%237957a8'%20filter='url(%23nnnoise-filter)'/%3e%3cscript%20xmlns=''/%3e%3c/svg%3e`,dD=`data:image/svg+xml,%3csvg%20viewBox='0%200%202500%202500'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%3e%3cclipPath%20id='a'%3e%3cpath%20d='m2369.8%201249.51c0%20640.93-496.52%201160.5-1109%201160.5s-1109-519.57-1109-1160.5%20496.51-1160.5%201109-1160.5c344.52%200%20652.35%20164.4%20855.76%20422.31%2098.18%20124.49%20172.03%20270.77%20213.94%20430.86%2025.62%2097.88%2039.3%20200.93%2039.3%20307.32z'/%3e%3c/clipPath%3e%3cg%20clip-path='url(%23a)'%20fill='none'%3e%3cpath%20d='m2369.4%201250.48c0%20640.93-496.52%201160.5-1109%201160.5s-1109-519.58-1109-1160.5%20496.52-1160.5%201109-1160.5c344.52%200%20652.35%20164.4%20855.76%20422.31%2098.18%20124.49%20172.03%20270.77%20213.94%20430.86%2025.62%2097.88%2039.3%20200.93%2039.3%20307.32z'%20fill='%231b1429'/%3e%3cpath%20d='m332.4%20613.98c33.39-36.64%20437.65-465.53%201013-375%20375.45%2059.08%20601.6%20304.4%20667%20381-467.24-379.26-1097.21-228.49-1297%20135-165.87%20301.77-52.55%20781.67%20335.9%201056.74-74.51-17.99-379.08-100.49-582.9-400.74-241.64-355.98-151.18-737.43-136-797z'%20fill='%2329abe2'/%3e%3cpath%20d='m1740%201921c9.67-32.67%2019.33-65.33%2029-98%2088.29-53.43%20262.17-176.33%20372-401%20209.73-429.04%202.88-854.91-25-910%2063.47%2062.19%20267.13%20277.48%20285%20605%207.49%20137.26-19.98%20251.56-47%20330-49.7%20144.28-149.08%20223.9-201%20270-152.65%20135.56-318.89%20184.58-413%20204z'%20fill='%230071bc'/%3e%3cpath%20d='m167.27%201447.13c-20.13-108.6-40.36-301.9%2024.14-526.15%2037.8-131.42%2093.45-234.34%20141-307-42.4%20432.84%20104.25%20849.96%20401%201033%20391.38%20241.41%20893.4%20136.6%201180.79-173.3%20367.62-396.42%20209.99-939.41%20202.81-962.68%2035.13%2067.97%20226%20452.93%2061%20855-93.77%20228.5-259.8%20358.51-339%20419-57.85%2044.18-305.48%20226.06-641.6%20237.98-625.57%2022.17-1006.31-539.45-1030.14-575.85z'%20fill='%2300e7ff'/%3e%3cpath%20d='m360%201779c-7.16%2050.73%20127.72%20135.46%20646%20285%20351.58-186.63%20524.61-410.26%20604-534%2068.94-107.46%2095.49-183.58%2096-265%201.41-225.73-198.01-381.45-243-415%2081.69%20183.02%20140.76%20403.88%2045%20572-222.64%20390.88-1129.97%20229.31-1148%20357z'%20fill='%2329abe2'/%3e%3cpath%20d='m167.27%201447.13c69.35%20548.18%20540.71%20963.74%201093.14%20963.85%20552.64.11%201024.28-415.58%201093.6-963.98-36.48%2051.42-410.93%20560.07-1081.6%20565.98-685.19%206.04-1071.21-518.04-1105.14-565.85z'%20fill='%230071bc'/%3e%3c/g%3e%3cpath%20d='m1014%202002.95c470.73-239.57%20721.6-598.77%20630-897.18-19.62-63.9-64.87-157.38-181-255.77%2063.26%2024.3%20113.98%2053.31%20155%2083.66%20407.5%20301.52%20147.98%20950.37%20122%201012.72-60.64%2030.29-158.51%2070.54-288%2089.24-200.41%2028.95-362.82-10.14-438-32.67z'%20fill='%230071bc'/%3e%3c/svg%3e`,fD={class:`bg-elevated border border-default rounded-md`},pD={class:`flex items-center gap-x-2.5 p-3 pb-0.5`},mD={class:`bg-accented p-1 border border-accented rounded-sm`},hD=[`textContent`],gD={class:`p-3`},_D=P({__name:`card`,props:{icon:{},title:{}},setup(e){return(t,n)=>{let r=yE;return L(),R(`div`,fD,[B(`div`,pD,[B(`div`,mD,[V(r,{name:e.icon,class:`size-3.5`},null,8,[`name`])]),B(`span`,{class:`font-mono font-semibold text-highlighted text-sm uppercase`,textContent:ft(e.title)},null,8,hD)]),B(`div`,gD,[F(t.$slots,`default`)])])}}}),vD=class extends Error{constructor(e){super(e),this.name=`ShikiError`}};function yD(e){return bD(e)}function bD(e){return Array.isArray(e)?xD(e):e instanceof RegExp?e:typeof e==`object`?SD(e):e}function xD(e){let t=[];for(let n=0,r=e.length;n{for(let n in t)e[n]=t[n]}),e}function wD(e){let t=~e.lastIndexOf(`/`)||~e.lastIndexOf(`\\`);return t===0?e:~t===e.length-1?wD(e.substring(0,e.length-1)):e.substr(~t+1)}var TD=/\$(\d+)|\${(\d+):\/(downcase|upcase)}/g,ED=class{static hasCaptures(e){return e===null?!1:(TD.lastIndex=0,TD.test(e))}static replaceCaptures(e,t,n){return e.replace(TD,(e,r,i,a)=>{let o=n[parseInt(r||i,10)];if(o){let e=t.substring(o.start,o.end);for(;e[0]===`.`;)e=e.substring(1);switch(a){case`downcase`:return e.toLowerCase();case`upcase`:return e.toUpperCase();default:return e}}else return e})}};function DD(e,t){return et?1:0}function OD(e,t){if(e===null&&t===null)return 0;if(!e)return-1;if(!t)return 1;let n=e.length,r=t.length;if(n===r){for(let r=0;rthis._root.match(e));getColorMap(){return this._colorMap.getColorMap()}getDefaults(){return this._defaults}match(e){if(e===null)return this._defaults;let t=e.scopeName,n=this._cachedMatchRoot.get(t).find(t=>PD(e.parent,t.parentScopes));return n?new ID(n.fontStyle,n.foreground,n.background):null}},ND=class e{constructor(e,t){this.parent=e,this.scopeName=t}static push(t,n){for(let r of n)t=new e(t,r);return t}static from(...t){let n=null;for(let r=0;r`){if(n===t.length-1)return!1;r=t[++n],i=!0}for(;e&&!FD(e.scopeName,r);){if(i)return!1;e=e.parent}if(!e)return!1;e=e.parent}return!0}function FD(e,t){return t===e||e.startsWith(t)&&e[t.length]===`.`}var ID=class{constructor(e,t,n){this.fontStyle=e,this.foregroundId=t,this.backgroundId=n}};function LD(e){if(!e||!e.settings||!Array.isArray(e.settings))return[];let t=e.settings,n=[],r=0;for(let e=0,i=t.length;e1&&(u=i.slice(0,i.length-1),u.reverse()),n[r++]=new RD(l,u,e,o,s,c)}}return n}var RD=class{constructor(e,t,n,r,i,a){this.scope=e,this.parentScopes=t,this.index=n,this.fontStyle=r,this.foreground=i,this.background=a}},zD=(e=>(e[e.NotSet=-1]=`NotSet`,e[e.None=0]=`None`,e[e.Italic=1]=`Italic`,e[e.Bold=2]=`Bold`,e[e.Underline=4]=`Underline`,e[e.Strikethrough=8]=`Strikethrough`,e))(zD||{});function BD(e,t){e.sort((e,t)=>{let n=DD(e.scope,t.scope);return n!==0||(n=OD(e.parentScopes,t.parentScopes),n!==0)?n:e.index-t.index});let n=0,r=`#000000`,i=`#ffffff`;for(;e.length>=1&&e[0].scope===``;){let t=e.shift();t.fontStyle!==-1&&(n=t.fontStyle),t.foreground!==null&&(r=t.foreground),t.background!==null&&(i=t.background)}let a=new VD(t),o=new ID(n,a.getId(r),a.getId(i)),s=new WD(new UD(0,null,-1,0,0),[]);for(let t=0,n=e.length;te?console.log(`how did this happen?`):this.scopeDepth=e,t!==-1&&(this.fontStyle=t),n!==0&&(this.foreground=n),r!==0&&(this.background=r)}},WD=class e{constructor(e,t=[],n={}){this._mainRule=e,this._children=n,this._rulesWithParentScopes=t}_rulesWithParentScopes;static _cmpBySpecificity(e,t){if(e.scopeDepth!==t.scopeDepth)return t.scopeDepth-e.scopeDepth;let n=0,r=0;for(;e.parentScopes[n]===`>`&&n++,t.parentScopes[r]===`>`&&r++,!(n>=e.parentScopes.length||r>=t.parentScopes.length);){let i=t.parentScopes[r].length-e.parentScopes[n].length;if(i!==0)return i;n++,r++}return t.parentScopes.length-e.parentScopes.length}match(t){if(t!==``){let e=t.indexOf(`.`),n,r;if(e===-1?(n=t,r=``):(n=t.substring(0,e),r=t.substring(e+1)),this._children.hasOwnProperty(n))return this._children[n].match(r)}let n=this._rulesWithParentScopes.concat(this._mainRule);return n.sort(e._cmpBySpecificity),n}insert(t,n,r,i,a,o){if(n===``){this._doInsertHere(t,r,i,a,o);return}let s=n.indexOf(`.`),c,l;s===-1?(c=n,l=``):(c=n.substring(0,s),l=n.substring(s+1));let u;this._children.hasOwnProperty(c)?u=this._children[c]:(u=new e(this._mainRule.clone(),UD.cloneArr(this._rulesWithParentScopes)),this._children[c]=u),u.insert(t+1,l,r,i,a,o)}_doInsertHere(e,t,n,r,i){if(t===null){this._mainRule.acceptOverwrite(e,n,r,i);return}for(let a=0,o=this._rulesWithParentScopes.length;a>>0}static getTokenType(e){return(e&768)>>>8}static containsBalancedBrackets(e){return(e&1024)!=0}static getFontStyle(e){return(e&30720)>>>11}static getForeground(e){return(e&16744448)>>>15}static getBackground(e){return(e&4278190080)>>>24}static set(t,n,r,i,a,o,s){let c=e.getLanguageId(t),l=e.getTokenType(t),u=e.containsBalancedBrackets(t)?1:0,d=e.getFontStyle(t),f=e.getForeground(t),p=e.getBackground(t);return n!==0&&(c=n),r!==8&&(l=qD(r)),i!==null&&(u=i?1:0),a!==-1&&(d=a),o!==0&&(f=o),s!==0&&(p=s),(c<<0|l<<8|u<<10|d<<11|f<<15|p<<24)>>>0}};function KD(e){return e}function qD(e){return e}function JD(e,t){let n=[],r=XD(e),i=r.next();for(;i!==null;){let e=0;if(i.length===2&&i.charAt(1)===`:`){switch(i.charAt(0)){case`R`:e=1;break;case`L`:e=-1;break;default:console.log(`Unknown priority ${i} in scope selector`)}i=r.next()}let t=o();if(n.push({matcher:t,priority:e}),i!==`,`)break;i=r.next()}return n;function a(){if(i===`-`){i=r.next();let e=a();return t=>!!e&&!e(t)}if(i===`(`){i=r.next();let e=s();return i===`)`&&(i=r.next()),e}if(YD(i)){let e=[];do e.push(i),i=r.next();while(YD(i));return n=>t(e,n)}return null}function o(){let e=[],t=a();for(;t;)e.push(t),t=a();return t=>e.every(e=>e(t))}function s(){let e=[],t=o();for(;t&&(e.push(t),i===`|`||i===`,`);){do i=r.next();while(i===`|`||i===`,`);t=o()}return t=>e.some(e=>e(t))}}function YD(e){return!!e&&!!e.match(/[\w\.:]+/)}function XD(e){let t=/([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g,n=t.exec(e);return{next:()=>{if(!n)return null;let r=n[0];return n=t.exec(e),r}}}function ZD(e){typeof e.dispose==`function`&&e.dispose()}var QD=class{constructor(e){this.scopeName=e}toKey(){return this.scopeName}},$D=class{constructor(e,t){this.scopeName=e,this.ruleName=t}toKey(){return`${this.scopeName}#${this.ruleName}`}},eO=class{_references=[];_seenReferenceKeys=new Set;get references(){return this._references}visitedRule=new Set;add(e){let t=e.toKey();this._seenReferenceKeys.has(t)||(this._seenReferenceKeys.add(t),this._references.push(e))}},tO=class{constructor(e,t){this.repo=e,this.initialScopeName=t,this.seenFullScopeRequests.add(this.initialScopeName),this.Q=[new QD(this.initialScopeName)]}seenFullScopeRequests=new Set;seenPartialScopeRequests=new Set;Q;processQueue(){let e=this.Q;this.Q=[];let t=new eO;for(let n of e)nO(n,this.initialScopeName,this.repo,t);for(let e of t.references)if(e instanceof QD){if(this.seenFullScopeRequests.has(e.scopeName))continue;this.seenFullScopeRequests.add(e.scopeName),this.Q.push(e)}else{if(this.seenFullScopeRequests.has(e.scopeName)||this.seenPartialScopeRequests.has(e.toKey()))continue;this.seenPartialScopeRequests.add(e.toKey()),this.Q.push(e)}}};function nO(e,t,n,r){let i=n.lookup(e.scopeName);if(!i){if(e.scopeName===t)throw Error(`No grammar provided for <${t}>`);return}let a=n.lookup(t);e instanceof QD?iO({baseGrammar:a,selfGrammar:i},r):rO(e.ruleName,{baseGrammar:a,selfGrammar:i,repository:i.repository},r);let o=n.injections(e.scopeName);if(o)for(let e of o)r.add(new QD(e))}function rO(e,t,n){if(t.repository&&t.repository[e]){let r=t.repository[e];aO([r],t,n)}}function iO(e,t){e.selfGrammar.patterns&&Array.isArray(e.selfGrammar.patterns)&&aO(e.selfGrammar.patterns,{...e,repository:e.selfGrammar.repository},t),e.selfGrammar.injections&&aO(Object.values(e.selfGrammar.injections),{...e,repository:e.selfGrammar.repository},t)}function aO(e,t,n){for(let r of e){if(n.visitedRule.has(r))continue;n.visitedRule.add(r);let e=r.repository?CD({},t.repository,r.repository):t.repository;Array.isArray(r.patterns)&&aO(r.patterns,{...t,repository:e},n);let i=r.include;if(!i)continue;let a=dO(i);switch(a.kind){case 0:iO({...t,selfGrammar:t.baseGrammar},n);break;case 1:iO(t,n);break;case 2:rO(a.ruleName,{...t,repository:e},n);break;case 3:case 4:let r=a.scopeName===t.selfGrammar.scopeName?t.selfGrammar:a.scopeName===t.baseGrammar.scopeName?t.baseGrammar:void 0;if(r){let i={baseGrammar:t.baseGrammar,selfGrammar:r,repository:e};a.kind===4?rO(a.ruleName,i,n):iO(i,n)}else a.kind===4?n.add(new $D(a.scopeName,a.ruleName)):n.add(new QD(a.scopeName));break}}}var oO=class{kind=0},sO=class{kind=1},cO=class{constructor(e){this.ruleName=e}kind=2},lO=class{constructor(e){this.scopeName=e}kind=3},uO=class{constructor(e,t){this.scopeName=e,this.ruleName=t}kind=4};function dO(e){if(e===`$base`)return new oO;if(e===`$self`)return new sO;let t=e.indexOf(`#`);return t===-1?new lO(e):t===0?new cO(e.substring(1)):new uO(e.substring(0,t),e.substring(t+1))}var fO=/\\(\d+)/,pO=/\\(\d+)/g,mO=-1,hO=-2;function gO(e){return e}function _O(e){return e}var vO=class{$location;id;_nameIsCapturing;_name;_contentNameIsCapturing;_contentName;constructor(e,t,n,r){this.$location=e,this.id=t,this._name=n||null,this._nameIsCapturing=ED.hasCaptures(this._name),this._contentName=r||null,this._contentNameIsCapturing=ED.hasCaptures(this._contentName)}get debugName(){let e=this.$location?`${wD(this.$location.filename)}:${this.$location.line}`:`unknown`;return`${this.constructor.name}#${this.id} @ ${e}`}getName(e,t){return!this._nameIsCapturing||this._name===null||e===null||t===null?this._name:ED.replaceCaptures(this._name,e,t)}getContentName(e,t){return!this._contentNameIsCapturing||this._contentName===null?this._contentName:ED.replaceCaptures(this._contentName,e,t)}},yO=class extends vO{retokenizeCapturedWithRuleId;constructor(e,t,n,r,i){super(e,t,n,r),this.retokenizeCapturedWithRuleId=i}dispose(){}collectPatterns(e,t){throw Error(`Not supported!`)}compile(e,t){throw Error(`Not supported!`)}compileAG(e,t,n,r){throw Error(`Not supported!`)}},bO=class extends vO{_match;captures;_cachedCompiledPatterns;constructor(e,t,n,r,i){super(e,t,n,null),this._match=new TO(r,this.id),this.captures=i,this._cachedCompiledPatterns=null}dispose(){this._cachedCompiledPatterns&&=(this._cachedCompiledPatterns.dispose(),null)}get debugMatchRegExp(){return`${this._match.source}`}collectPatterns(e,t){t.push(this._match)}compile(e,t){return this._getCachedCompiledPatterns(e).compile(e)}compileAG(e,t,n,r){return this._getCachedCompiledPatterns(e).compileAG(e,n,r)}_getCachedCompiledPatterns(e){return this._cachedCompiledPatterns||(this._cachedCompiledPatterns=new EO,this.collectPatterns(e,this._cachedCompiledPatterns)),this._cachedCompiledPatterns}},xO=class extends vO{hasMissingPatterns;patterns;_cachedCompiledPatterns;constructor(e,t,n,r,i){super(e,t,n,r),this.patterns=i.patterns,this.hasMissingPatterns=i.hasMissingPatterns,this._cachedCompiledPatterns=null}dispose(){this._cachedCompiledPatterns&&=(this._cachedCompiledPatterns.dispose(),null)}collectPatterns(e,t){for(let n of this.patterns)e.getRule(n).collectPatterns(e,t)}compile(e,t){return this._getCachedCompiledPatterns(e).compile(e)}compileAG(e,t,n,r){return this._getCachedCompiledPatterns(e).compileAG(e,n,r)}_getCachedCompiledPatterns(e){return this._cachedCompiledPatterns||(this._cachedCompiledPatterns=new EO,this.collectPatterns(e,this._cachedCompiledPatterns)),this._cachedCompiledPatterns}},SO=class extends vO{_begin;beginCaptures;_end;endHasBackReferences;endCaptures;applyEndPatternLast;hasMissingPatterns;patterns;_cachedCompiledPatterns;constructor(e,t,n,r,i,a,o,s,c,l){super(e,t,n,r),this._begin=new TO(i,this.id),this.beginCaptures=a,this._end=new TO(o||`￿`,-1),this.endHasBackReferences=this._end.hasBackReferences,this.endCaptures=s,this.applyEndPatternLast=c||!1,this.patterns=l.patterns,this.hasMissingPatterns=l.hasMissingPatterns,this._cachedCompiledPatterns=null}dispose(){this._cachedCompiledPatterns&&=(this._cachedCompiledPatterns.dispose(),null)}get debugBeginRegExp(){return`${this._begin.source}`}get debugEndRegExp(){return`${this._end.source}`}getEndWithResolvedBackReferences(e,t){return this._end.resolveBackReferences(e,t)}collectPatterns(e,t){t.push(this._begin)}compile(e,t){return this._getCachedCompiledPatterns(e,t).compile(e)}compileAG(e,t,n,r){return this._getCachedCompiledPatterns(e,t).compileAG(e,n,r)}_getCachedCompiledPatterns(e,t){if(!this._cachedCompiledPatterns){this._cachedCompiledPatterns=new EO;for(let t of this.patterns)e.getRule(t).collectPatterns(e,this._cachedCompiledPatterns);this.applyEndPatternLast?this._cachedCompiledPatterns.push(this._end.hasBackReferences?this._end.clone():this._end):this._cachedCompiledPatterns.unshift(this._end.hasBackReferences?this._end.clone():this._end)}return this._end.hasBackReferences&&(this.applyEndPatternLast?this._cachedCompiledPatterns.setSource(this._cachedCompiledPatterns.length()-1,t):this._cachedCompiledPatterns.setSource(0,t)),this._cachedCompiledPatterns}},CO=class extends vO{_begin;beginCaptures;whileCaptures;_while;whileHasBackReferences;hasMissingPatterns;patterns;_cachedCompiledPatterns;_cachedCompiledWhilePatterns;constructor(e,t,n,r,i,a,o,s,c){super(e,t,n,r),this._begin=new TO(i,this.id),this.beginCaptures=a,this.whileCaptures=s,this._while=new TO(o,hO),this.whileHasBackReferences=this._while.hasBackReferences,this.patterns=c.patterns,this.hasMissingPatterns=c.hasMissingPatterns,this._cachedCompiledPatterns=null,this._cachedCompiledWhilePatterns=null}dispose(){this._cachedCompiledPatterns&&=(this._cachedCompiledPatterns.dispose(),null),this._cachedCompiledWhilePatterns&&=(this._cachedCompiledWhilePatterns.dispose(),null)}get debugBeginRegExp(){return`${this._begin.source}`}get debugWhileRegExp(){return`${this._while.source}`}getWhileWithResolvedBackReferences(e,t){return this._while.resolveBackReferences(e,t)}collectPatterns(e,t){t.push(this._begin)}compile(e,t){return this._getCachedCompiledPatterns(e).compile(e)}compileAG(e,t,n,r){return this._getCachedCompiledPatterns(e).compileAG(e,n,r)}_getCachedCompiledPatterns(e){if(!this._cachedCompiledPatterns){this._cachedCompiledPatterns=new EO;for(let t of this.patterns)e.getRule(t).collectPatterns(e,this._cachedCompiledPatterns)}return this._cachedCompiledPatterns}compileWhile(e,t){return this._getCachedCompiledWhilePatterns(e,t).compile(e)}compileWhileAG(e,t,n,r){return this._getCachedCompiledWhilePatterns(e,t).compileAG(e,n,r)}_getCachedCompiledWhilePatterns(e,t){return this._cachedCompiledWhilePatterns||(this._cachedCompiledWhilePatterns=new EO,this._cachedCompiledWhilePatterns.push(this._while.hasBackReferences?this._while.clone():this._while)),this._while.hasBackReferences&&this._cachedCompiledWhilePatterns.setSource(0,t||`￿`),this._cachedCompiledWhilePatterns}},wO=class e{static createCaptureRule(e,t,n,r,i){return e.registerRule(e=>new yO(t,e,n,r,i))}static getCompiledRuleId(t,n,r){return t.id||n.registerRule(i=>{if(t.id=i,t.match)return new bO(t.$vscodeTextmateLocation,t.id,t.name,t.match,e._compileCaptures(t.captures,n,r));if(t.begin===void 0){t.repository&&(r=CD({},r,t.repository));let i=t.patterns;return i===void 0&&t.include&&(i=[{include:t.include}]),new xO(t.$vscodeTextmateLocation,t.id,t.name,t.contentName,e._compilePatterns(i,n,r))}return t.while?new CO(t.$vscodeTextmateLocation,t.id,t.name,t.contentName,t.begin,e._compileCaptures(t.beginCaptures||t.captures,n,r),t.while,e._compileCaptures(t.whileCaptures||t.captures,n,r),e._compilePatterns(t.patterns,n,r)):new SO(t.$vscodeTextmateLocation,t.id,t.name,t.contentName,t.begin,e._compileCaptures(t.beginCaptures||t.captures,n,r),t.end,e._compileCaptures(t.endCaptures||t.captures,n,r),t.applyEndPatternLast,e._compilePatterns(t.patterns,n,r))}),t.id}static _compileCaptures(t,n,r){let i=[];if(t){let a=0;for(let e in t){if(e===`$vscodeTextmateLocation`)continue;let t=parseInt(e,10);t>a&&(a=t)}for(let e=0;e<=a;e++)i[e]=null;for(let a in t){if(a===`$vscodeTextmateLocation`)continue;let o=parseInt(a,10),s=0;t[a].patterns&&(s=e.getCompiledRuleId(t[a],n,r)),i[o]=e.createCaptureRule(n,t[a].$vscodeTextmateLocation,t[a].name,t[a].contentName,s)}}return i}static _compilePatterns(t,n,r){let i=[];if(t)for(let a=0,o=t.length;ae.substring(t.start,t.end));return pO.lastIndex=0,this.source.replace(pO,(e,t)=>AD(n[parseInt(t,10)]||``))}_buildAnchorCache(){if(typeof this.source!=`string`)throw Error(`This method should only be called if the source is a string`);let e=[],t=[],n=[],r=[],i,a,o,s;for(i=0,a=this.source.length;ie.source),this._items.map(e=>e.ruleId)),this._cached}compileAG(e,t,n){return this._hasAnchors?t?n?(this._anchorCache.A1_G1||(this._anchorCache.A1_G1=this._resolveAnchors(e,t,n)),this._anchorCache.A1_G1):(this._anchorCache.A1_G0||(this._anchorCache.A1_G0=this._resolveAnchors(e,t,n)),this._anchorCache.A1_G0):n?(this._anchorCache.A0_G1||(this._anchorCache.A0_G1=this._resolveAnchors(e,t,n)),this._anchorCache.A0_G1):(this._anchorCache.A0_G0||(this._anchorCache.A0_G0=this._resolveAnchors(e,t,n)),this._anchorCache.A0_G0):this.compile(e)}_resolveAnchors(e,t,n){return new DO(e,this._items.map(e=>e.resolveAnchors(t,n)),this._items.map(e=>e.ruleId))}},DO=class{constructor(e,t,n){this.regExps=t,this.rules=n,this.scanner=e.createOnigScanner(t)}scanner;dispose(){typeof this.scanner.dispose==`function`&&this.scanner.dispose()}toString(){let e=[];for(let t=0,n=this.rules.length;tnew OO(this._scopeToLanguage(e),this._toStandardTokenType(e)));_scopeToLanguage(e){return this._embeddedLanguagesMatcher.match(e)||0}_toStandardTokenType(t){let n=t.match(e.STANDARD_TOKEN_TYPE_REGEXP);if(!n)return 8;switch(n[1]){case`comment`:return 1;case`string`:return 2;case`regex`:return 3;case`meta.embedded`:return 0}throw Error(`Unexpected match for standard token type!`)}static STANDARD_TOKEN_TYPE_REGEXP=/\b(comment|string|regex|meta\.embedded)\b/},AO=class{values;scopesRegExp;constructor(e){if(e.length===0)this.values=null,this.scopesRegExp=null;else{this.values=new Map(e);let t=e.map(([e,t])=>AD(e));t.sort(),t.reverse(),this.scopesRegExp=RegExp(`^((${t.join(`)|(`)}))($|\\.)`,``)}}match(e){if(!this.scopesRegExp)return;let t=e.match(this.scopesRegExp);if(t)return this.values.get(t[1])}};typeof process<`u`&&{}.VSCODE_TEXTMATE_DEBUG;var jO=!1,MO=class{constructor(e,t){this.stack=e,this.stoppedEarly=t}};function NO(e,t,n,r,i,a,o,s){let c=t.content.length,l=!1,u=-1;if(o){let o=PO(e,t,n,r,i,a);i=o.stack,r=o.linePos,n=o.isFirstLine,u=o.anchorPosition}let d=Date.now();for(;!l;){if(s!==0&&Date.now()-d>s)return new MO(i,!0);f()}return new MO(i,!1);function f(){let o=FO(e,t,n,r,i,u);if(!o){a.produce(i,c),l=!0;return}let s=o.captureIndices,d=o.matchedRuleId,f=s&&s.length>0?s[0].end>r:!1;if(d===mO){let o=i.getRule(e);a.produce(i,s[0].start),i=i.withContentNameScopesList(i.nameScopesList),VO(e,t,n,i,a,o.endCaptures,s),a.produce(i,s[0].end);let d=i;if(i=i.parent,u=d.getAnchorPos(),!f&&d.getEnterPos()===r){i=d,a.produce(i,c),l=!0;return}}else{let o=e.getRule(d);a.produce(i,s[0].start);let p=i,m=o.getName(t.content,s),h=i.contentNameScopesList.pushAttributed(m,e);if(i=i.push(d,r,u,s[0].end===c,null,h,h),o instanceof SO){let r=o;VO(e,t,n,i,a,r.beginCaptures,s),a.produce(i,s[0].end),u=s[0].end;let d=r.getContentName(t.content,s),m=h.pushAttributed(d,e);if(i=i.withContentNameScopesList(m),r.endHasBackReferences&&(i=i.withEndRule(r.getEndWithResolvedBackReferences(t.content,s))),!f&&p.hasSameRuleAs(i)){i=i.pop(),a.produce(i,c),l=!0;return}}else if(o instanceof CO){let r=o;VO(e,t,n,i,a,r.beginCaptures,s),a.produce(i,s[0].end),u=s[0].end;let d=r.getContentName(t.content,s),m=h.pushAttributed(d,e);if(i=i.withContentNameScopesList(m),r.whileHasBackReferences&&(i=i.withEndRule(r.getWhileWithResolvedBackReferences(t.content,s))),!f&&p.hasSameRuleAs(i)){i=i.pop(),a.produce(i,c),l=!0;return}}else if(VO(e,t,n,i,a,o.captures,s),a.produce(i,s[0].end),i=i.pop(),!f){i=i.safePop(),a.produce(i,c),l=!0;return}}s[0].end>r&&(r=s[0].end,n=!1)}}function PO(e,t,n,r,i,a){let o=i.beginRuleCapturedEOL?0:-1,s=[];for(let t=i;t;t=t.pop()){let n=t.getRule(e);n instanceof CO&&s.push({rule:n,stack:t})}for(let c=s.pop();c;c=s.pop()){let{ruleScanner:s,findOptions:l}=zO(c.rule,e,c.stack.endRule,n,r===o),u=s.findNextMatchSync(t,r,l);if(u){if(u.ruleId!==hO){i=c.stack.pop();break}u.captureIndices&&u.captureIndices.length&&(a.produce(c.stack,u.captureIndices[0].start),VO(e,t,n,c.stack,a,c.rule.whileCaptures,u.captureIndices),a.produce(c.stack,u.captureIndices[0].end),o=u.captureIndices[0].end,u.captureIndices[0].end>r&&(r=u.captureIndices[0].end,n=!1))}else{i=c.stack.pop();break}}return{stack:i,linePos:r,anchorPosition:o,isFirstLine:n}}function FO(e,t,n,r,i,a){let o=IO(e,t,n,r,i,a),s=e.getInjections();if(s.length===0)return o;let c=LO(s,e,t,n,r,i,a);if(!c)return o;if(!o)return c;let l=o.captureIndices[0].start,u=c.captureIndices[0].start;return u=s)&&(s=g,c=h.captureIndices,l=h.ruleId,u=f.priority,s===i))break}return c?{priorityMatch:u===-1,captureIndices:c,matchedRuleId:l}:null}function RO(e,t,n,r,i){return jO?{ruleScanner:e.compile(t,n),findOptions:BO(r,i)}:{ruleScanner:e.compileAG(t,n,r,i),findOptions:0}}function zO(e,t,n,r,i){return jO?{ruleScanner:e.compileWhile(t,n),findOptions:BO(r,i)}:{ruleScanner:e.compileWhileAG(t,n,r,i),findOptions:0}}function BO(e,t){let n=0;return e||(n|=1),t||(n|=4),n}function VO(e,t,n,r,i,a,o){if(a.length===0)return;let s=t.content,c=Math.min(a.length,o.length),l=[],u=o[0].end;for(let t=0;tu)break;for(;l.length>0&&l[l.length-1].endPos<=d.start;)i.produceFromScopes(l[l.length-1].scopes,l[l.length-1].endPos),l.pop();if(l.length>0?i.produceFromScopes(l[l.length-1].scopes,d.start):i.produce(r,d.start),c.retokenizeCapturedWithRuleId){let t=c.getName(s,o),a=r.contentNameScopesList.pushAttributed(t,e),l=c.getContentName(s,o),u=a.pushAttributed(l,e),f=r.push(c.retokenizeCapturedWithRuleId,d.start,-1,!1,null,a,u),p=e.createOnigString(s.substring(0,d.end));NO(e,p,n&&d.start===0,d.start,f,i,!1,0),ZD(p);continue}let f=c.getName(s,o);if(f!==null){let t=(l.length>0?l[l.length-1].scopes:r.contentNameScopesList).pushAttributed(f,e);l.push(new HO(t,d.end))}}for(;l.length>0;)i.produceFromScopes(l[l.length-1].scopes,l[l.length-1].endPos),l.pop()}var HO=class{scopes;endPos;constructor(e,t){this.scopes=e,this.endPos=t}};function UO(e,t,n,r,i,a,o,s){return new qO(e,t,n,r,i,a,o,s)}function WO(e,t,n,r,i){let a=JD(t,GO),o=wO.getCompiledRuleId(n,r,i.repository);for(let n of a)e.push({debugSelector:t,matcher:n.matcher,ruleId:o,grammar:i,priority:n.priority})}function GO(e,t){if(t.length{for(let r=n;rn&&e.substr(0,n)===t&&e[n]===`.`}var qO=class{constructor(e,t,n,r,i,a,o,s){if(this._rootScopeName=e,this.balancedBracketSelectors=a,this._onigLib=s,this._basicScopeAttributesProvider=new kO(n,r),this._rootId=-1,this._lastRuleId=0,this._ruleId2desc=[null],this._includedGrammars={},this._grammarRepository=o,this._grammar=JO(t,null),this._injections=null,this._tokenTypeMatchers=[],i)for(let e of Object.keys(i)){let t=JD(e,GO);for(let n of t)this._tokenTypeMatchers.push({matcher:n.matcher,type:i[e]})}}_rootId;_lastRuleId;_ruleId2desc;_includedGrammars;_grammarRepository;_grammar;_injections;_basicScopeAttributesProvider;_tokenTypeMatchers;get themeProvider(){return this._grammarRepository}dispose(){for(let e of this._ruleId2desc)e&&e.dispose()}createOnigScanner(e){return this._onigLib.createOnigScanner(e)}createOnigString(e){return this._onigLib.createOnigString(e)}getMetadataForScope(e){return this._basicScopeAttributesProvider.getBasicScopeAttributes(e)}_collectInjections(){let e={lookup:e=>e===this._rootScopeName?this._grammar:this.getExternalGrammar(e),injections:e=>this._grammarRepository.injections(e)},t=[],n=this._rootScopeName,r=e.lookup(n);if(r){let e=r.injections;if(e)for(let n in e)WO(t,n,e[n],this,r);let i=this._grammarRepository.injections(n);i&&i.forEach(e=>{let n=this.getExternalGrammar(e);if(n){let e=n.injectionSelector;e&&WO(t,e,n,this,n)}})}return t.sort((e,t)=>e.priority-t.priority),t}getInjections(){return this._injections===null&&(this._injections=this._collectInjections()),this._injections}registerRule(e){let t=++this._lastRuleId,n=e(gO(t));return this._ruleId2desc[t]=n,n}getRule(e){return this._ruleId2desc[_O(e)]}getExternalGrammar(e,t){if(this._includedGrammars[e])return this._includedGrammars[e];if(this._grammarRepository){let n=this._grammarRepository.lookup(e);if(n)return this._includedGrammars[e]=JO(n,t&&t.$base),this._includedGrammars[e]}}tokenizeLine(e,t,n=0){let r=this._tokenize(e,t,!1,n);return{tokens:r.lineTokens.getResult(r.ruleStack,r.lineLength),ruleStack:r.ruleStack,stoppedEarly:r.stoppedEarly}}tokenizeLine2(e,t,n=0){let r=this._tokenize(e,t,!0,n);return{tokens:r.lineTokens.getBinaryResult(r.ruleStack,r.lineLength),ruleStack:r.ruleStack,stoppedEarly:r.stoppedEarly}}_tokenize(e,t,n,r){this._rootId===-1&&(this._rootId=wO.getCompiledRuleId(this._grammar.repository.$self,this,this._grammar.repository),this.getInjections());let i;if(!t||t===XO.NULL){i=!0;let e=this._basicScopeAttributesProvider.getDefaultAttributes(),n=this.themeProvider.getDefaults(),r=GD.set(0,e.languageId,e.tokenType,null,n.fontStyle,n.foregroundId,n.backgroundId),a=this.getRule(this._rootId).getName(null,null),o;o=a?YO.createRootAndLookUpScopeName(a,r,this):YO.createRoot(`unknown`,r),t=new XO(null,this._rootId,-1,-1,!1,null,o,o)}else i=!1,t.reset();e+=` +`;let a=this.createOnigString(e),o=a.content.length,s=new QO(n,e,this._tokenTypeMatchers,this.balancedBracketSelectors),c=NO(this,a,i,0,t,s,!0,r);return ZD(a),{lineLength:o,lineTokens:s,ruleStack:c.stack,stoppedEarly:c.stoppedEarly}}};function JO(e,t){return e=yD(e),e.repository=e.repository||{},e.repository.$self={$vscodeTextmateLocation:e.$vscodeTextmateLocation,patterns:e.patterns,name:e.scopeName},e.repository.$base=t||e.repository.$self,e}var YO=class e{constructor(e,t,n){this.parent=e,this.scopePath=t,this.tokenAttributes=n}static fromExtension(t,n){let r=t,i=t?.scopePath??null;for(let t of n)i=ND.push(i,t.scopeNames),r=new e(r,i,t.encodedTokenAttributes);return r}static createRoot(t,n){return new e(null,new ND(null,t),n)}static createRootAndLookUpScopeName(t,n,r){let i=r.getMetadataForScope(t),a=new ND(null,t),o=r.themeProvider.themeMatch(a);return new e(null,a,e.mergeAttributes(n,i,o))}get scopeName(){return this.scopePath.scopeName}toString(){return this.getScopeNames().join(` `)}equals(t){return e.equals(this,t)}static equals(e,t){do{if(e===t||!e&&!t)return!0;if(!e||!t||e.scopeName!==t.scopeName||e.tokenAttributes!==t.tokenAttributes)return!1;e=e.parent,t=t.parent}while(!0)}static mergeAttributes(e,t,n){let r=-1,i=0,a=0;return n!==null&&(r=n.fontStyle,i=n.foregroundId,a=n.backgroundId),GD.set(e,t.languageId,t.tokenType,null,r,i,a)}pushAttributed(t,n){if(t===null)return this;if(t.indexOf(` `)===-1)return e._pushAttributed(this,t,n);let r=t.split(/ /g),i=this;for(let t of r)i=e._pushAttributed(i,t,n);return i}static _pushAttributed(t,n,r){let i=r.getMetadataForScope(n),a=t.scopePath.push(n),o=r.themeProvider.themeMatch(a);return new e(t,a,e.mergeAttributes(t.tokenAttributes,i,o))}getScopeNames(){return this.scopePath.getSegments()}getExtensionIfDefined(e){let t=[],n=this;for(;n&&n!==e;)t.push({encodedTokenAttributes:n.tokenAttributes,scopeNames:n.scopePath.getExtensionIfDefined(n.parent?.scopePath??null)}),n=n.parent;return n===e?t.reverse():void 0}},XO=class e{constructor(e,t,n,r,i,a,o,s){this.parent=e,this.ruleId=t,this.beginRuleCapturedEOL=i,this.endRule=a,this.nameScopesList=o,this.contentNameScopesList=s,this.depth=this.parent?this.parent.depth+1:1,this._enterPos=n,this._anchorPos=r}_stackElementBrand=void 0;static NULL=new e(null,0,0,0,!1,null,null,null);_enterPos;_anchorPos;depth;equals(t){return t===null?!1:e._equals(this,t)}static _equals(e,t){return e===t?!0:this._structuralEquals(e,t)?YO.equals(e.contentNameScopesList,t.contentNameScopesList):!1}static _structuralEquals(e,t){do{if(e===t||!e&&!t)return!0;if(!e||!t||e.depth!==t.depth||e.ruleId!==t.ruleId||e.endRule!==t.endRule)return!1;e=e.parent,t=t.parent}while(!0)}clone(){return this}static _reset(e){for(;e;)e._enterPos=-1,e._anchorPos=-1,e=e.parent}reset(){e._reset(this)}pop(){return this.parent}safePop(){return this.parent?this.parent:this}push(t,n,r,i,a,o,s){return new e(this,t,n,r,i,a,o,s)}getEnterPos(){return this._enterPos}getAnchorPos(){return this._anchorPos}getRule(e){return e.getRule(this.ruleId)}toString(){let e=[];return this._writeString(e,0),`[`+e.join(`,`)+`]`}_writeString(e,t){return this.parent&&(t=this.parent._writeString(e,t)),e[t++]=`(${this.ruleId}, ${this.nameScopesList?.toString()}, ${this.contentNameScopesList?.toString()})`,t}withContentNameScopesList(e){return this.contentNameScopesList===e?this:this.parent.push(this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,this.endRule,this.nameScopesList,e)}withEndRule(t){return this.endRule===t?this:new e(this.parent,this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,t,this.nameScopesList,this.contentNameScopesList)}hasSameRuleAs(e){let t=this;for(;t&&t._enterPos===e._enterPos;){if(t.ruleId===e.ruleId)return!0;t=t.parent}return!1}toStateStackFrame(){return{ruleId:_O(this.ruleId),beginRuleCapturedEOL:this.beginRuleCapturedEOL,endRule:this.endRule,nameScopesList:this.nameScopesList?.getExtensionIfDefined(this.parent?.nameScopesList??null)??[],contentNameScopesList:this.contentNameScopesList?.getExtensionIfDefined(this.nameScopesList)??[]}}static pushFrame(t,n){let r=YO.fromExtension(t?.nameScopesList??null,n.nameScopesList);return new e(t,gO(n.ruleId),n.enterPos??-1,n.anchorPos??-1,n.beginRuleCapturedEOL,n.endRule,r,YO.fromExtension(r,n.contentNameScopesList))}},ZO=class{balancedBracketScopes;unbalancedBracketScopes;allowAny=!1;constructor(e,t){this.balancedBracketScopes=e.flatMap(e=>e===`*`?(this.allowAny=!0,[]):JD(e,GO).map(e=>e.matcher)),this.unbalancedBracketScopes=t.flatMap(e=>JD(e,GO).map(e=>e.matcher))}get matchesAlways(){return this.allowAny&&this.unbalancedBracketScopes.length===0}get matchesNever(){return this.balancedBracketScopes.length===0&&!this.allowAny}match(e){for(let t of this.unbalancedBracketScopes)if(t(e))return!1;for(let t of this.balancedBracketScopes)if(t(e))return!0;return this.allowAny}},QO=class{constructor(e,t,n,r){this.balancedBracketSelectors=r,this._emitBinaryTokens=e,this._tokenTypeOverrides=n,this._lineText=null,this._tokens=[],this._binaryTokens=[],this._lastTokenEndIndex=0}_emitBinaryTokens;_lineText;_tokens;_binaryTokens;_lastTokenEndIndex;_tokenTypeOverrides;produce(e,t){this.produceFromScopes(e.contentNameScopesList,t)}produceFromScopes(e,t){if(this._lastTokenEndIndex>=t)return;if(this._emitBinaryTokens){let n=e?.tokenAttributes??0,r=!1;if(this.balancedBracketSelectors?.matchesAlways&&(r=!0),this._tokenTypeOverrides.length>0||this.balancedBracketSelectors&&!this.balancedBracketSelectors.matchesAlways&&!this.balancedBracketSelectors.matchesNever){let t=e?.getScopeNames()??[];for(let e of this._tokenTypeOverrides)e.matcher(t)&&(n=GD.set(n,0,KD(e.type),null,-1,0,0));this.balancedBracketSelectors&&(r=this.balancedBracketSelectors.match(t))}if(r&&(n=GD.set(n,0,8,r,-1,0,0)),this._binaryTokens.length>0&&this._binaryTokens[this._binaryTokens.length-1]===n){this._lastTokenEndIndex=t;return}this._binaryTokens.push(this._lastTokenEndIndex),this._binaryTokens.push(n),this._lastTokenEndIndex=t;return}let n=e?.getScopeNames()??[];this._tokens.push({startIndex:this._lastTokenEndIndex,endIndex:t,scopes:n}),this._lastTokenEndIndex=t}getResult(e,t){return this._tokens.length>0&&this._tokens[this._tokens.length-1].startIndex===t-1&&this._tokens.pop(),this._tokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(e,t),this._tokens[this._tokens.length-1].startIndex=0),this._tokens}getBinaryResult(e,t){this._binaryTokens.length>0&&this._binaryTokens[this._binaryTokens.length-2]===t-1&&(this._binaryTokens.pop(),this._binaryTokens.pop()),this._binaryTokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(e,t),this._binaryTokens[this._binaryTokens.length-2]=0);let n=new Uint32Array(this._binaryTokens.length);for(let e=0,t=this._binaryTokens.length;e0;)a.Q.map(e=>this._loadSingleGrammar(e.scopeName)),a.processQueue();return this._grammarForScopeName(e,t,n,r,i)}_loadSingleGrammar(e){this._ensureGrammarCache.has(e)||(this._doLoadSingleGrammar(e),this._ensureGrammarCache.set(e,!0))}_doLoadSingleGrammar(e){let t=this._options.loadGrammar(e);if(t){let n=typeof this._options.getInjections==`function`?this._options.getInjections(e):void 0;this._syncRegistry.addGrammar(t,n)}}addGrammar(e,t=[],n=0,r=null){return this._syncRegistry.addGrammar(e,t),this._grammarForScopeName(e.scopeName,n,r)}_grammarForScopeName(e,t=0,n=null,r=null,i=null){return this._syncRegistry.grammarForScopeName(e,t,n,r,i)}},tk=XO.NULL;const nk=[`area`,`base`,`basefont`,`bgsound`,`br`,`col`,`command`,`embed`,`frame`,`hr`,`image`,`img`,`input`,`keygen`,`link`,`meta`,`param`,`source`,`track`,`wbr`];var rk=class{constructor(e,t,n){this.normal=t,this.property=e,n&&(this.space=n)}};rk.prototype.normal={},rk.prototype.property={},rk.prototype.space=void 0;function ik(e,t){let n={},r={};for(let t of e)Object.assign(n,t.property),Object.assign(r,t.normal);return new rk(n,r,t)}function ak(e){return e.toLowerCase()}var ok=class{constructor(e,t){this.attribute=t,this.property=e}};ok.prototype.attribute=``,ok.prototype.booleanish=!1,ok.prototype.boolean=!1,ok.prototype.commaOrSpaceSeparated=!1,ok.prototype.commaSeparated=!1,ok.prototype.defined=!1,ok.prototype.mustUseProperty=!1,ok.prototype.number=!1,ok.prototype.overloadedBoolean=!1,ok.prototype.property=``,ok.prototype.spaceSeparated=!1,ok.prototype.space=void 0;var sk=n({boolean:()=>lk,booleanish:()=>uk,commaOrSpaceSeparated:()=>mk,commaSeparated:()=>pk,number:()=>Y,overloadedBoolean:()=>dk,spaceSeparated:()=>fk},1),ck=0;const lk=hk(),uk=hk(),dk=hk(),Y=hk(),fk=hk(),pk=hk(),mk=hk();function hk(){return 2**++ck}var gk=Object.keys(sk),_k=class extends ok{constructor(e,t,n,r){let i=-1;if(super(e,t),vk(this,`space`,r),typeof n==`number`)for(;++i4&&n.slice(0,4)===`data`&&Ak.test(t)){if(t.charAt(4)===`-`){let e=t.slice(5).replace(kk,Nk);r=`data`+e.charAt(0).toUpperCase()+e.slice(1)}else{let e=t.slice(4);if(!kk.test(e)){let n=e.replace(Ok,Mk);n.charAt(0)!==`-`&&(n=`-`+n),t=`data`+n}}i=_k}return new i(r,t)}function Mk(e){return`-`+e.toLowerCase()}function Nk(e){return e.charAt(1).toUpperCase()}const Pk=ik([bk,Ck,Tk,Ek,Dk],`html`),Fk=ik([bk,wk,Tk,Ek,Dk],`svg`);var Ik={}.hasOwnProperty;function Lk(e,t){let n=t||{};function r(t,...n){let i=r.invalid,a=r.handlers;if(t&&Ik.call(t,e)){let n=String(t[e]);i=Ik.call(a,n)?a[n]:r.unknown}if(i)return i.call(this,t,...n)}return r.handlers=n.handlers||{},r.invalid=n.invalid,r.unknown=n.unknown,r}var Rk=/["&'<>`]/g,zk=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,Bk=/[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g,Vk=/[|\\{}()[\]^$+*?.]/g,Hk=new WeakMap;function Uk(e,t){if(e=e.replace(t.subset?Wk(t.subset):Rk,r),t.subset||t.escapeOnly)return e;return e.replace(zk,n).replace(Bk,r);function n(e,n,r){return t.format((e.charCodeAt(0)-55296)*1024+e.charCodeAt(1)-56320+65536,r.charCodeAt(n+2),t)}function r(e,n,r){return t.format(e.charCodeAt(0),r.charCodeAt(n+1),t)}}function Wk(e){let t=Hk.get(e);return t||(t=Gk(e),Hk.set(e,t)),t}function Gk(e){let t=[],n=-1;for(;++n`,OElig:`Œ`,oelig:`œ`,Scaron:`Š`,scaron:`š`,Yuml:`Ÿ`,circ:`ˆ`,tilde:`˜`,ensp:` `,emsp:` `,thinsp:` `,zwnj:`‌`,zwj:`‍`,lrm:`‎`,rlm:`‏`,ndash:`–`,mdash:`—`,lsquo:`‘`,rsquo:`’`,sbquo:`‚`,ldquo:`“`,rdquo:`”`,bdquo:`„`,dagger:`†`,Dagger:`‡`,permil:`‰`,lsaquo:`‹`,rsaquo:`›`,euro:`€`},Qk=[`cent`,`copy`,`divide`,`gt`,`lt`,`not`,`para`,`times`];var $k={}.hasOwnProperty,eA={},tA;for(tA in Zk)$k.call(Zk,tA)&&(eA[Zk[tA]]=tA);var nA=/[^\dA-Za-z]/;function rA(e,t,n,r){let i=String.fromCharCode(e);if($k.call(eA,i)){let e=eA[i],a=`&`+e;return n&&Xk.includes(e)&&!Qk.includes(e)&&(!r||t&&t!==61&&nA.test(String.fromCharCode(t)))?a:a+`;`}return``}function iA(e,t,n){let r=qk(e,t,n.omitOptionalSemicolons),i;if((n.useNamedReferences||n.useShortestReferences)&&(i=rA(e,t,n.omitOptionalSemicolons,n.attribute)),(n.useShortestReferences||!i)&&n.useShortestReferences){let i=Yk(e,t,n.omitOptionalSemicolons);i.length|^->||--!>|`],cA=[`<`,`>`];function lA(e,t,n,r){return r.settings.bogusComments?``:``;function i(e){return aA(e,Object.assign({},r.settings.characterReferences,{subset:cA}))}}function uA(e,t,n,r){return``}function dA(e,t){let n=String(e);if(typeof t!=`string`)throw TypeError(`Expected character`);let r=0,i=n.indexOf(t);for(;i!==-1;)r++,i=n.indexOf(t,i+t.length);return r}function fA(e,t){let n=t||{};return(e[e.length-1]===``?[...e,``]:e).join((n.padRight?` `:``)+`,`+(n.padLeft===!1?``:` `)).trim()}function pA(e){return e.join(` `).trim()}var mA=/[ \t\n\f\r]/g;function hA(e){return typeof e==`object`?e.type===`text`?gA(e.value):!1:gA(e)}function gA(e){return e.replace(mA,``)===``}const _A=bA(1),vA=bA(-1);var yA=[];function bA(e){return t;function t(t,n,r){let i=t?t.children:yA,a=(n||0)+e,o=i[a];if(!r)for(;o&&hA(o);)a+=e,o=i[a];return o}}var xA={}.hasOwnProperty;function SA(e){return t;function t(t,n,r){return xA.call(e,t.tagName)&&e[t.tagName](t,n,r)}}const CA=SA({body:EA,caption:wA,colgroup:wA,dd:AA,dt:kA,head:wA,html:TA,li:OA,optgroup:MA,option:NA,p:DA,rp:jA,rt:jA,tbody:FA,td:RA,tfoot:IA,th:RA,thead:PA,tr:LA});function wA(e,t,n){let r=_A(n,t,!0);return!r||r.type!==`comment`&&!(r.type===`text`&&hA(r.value.charAt(0)))}function TA(e,t,n){let r=_A(n,t);return!r||r.type!==`comment`}function EA(e,t,n){let r=_A(n,t);return!r||r.type!==`comment`}function DA(e,t,n){let r=_A(n,t);return r?r.type===`element`&&(r.tagName===`address`||r.tagName===`article`||r.tagName===`aside`||r.tagName===`blockquote`||r.tagName===`details`||r.tagName===`div`||r.tagName===`dl`||r.tagName===`fieldset`||r.tagName===`figcaption`||r.tagName===`figure`||r.tagName===`footer`||r.tagName===`form`||r.tagName===`h1`||r.tagName===`h2`||r.tagName===`h3`||r.tagName===`h4`||r.tagName===`h5`||r.tagName===`h6`||r.tagName===`header`||r.tagName===`hgroup`||r.tagName===`hr`||r.tagName===`main`||r.tagName===`menu`||r.tagName===`nav`||r.tagName===`ol`||r.tagName===`p`||r.tagName===`pre`||r.tagName===`section`||r.tagName===`table`||r.tagName===`ul`):!n||!(n.type===`element`&&(n.tagName===`a`||n.tagName===`audio`||n.tagName===`del`||n.tagName===`ins`||n.tagName===`map`||n.tagName===`noscript`||n.tagName===`video`))}function OA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&r.tagName===`li`}function kA(e,t,n){let r=_A(n,t);return!!(r&&r.type===`element`&&(r.tagName===`dt`||r.tagName===`dd`))}function AA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&(r.tagName===`dt`||r.tagName===`dd`)}function jA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&(r.tagName===`rp`||r.tagName===`rt`)}function MA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&r.tagName===`optgroup`}function NA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&(r.tagName===`option`||r.tagName===`optgroup`)}function PA(e,t,n){let r=_A(n,t);return!!(r&&r.type===`element`&&(r.tagName===`tbody`||r.tagName===`tfoot`))}function FA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&(r.tagName===`tbody`||r.tagName===`tfoot`)}function IA(e,t,n){return!_A(n,t)}function LA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&r.tagName===`tr`}function RA(e,t,n){let r=_A(n,t);return!r||r.type===`element`&&(r.tagName===`td`||r.tagName===`th`)}const zA=SA({body:HA,colgroup:UA,head:VA,html:BA,tbody:WA});function BA(e){let t=_A(e,-1);return!t||t.type!==`comment`}function VA(e){let t=new Set;for(let n of e.children)if(n.type===`element`&&(n.tagName===`base`||n.tagName===`title`)){if(t.has(n.tagName))return!1;t.add(n.tagName)}let n=e.children[0];return!n||n.type===`element`}function HA(e){let t=_A(e,-1,!0);return!t||t.type!==`comment`&&!(t.type===`text`&&hA(t.value.charAt(0)))&&!(t.type===`element`&&(t.tagName===`meta`||t.tagName===`link`||t.tagName===`script`||t.tagName===`style`||t.tagName===`template`))}function UA(e,t,n){let r=vA(n,t),i=_A(e,-1,!0);return n&&r&&r.type===`element`&&r.tagName===`colgroup`&&CA(r,n.children.indexOf(r),n)?!1:!!(i&&i.type===`element`&&i.tagName===`col`)}function WA(e,t,n){let r=vA(n,t),i=_A(e,-1);return n&&r&&r.type===`element`&&(r.tagName===`thead`||r.tagName===`tbody`)&&CA(r,n.children.indexOf(r),n)?!1:!!(i&&i.type===`element`&&i.tagName===`tr`)}var GA={name:[[` +\f\r &/=>`.split(``),` +\f\r "&'/=>\``.split(``)],[`\0 +\f\r "&'/<=>`.split(``),`\0 +\f\r "&'/<=>\``.split(``)]],unquoted:[[` +\f\r &>`.split(``),`\0 +\f\r "&'<=>\``.split(``)],[`\0 +\f\r "&'<=>\``.split(``),`\0 +\f\r "&'<=>\``.split(``)]],single:[[`&'`.split(``),`"&'\``.split(``)],[`\0&'`.split(``),`\0"&'\``.split(``)]],double:[[`"&`.split(``),`"&'\``.split(``)],[`\0"&`.split(``),`\0"&'\``.split(``)]]};function KA(e,t,n,r){let i=r.schema,a=i.space===`svg`?!1:r.settings.omitOptionalTags,o=i.space===`svg`?r.settings.closeEmptyElements:r.settings.voids.includes(e.tagName.toLowerCase()),s=[],c;i.space===`html`&&e.tagName===`svg`&&(r.schema=Fk);let l=qA(r,e.properties),u=r.all(i.space===`html`&&e.tagName===`template`?e.content:e);return r.schema=i,u&&(o=!1),(l||!a||!zA(e,t,n))&&(s.push(`<`,e.tagName,l?` `+l:``),o&&(i.space===`svg`||r.settings.closeSelfClosing)&&(c=l.charAt(l.length-1),(!r.settings.tightSelfClosing||c===`/`||c&&c!==`"`&&c!==`'`)&&s.push(` `),s.push(`/`)),s.push(`>`)),s.push(u),!o&&(!a||!CA(e,t,n))&&s.push(``),s.join(``)}function qA(e,t){let n=[],r=-1,i;if(t){for(i in t)if(t[i]!==null&&t[i]!==void 0){let r=JA(e,i,t[i]);r&&n.push(r)}}for(;++rdA(n,e.alternative)&&(o=e.alternative),s=o+aA(n,Object.assign({},e.settings.characterReferences,{subset:(o===`'`?GA.single:GA.double)[i][a],attribute:!0}))+o),c+(s&&`=`+s))}var YA=[`<`,`&`];function XA(e,t,n,r){return n&&n.type===`element`&&(n.tagName===`script`||n.tagName===`style`)?e.value:aA(e.value,Object.assign({},r.settings.characterReferences,{subset:YA}))}function ZA(e,t,n,r){return r.settings.allowDangerousHtml?e.value:XA(e,t,n,r)}function QA(e,t,n,r){return r.all(e)}const $A=Lk(`type`,{invalid:ej,unknown:tj,handlers:{comment:lA,doctype:uA,element:KA,raw:ZA,root:QA,text:XA}});function ej(e){throw Error("Expected node, not `"+e+"`")}function tj(e){let t=e;throw Error("Cannot compile unknown node `"+t.type+"`")}var nj={},rj={},ij=[];function aj(e,t){let n=t||nj,r=n.quote||`"`,i=r===`"`?`'`:`"`;if(r!==`"`&&r!==`'`)throw Error("Invalid quote `"+r+"`, expected `'` or `\"`");return{one:oj,all:sj,settings:{omitOptionalTags:n.omitOptionalTags||!1,allowParseErrors:n.allowParseErrors||!1,allowDangerousCharacters:n.allowDangerousCharacters||!1,quoteSmart:n.quoteSmart||!1,preferUnquoted:n.preferUnquoted||!1,tightAttributes:n.tightAttributes||!1,upperDoctype:n.upperDoctype||!1,tightDoctype:n.tightDoctype||!1,bogusComments:n.bogusComments||!1,tightCommaSeparatedLists:n.tightCommaSeparatedLists||!1,tightSelfClosing:n.tightSelfClosing||!1,collapseEmptyAttributes:n.collapseEmptyAttributes||!1,allowDangerousHtml:n.allowDangerousHtml||!1,voids:n.voids||nk,characterReferences:n.characterReferences||rj,closeSelfClosing:n.closeSelfClosing||!1,closeEmptyElements:n.closeEmptyElements||!1},schema:n.space===`svg`?Fk:Pk,quote:r,alternative:i}.one(Array.isArray(e)?{type:`root`,children:e}:e,void 0,void 0)}function oj(e,t,n){return $A(e,t,n,this)}function sj(e){let t=[],n=e&&e.children||ij,r=-1;for(;++re.default||e)}function fj(e){return!e||[`plaintext`,`txt`,`text`,`plain`].includes(e)}function pj(e){return e===`ansi`||fj(e)}function mj(e){return e===`none`}function hj(e){return mj(e)}function gj(e,t){if(!t)return e;e.properties||={},e.properties.class||=[],typeof e.properties.class==`string`&&(e.properties.class=e.properties.class.split(/\s+/g)),Array.isArray(e.properties.class)||(e.properties.class=[]);let n=Array.isArray(t)?t:t.split(/\s+/g);for(let t of n)t&&!e.properties.class.includes(t)&&e.properties.class.push(t);return e}function _j(e,t=!1){if(e.length===0)return[[``,0]];let n=e.split(/(\r?\n)/g),r=0,i=[];for(let e=0;ee);function n(n){if(n===e.length)return{line:t.length-1,character:t[t.length-1].length};let r=n,i=0;for(let e of t){if(rn&&r.push({...e,content:e.content.slice(n,i),offset:e.offset+n}),n=i;return ne-t);return n.length?e.map(e=>e.flatMap(e=>{let t=n.filter(t=>e.offsett-e.offset).sort((e,t)=>e-t);return t.length?xj(e,t):e})):e}function Cj(e,t,n,r,i=`css-vars`){let a={content:e.content,explanation:e.explanation,offset:e.offset},o=t.map(t=>wj(e.variants[t])),s=new Set(o.flatMap(e=>Object.keys(e))),c={},l=(e,r)=>{let i=r===`color`?``:r===`background-color`?`-bg`:`-${r}`;return n+t[e]+(r===`color`?``:i)};return o.forEach((e,n)=>{for(let a of s){let s=e[a]||`inherit`;if(n===0&&r&&bj.includes(a))if(r===yj&&o.length>1){let e=t.findIndex(e=>e===`light`),r=t.findIndex(e=>e===`dark`);if(e===-1||r===-1)throw new vD('When using `defaultColor: "light-dark()"`, you must provide both `light` and `dark` themes');c[a]=`light-dark(${o[e][a]||`inherit`}, ${o[r][a]||`inherit`})`,i===`css-vars`&&(c[l(n,a)]=s)}else c[a]=s;else i===`css-vars`&&(c[l(n,a)]=s)}}),a.htmlStyle=c,a}function wj(e){let t={};if(e.color&&(t.color=e.color),e.bgColor&&(t[`background-color`]=e.bgColor),e.fontStyle){e.fontStyle&zD.Italic&&(t[`font-style`]=`italic`),e.fontStyle&zD.Bold&&(t[`font-weight`]=`bold`);let n=[];e.fontStyle&zD.Underline&&n.push(`underline`),e.fontStyle&zD.Strikethrough&&n.push(`line-through`),n.length&&(t[`text-decoration`]=n.join(` `))}return t}function Tj(e){return typeof e==`string`?e:Object.entries(e).map(([e,t])=>`${e}:${t}`).join(`;`)}var Ej=new WeakMap;function Dj(e,t){Ej.set(e,t)}function Oj(e){return Ej.get(e)}var kj=class e{_stacks={};lang;get themes(){return Object.keys(this._stacks)}get theme(){return this.themes[0]}get _stack(){return this._stacks[this.theme]}static initial(t,n){return new e(Object.fromEntries(uj(n).map(e=>[e,tk])),t)}constructor(...e){if(e.length===2){let[t,n]=e;this.lang=n,this._stacks=t}else{let[t,n,r]=e;this.lang=n,this._stacks={[r]:t}}}getInternalStack(e=this.theme){return this._stacks[e]}getScopes(e=this.theme){return Aj(this._stacks[e])}toJSON(){return{lang:this.lang,theme:this.theme,themes:this.themes,scopes:this.getScopes()}}};function Aj(e){let t=[],n=new Set;function r(e){if(n.has(e))return;n.add(e);let i=e?.nameScopesList?.scopeName;i&&t.push(i),e.parent&&r(e.parent)}return r(e),t}function jj(e,t){if(!(e instanceof kj))throw new vD(`Invalid grammar state`);return e.getInternalStack(t)}function Mj(){let e=new WeakMap;function t(t){if(!e.has(t.meta)){let n=function(e){if(typeof e==`number`){if(e<0||e>t.source.length)throw new vD(`Invalid decoration offset: ${e}. Code length: ${t.source.length}`);return{...r.indexToPos(e),offset:e}}else{let t=r.lines[e.line];if(t===void 0)throw new vD(`Invalid decoration position ${JSON.stringify(e)}. Lines length: ${r.lines.length}`);let n=e.character;if(n<0&&(n=t.length+n),n<0||n>t.length)throw new vD(`Invalid decoration position ${JSON.stringify(e)}. Line ${e.line} length: ${t.length}`);return{...e,character:n,offset:r.posToIndex(e.line,n)}}},r=vj(t.source),i=(t.options.decorations||[]).map(e=>({...e,start:n(e.start),end:n(e.end)}));Nj(i),e.set(t.meta,{decorations:i,converter:r,source:t.source})}return e.get(t.meta)}return{name:`shiki:decorations`,tokens(e){if(this.options.decorations?.length)return Sj(e,t(this).decorations.flatMap(e=>[e.start.offset,e.end.offset]))},code(e){if(!this.options.decorations?.length)return;let n=t(this),r=Array.from(e.children).filter(e=>e.type===`element`&&e.tagName===`span`);if(r.length!==n.converter.lines.length)throw new vD(`Number of lines in code element (${r.length}) does not match the number of lines in the source (${n.converter.lines.length}). Failed to apply decorations.`);function i(e,t,n,i){let a=r[e],s=``,c=-1,l=-1;if(t===0&&(c=0),n===0&&(l=0),n===1/0&&(l=a.children.length),c===-1||l===-1)for(let e=0;ee);return e.tagName=t.tagName||`span`,e.properties={...e.properties,...r,class:e.properties.class},t.properties?.class&&gj(e,t.properties.class),e=i(e,n)||e,e}let s=[],c=n.decorations.sort((e,t)=>t.start.offset-e.start.offset||e.end.offset-t.end.offset);for(let e of c){let{start:t,end:n}=e;if(t.line===n.line)i(t.line,t.character,n.character,e);else if(t.linea(r,e));i(n.line,0,n.character,e)}}s.forEach(e=>e())}}}function Nj(e){for(let t=0;tn.end.offset)throw new vD(`Invalid decoration range: ${JSON.stringify(n.start)} - ${JSON.stringify(n.end)}`);for(let r=t+1;rNumber.parseInt(e));return t.length!==3||t.some(e=>Number.isNaN(e))?void 0:{type:`rgb`,rgb:t}}else if(t===`5`){let t=e.shift();if(t)return{type:`table`,index:Number(t)}}}function Hj(e){let t=[];for(;e.length>0;){let n=e.shift();if(!n)continue;let r=Number.parseInt(n);if(!Number.isNaN(r))if(r===0)t.push({type:`resetAll`});else if(r<=9)zj[r]&&t.push({type:`setDecoration`,value:zj[r]});else if(r<=29){let e=zj[r-20];e&&(t.push({type:`resetDecoration`,value:e}),e===`dim`&&t.push({type:`resetDecoration`,value:`bold`}))}else if(r<=37)t.push({type:`setForegroundColor`,value:{type:`named`,name:Rj[r-30]}});else if(r===38){let n=Vj(e);n&&t.push({type:`setForegroundColor`,value:n})}else if(r===39)t.push({type:`resetForegroundColor`});else if(r<=47)t.push({type:`setBackgroundColor`,value:{type:`named`,name:Rj[r-40]}});else if(r===48){let n=Vj(e);n&&t.push({type:`setBackgroundColor`,value:n})}else r===49?t.push({type:`resetBackgroundColor`}):r===53?t.push({type:`setDecoration`,value:`overline`}):r===55?t.push({type:`resetDecoration`,value:`overline`}):r>=90&&r<=97?t.push({type:`setForegroundColor`,value:{type:`named`,name:Rj[r-90+8]}}):r>=100&&r<=107&&t.push({type:`setBackgroundColor`,value:{type:`named`,name:Rj[r-100+8]}})}return t}function Uj(){let e=null,t=null,n=new Set;return{parse(r){let i=[],a=0;do{let o=Bj(r,a),s=o.sequence?r.substring(a,o.startPosition):r.substring(a);if(s.length>0&&i.push({value:s,foreground:e,background:t,decorations:new Set(n)}),o.sequence){let r=Hj(o.sequence);for(let i of r)i.type===`resetAll`?(e=null,t=null,n.clear()):i.type===`resetForegroundColor`?e=null:i.type===`resetBackgroundColor`?t=null:i.type===`resetDecoration`&&n.delete(i.value);for(let i of r)i.type===`setForegroundColor`?e=i.value:i.type===`setBackgroundColor`?t=i.value:i.type===`setDecoration`&&n.add(i.value)}a=o.position}while(aMath.max(0,Math.min(e,255)).toString(16).padStart(2,`0`)).join(``)}`}let r;function i(){if(r)return r;r=[];for(let e=0;e{let n=`terminal.ansi${t[0].toUpperCase()}${t.substring(1)}`;return[t,e.colors?.[n]||Kj[t]]}))),o=Uj();return i.map(t=>o.parse(t[0]).map(n=>{let i,o;n.decorations.has(`reverse`)?(i=n.background?a.value(n.background):e.bg,o=n.foreground?a.value(n.foreground):e.fg):(i=n.foreground?a.value(n.foreground):e.fg,o=n.background?a.value(n.background):void 0),i=lj(i,r),o=lj(o,r),n.decorations.has(`dim`)&&(i=Jj(i));let s=zD.None;return n.decorations.has(`bold`)&&(s|=zD.Bold),n.decorations.has(`italic`)&&(s|=zD.Italic),n.decorations.has(`underline`)&&(s|=zD.Underline),n.decorations.has(`strikethrough`)&&(s|=zD.Strikethrough),{content:n.value,offset:t[1],color:i,bgColor:o,fontStyle:s}}))}function Jj(e){let t=e.match(/#([0-9a-f]{3,8})/i);if(t){let e=t[1];if(e.length===8){let t=Math.round(Number.parseInt(e.slice(6,8),16)/2).toString(16).padStart(2,`0`);return`#${e.slice(0,6)}${t}`}else if(e.length===6)return`#${e}80`;else if(e.length===4){let t=e[0],n=e[1],r=e[2],i=e[3];return`#${t}${t}${n}${n}${r}${r}${Math.round(Number.parseInt(`${i}${i}`,16)/2).toString(16).padStart(2,`0`)}`}else if(e.length===3){let t=e[0],n=e[1],r=e[2];return`#${t}${t}${n}${n}${r}${r}80`}}let n=e.match(/var\((--[\w-]+-ansi-[\w-]+)\)/);return n?`var(${n[1]}-dim)`:e}function Yj(e,t,n={}){let{theme:r=e.getLoadedThemes()[0]}=n,i=e.resolveLangAlias(n.lang||`text`);if(fj(i)||mj(r))return _j(t).map(e=>[{content:e[0],offset:e[1]}]);let{theme:a,colorMap:o}=e.setTheme(r);if(i===`ansi`)return qj(a,t,n);let s=e.getLanguage(n.lang||`text`);if(n.grammarState){if(n.grammarState.lang!==s.name)throw new vD(`Grammar state language "${n.grammarState.lang}" does not match highlight language "${s.name}"`);if(!n.grammarState.themes.includes(a.name))throw new vD(`Grammar state themes "${n.grammarState.themes}" do not contain highlight theme "${a.name}"`)}return Zj(t,s,a,o,n)}function Xj(...e){if(e.length===2)return Oj(e[1]);let[t,n,r={}]=e,{lang:i=`text`,theme:a=t.getLoadedThemes()[0]}=r;if(fj(i)||mj(a))throw new vD(`Plain language does not have grammar state`);if(i===`ansi`)throw new vD(`ANSI language does not have grammar state`);let{theme:o,colorMap:s}=t.setTheme(a),c=t.getLanguage(i);return new kj(Qj(n,c,o,s,r).stateStack,c.name,o.name)}function Zj(e,t,n,r,i){let a=Qj(e,t,n,r,i),o=new kj(a.stateStack,t.name,n.name);return Dj(a.tokens,o),a.tokens}function Qj(e,t,n,r,i){let a=cj(n,i),{tokenizeMaxLineLength:o=0,tokenizeTimeLimit:s=500}=i,c=_j(e),l=i.grammarState?jj(i.grammarState,n.name)??tk:i.grammarContextCode==null?tk:Qj(i.grammarContextCode,t,n,r,{...i,grammarState:void 0,grammarContextCode:void 0}).stateStack,u=[],d=[];for(let e=0,f=c.length;e0&&f.length>=o){u=[],d.push([{content:f,offset:p,color:``,fontStyle:0}]);continue}let m,h,g;i.includeExplanation&&(m=t.tokenizeLine(f,l,s),h=m.tokens,g=0);let _=t.tokenizeLine2(f,l,s),v=_.tokens.length/2;for(let e=0;ee.trim());break;case`object`:n=t.scope;break;default:continue}e.push({settings:t,selectors:n.map(e=>e.split(/ /))})}d.explanation=[];let r=0;for(;t+r({scopeName:e}))}function eM(e,t){let n=[];for(let r=0,i=t.length;r=0&&i>=0;)tM(e[r],n[i])&&--r,--i;return r===-1}function rM(e,t,n){let r=[];for(let{selectors:i,settings:a}of e)for(let e of i)if(nM(e,t,n)){r.push(a);break}return r}function iM(e,t,n){let r=Object.entries(n.themes).filter(e=>e[1]).map(e=>({color:e[0],theme:e[1]})),i=r.map(r=>{let i=Yj(e,t,{...n,theme:r.theme});return{tokens:i,state:Oj(i),theme:typeof r.theme==`string`?r.theme:r.theme.name}}),a=aM(...i.map(e=>e.tokens)),o=a[0].map((e,t)=>e.map((e,i)=>{let o={content:e.content,variants:{},offset:e.offset};return`includeExplanation`in n&&n.includeExplanation&&(o.explanation=e.explanation),a.forEach((e,n)=>{let{content:a,explanation:s,offset:c,...l}=e[t][i];o.variants[r[n].color]=l}),o})),s=i[0].state?new kj(Object.fromEntries(i.map(e=>[e.theme,e.state?.getInternalStack(e.theme)])),i[0].state.lang):void 0;return s&&Dj(o,s),o}function aM(...e){let t=e.map(()=>[]),n=e.length;for(let r=0;re[r]),a=t.map(()=>[]);t.forEach((e,t)=>e.push(a[t]));let o=i.map(()=>0),s=i.map(e=>e[0]);for(;s.every(e=>e);){let e=Math.min(...s.map(e=>e.content.length));for(let t=0;te[1]).map(e=>({color:e[0],theme:e[1]})).sort((e,t)=>e.color===l?-1:t.color===l?1:0);if(f.length===0)throw new vD("`themes` option must not be empty");let p=iM(e,t,n);if(c=Oj(p),l&&yj!==l&&!f.find(e=>e.color===l))throw new vD(`\`themes\` option must contain the defaultColor key \`${l}\``);let m=f.map(t=>e.getTheme(t.theme)),h=f.map(e=>e.color);a=p.map(e=>e.map(e=>Cj(e,h,u,l,d))),c&&Dj(a,c);let g=f.map(e=>cj(e.theme,n));i=sM(f,m,g,u,l,`fg`,d),r=sM(f,m,g,u,l,`bg`,d),o=`shiki-themes ${m.map(e=>e.name).join(` `)}`,s=l?void 0:[i,r].join(`;`)}else if(`theme`in n){let s=cj(n.theme,n);a=Yj(e,t,n);let l=e.getTheme(n.theme);r=lj(l.bg,s),i=lj(l.fg,s),o=l.name,c=Oj(a)}else throw new vD("Invalid options, either `theme` or `themes` must be provided");return{tokens:a,fg:i,bg:r,themeName:o,rootStyle:s,grammarState:c}}function sM(e,t,n,r,i,a,o){return e.map((s,c)=>{let l=lj(t[c][a],n[c])||`inherit`,u=`${r+s.color}${a===`bg`?`-bg`:``}:${l}`;if(c===0&&i){if(i===yj&&e.length>1){let r=e.findIndex(e=>e.color===`light`),i=e.findIndex(e=>e.color===`dark`);if(r===-1||i===-1)throw new vD('When using `defaultColor: "light-dark()"`, you must provide both `light` and `dark` themes');return`light-dark(${lj(t[r][a],n[r])||`inherit`}, ${lj(t[i][a],n[i])||`inherit`});${u}`}return l}return o===`css-vars`?u:null}).filter(e=>!!e).join(`;`)}function cM(e,t,n,r={meta:{},options:n,codeToHast:(t,n)=>cM(e,t,n),codeToTokens:(t,n)=>oM(e,t,n)}){let i=t;for(let e of Ij(n))i=e.preprocess?.call(r,i,n)||i;let{tokens:a,fg:o,bg:s,themeName:c,rootStyle:l,grammarState:u}=oM(e,i,n),{mergeWhitespaces:d=!0,mergeSameStyleTokens:f=!1}=n;d===!0?a=uM(a):d===`never`&&(a=dM(a)),f&&(a=fM(a));let p={...r,get source(){return i}};for(let e of Ij(n))a=e.tokens?.call(p,a)||a;return lM(a,{...n,fg:o,bg:s,themeName:c,rootStyle:n.rootStyle===!1?!1:n.rootStyle??l},p,u)}function lM(e,t,n,r=Oj(e)){let i=Ij(t),a=[],o={type:`root`,children:[]},{structure:s=`classic`,tabindex:c=`0`}=t,l={class:`shiki ${t.themeName||``}`};t.rootStyle!==!1&&(t.rootStyle==null?l.style=`background-color:${t.bg};color:${t.fg}`:l.style=t.rootStyle),c!==!1&&c!=null&&(l.tabindex=c.toString());for(let[e,n]of Object.entries(t.meta||{}))e.startsWith(`_`)||(l[e]=n);let u={type:`element`,tagName:`pre`,properties:l,children:[],data:t.data},d={type:`element`,tagName:`code`,properties:{},children:a},f=[],p={...n,structure:s,addClassToHast:gj,get source(){return n.source},get tokens(){return e},get options(){return t},get root(){return o},get pre(){return u},get code(){return d},get lines(){return f}};if(e.forEach((e,t)=>{t&&(s===`inline`?o.children.push({type:`element`,tagName:`br`,properties:{},children:[]}):s===`classic`&&a.push({type:`text`,value:` +`}));let n={type:`element`,tagName:`span`,properties:{class:`line`},children:[]},r=0;for(let a of e){let e={type:`element`,tagName:`span`,properties:{...a.htmlAttrs},children:[{type:`text`,value:a.content}]},c=Tj(a.htmlStyle||wj(a));c&&(e.properties.style=c);for(let o of i)e=o?.span?.call(p,e,t+1,r,n,a)||e;s===`inline`?o.children.push(e):s===`classic`&&n.children.push(e),r+=a.content.length}if(s===`classic`){for(let e of i)n=e?.line?.call(p,n,t+1)||n;f.push(n),a.push(n)}else s===`inline`&&f.push(n)}),s===`classic`){for(let e of i)d=e?.code?.call(p,d)||d;u.children.push(d);for(let e of i)u=e?.pre?.call(p,u)||u;o.children.push(u)}else if(s===`inline`){let e=[],t={type:`element`,tagName:`span`,properties:{class:`line`},children:[]};for(let n of o.children)n.type===`element`&&n.tagName===`br`?(e.push(t),t={type:`element`,tagName:`span`,properties:{class:`line`},children:[]}):(n.type===`element`||n.type===`text`)&&t.children.push(n);e.push(t);let n={type:`element`,tagName:`code`,properties:{},children:e};for(let e of i)n=e?.code?.call(p,n)||n;o.children=[];for(let e=0;e0&&o.children.push({type:`element`,tagName:`br`,properties:{},children:[]});let t=n.children[e];t.type===`element`&&o.children.push(...t.children)}}let m=o;for(let e of i)m=e?.root?.call(p,m)||m;return r&&Dj(m,r),m}function uM(e){return e.map(e=>{let t=[],n=``,r;return e.forEach((i,a)=>{let o=!(i.fontStyle&&(i.fontStyle&zD.Underline||i.fontStyle&zD.Strikethrough));o&&i.content.match(/^\s+$/)&&e[a+1]?(r===void 0&&(r=i.offset),n+=i.content):n?(o?t.push({...i,offset:r,content:n+i.content}):t.push({content:n,offset:r},i),r=void 0,n=``):t.push(i)}),t})}function dM(e){return e.map(e=>e.flatMap(e=>{if(e.content.match(/^\s+$/))return e;let t=e.content.match(/^(\s*)(.*?)(\s*)$/);if(!t)return e;let[,n,r,i]=t;if(!n&&!i)return e;let a=[{...e,offset:e.offset+n.length,content:r}];return n&&a.unshift({content:n,offset:e.offset}),i&&a.push({content:i,offset:e.offset+n.length+r.length}),a}))}function fM(e){return e.map(e=>{let t=[];for(let n of e){if(t.length===0){t.push({...n});continue}let e=t[t.length-1],r=Tj(e.htmlStyle||wj(e)),i=Tj(n.htmlStyle||wj(n)),a=e.fontStyle&&(e.fontStyle&zD.Underline||e.fontStyle&zD.Strikethrough),o=n.fontStyle&&(n.fontStyle&zD.Underline||n.fontStyle&zD.Strikethrough);!a&&!o&&r===i?e.content+=n.content:t.push({...n})}return t})}var pM=aj;function mM(e,t,n){let r={meta:{},options:n,codeToHast:(t,n)=>cM(e,t,n),codeToTokens:(t,n)=>oM(e,t,n)},i=pM(cM(e,t,n,r));for(let e of Ij(n))i=e.postprocess?.call(r,i,n)||i;return i}var hM={light:`#333333`,dark:`#bbbbbb`},gM={light:`#fffffe`,dark:`#1e1e1e`},_M=`__shiki_resolved`;function vM(e){if(e?.[_M])return e;let t={...e};t.tokenColors&&!t.settings&&(t.settings=t.tokenColors,delete t.tokenColors),t.type||=`dark`,t.colorReplacements={...t.colorReplacements},t.settings||=[];let{bg:n,fg:r}=t;if(!n||!r){let e=t.settings?t.settings.find(e=>!e.name&&!e.scope):void 0;e?.settings?.foreground&&(r=e.settings.foreground),e?.settings?.background&&(n=e.settings.background),!r&&t?.colors?.[`editor.foreground`]&&(r=t.colors[`editor.foreground`]),!n&&t?.colors?.[`editor.background`]&&(n=t.colors[`editor.background`]),r||=t.type===`light`?hM.light:hM.dark,n||=t.type===`light`?gM.light:gM.dark,t.fg=r,t.bg=n}t.settings[0]&&t.settings[0].settings&&!t.settings[0].scope||t.settings.unshift({settings:{foreground:t.fg,background:t.bg}});let i=0,a=new Map;function o(e){if(a.has(e))return a.get(e);i+=1;let n=`#${i.toString(16).padStart(8,`0`).toLowerCase()}`;return t.colorReplacements?.[`#${n}`]?o(e):(a.set(e,n),n)}t.settings=t.settings.map(e=>{let n=e.settings?.foreground&&!e.settings.foreground.startsWith(`#`),r=e.settings?.background&&!e.settings.background.startsWith(`#`);if(!n&&!r)return e;let i={...e,settings:{...e.settings}};if(n){let n=o(e.settings.foreground);t.colorReplacements[n]=e.settings.foreground,i.settings.foreground=n}if(r){let n=o(e.settings.background);t.colorReplacements[n]=e.settings.background,i.settings.background=n}return i});for(let e of Object.keys(t.colors||{}))if((e===`editor.foreground`||e===`editor.background`||e.startsWith(`terminal.ansi`))&&!t.colors[e]?.startsWith(`#`)){let n=o(t.colors[e]);t.colorReplacements[n]=t.colors[e],t.colors[e]=n}return Object.defineProperty(t,_M,{enumerable:!1,writable:!1,value:!0}),t}async function yM(e){return Array.from(new Set((await Promise.all(e.filter(e=>!pj(e)).map(async e=>await dj(e).then(e=>Array.isArray(e)?e:[e])))).flat()))}async function bM(e){return(await Promise.all(e.map(async e=>hj(e)?null:vM(await dj(e))))).filter(e=>!!e)}var xM=3,SM=!1;function CM(e,t=3){if(xM&&!(typeof xM==`number`&&t>xM)){if(SM)throw Error(`[SHIKI DEPRECATE]: ${e}`);console.trace(`[SHIKI DEPRECATE]: ${e}`)}}var wM=class extends Error{constructor(e){super(e),this.name=`ShikiError`}};function TM(e,t){if(!t)return e;if(t[e]){let n=new Set([e]);for(;t[e];){if(e=t[e],n.has(e))throw new wM(`Circular alias \`${Array.from(n).join(` -> `)} -> ${e}\``);n.add(e)}}return e}var EM=class extends ek{constructor(e,t,n,r={}){super(e),this._resolver=e,this._themes=t,this._langs=n,this._alias=r,this._themes.map(e=>this.loadTheme(e)),this.loadLanguages(this._langs)}_resolvedThemes=new Map;_resolvedGrammars=new Map;_langMap=new Map;_langGraph=new Map;_textmateThemeCache=new WeakMap;_loadedThemesCache=null;_loadedLanguagesCache=null;getTheme(e){return typeof e==`string`?this._resolvedThemes.get(e):this.loadTheme(e)}loadTheme(e){let t=vM(e);return t.name&&(this._resolvedThemes.set(t.name,t),this._loadedThemesCache=null),t}getLoadedThemes(){return this._loadedThemesCache||=[...this._resolvedThemes.keys()],this._loadedThemesCache}setTheme(e){let t=this._textmateThemeCache.get(e);t||(t=MD.createFromRawTheme(e),this._textmateThemeCache.set(e,t)),this._syncRegistry.setTheme(t)}getGrammar(e){return e=TM(e,this._alias),this._resolvedGrammars.get(e)}loadLanguage(e){if(this.getGrammar(e.name))return;let t=new Set([...this._langMap.values()].filter(t=>t.embeddedLangsLazy?.includes(e.name)));this._resolver.addLanguage(e);let n={balancedBracketSelectors:e.balancedBracketSelectors||[`*`],unbalancedBracketSelectors:e.unbalancedBracketSelectors||[]};this._syncRegistry._rawGrammars.set(e.scopeName,e);let r=this.loadGrammarWithConfiguration(e.scopeName,1,n);if(r.name=e.name,this._resolvedGrammars.set(e.name,r),e.aliases&&e.aliases.forEach(t=>{this._alias[t]=e.name}),this._loadedLanguagesCache=null,t.size)for(let e of t)this._resolvedGrammars.delete(e.name),this._loadedLanguagesCache=null,this._syncRegistry?._injectionGrammars?.delete(e.scopeName),this._syncRegistry?._grammars?.delete(e.scopeName),this.loadLanguage(this._langMap.get(e.name))}dispose(){super.dispose(),this._resolvedThemes.clear(),this._resolvedGrammars.clear(),this._langMap.clear(),this._langGraph.clear(),this._loadedThemesCache=null}loadLanguages(e){for(let t of e)this.resolveEmbeddedLanguages(t);let t=Array.from(this._langGraph.entries()),n=t.filter(([e,t])=>!t);if(n.length){let e=t.filter(([e,t])=>t?(t.embeddedLanguages||t.embeddedLangs)?.some(e=>n.map(([e])=>e).includes(e)):!1).filter(e=>!n.includes(e));throw new wM(`Missing languages ${n.map(([e])=>`\`${e}\``).join(`, `)}, required by ${e.map(([e])=>`\`${e}\``).join(`, `)}`)}for(let[e,n]of t)this._resolver.addLanguage(n);for(let[e,n]of t)this.loadLanguage(n)}getLoadedLanguages(){return this._loadedLanguagesCache||=[...new Set([...this._resolvedGrammars.keys(),...Object.keys(this._alias)])],this._loadedLanguagesCache}resolveEmbeddedLanguages(e){this._langMap.set(e.name,e),this._langGraph.set(e.name,e);let t=e.embeddedLanguages??e.embeddedLangs;if(t)for(let e of t)this._langGraph.set(e,this._langMap.get(e))}},DM=class{_langs=new Map;_scopeToLang=new Map;_injections=new Map;_onigLib;constructor(e,t){this._onigLib={createOnigScanner:t=>e.createScanner(t),createOnigString:t=>e.createString(t)},t.forEach(e=>this.addLanguage(e))}get onigLib(){return this._onigLib}getLangRegistration(e){return this._langs.get(e)}loadGrammar(e){return this._scopeToLang.get(e)}addLanguage(e){this._langs.set(e.name,e),e.aliases&&e.aliases.forEach(t=>{this._langs.set(t,e)}),this._scopeToLang.set(e.scopeName,e),e.injectTo&&e.injectTo.forEach(t=>{this._injections.get(t)||this._injections.set(t,[]),this._injections.get(t).push(e.scopeName)})}getInjections(e){let t=e.split(`.`),n=[];for(let e=1;e<=t.length;e++){let r=t.slice(0,e).join(`.`);n=[...n,...this._injections.get(r)||[]]}return n}},OM=0;function kM(e){OM+=1,e.warnings!==!1&&OM>=10&&OM%10==0&&console.warn(`[Shiki] ${OM} instances have been created. Shiki is supposed to be used as a singleton, consider refactoring your code to cache your highlighter instance; Or call \`highlighter.dispose()\` to release unused instances.`);let t=!1;if(!e.engine)throw new wM("`engine` option is required for synchronous mode");let n=(e.langs||[]).flat(1),r=(e.themes||[]).flat(1).map(vM),i=new EM(new DM(e.engine,n),r,n,e.langAlias),a;function o(t){return TM(t,e.langAlias)}function s(e){g();let t=i.getGrammar(typeof e==`string`?e:e.name);if(!t)throw new wM(`Language \`${e}\` not found, you may need to load it first`);return t}function c(e){if(e===`none`)return{bg:``,fg:``,name:`none`,settings:[],type:`dark`};g();let t=i.getTheme(e);if(!t)throw new wM(`Theme \`${e}\` not found, you may need to load it first`);return t}function l(e){g();let t=c(e);return a!==e&&(i.setTheme(t),a=e),{theme:t,colorMap:i.getColorMap()}}function u(){return g(),i.getLoadedThemes()}function d(){return g(),i.getLoadedLanguages()}function f(...e){g(),i.loadLanguages(e.flat(1))}async function p(...e){return f(await yM(e))}function m(...e){g();for(let t of e.flat(1))i.loadTheme(t)}async function h(...e){return g(),m(await bM(e))}function g(){if(t)throw new wM(`Shiki instance has been disposed`)}function _(){t||(t=!0,i.dispose(),--OM)}return{setTheme:l,getTheme:c,getLanguage:s,getLoadedThemes:u,getLoadedLanguages:d,resolveLangAlias:o,loadLanguage:p,loadLanguageSync:f,loadTheme:h,loadThemeSync:m,dispose:_,[Symbol.dispose]:_}}async function AM(e){e.engine||CM("`engine` option is required. Use `createOnigurumaEngine` or `createJavaScriptRegexEngine` to create an engine.");let[t,n,r]=await Promise.all([bM(e.themes||[]),yM(e.langs||[]),e.engine]);return kM({...e,themes:t,langs:n,engine:r})}async function jM(e){let t=await AM(e);return{getLastGrammarState:(...e)=>Xj(t,...e),codeToTokensBase:(e,n)=>Yj(t,e,n),codeToTokensWithThemes:(e,n)=>iM(t,e,n),codeToTokens:(e,n)=>oM(t,e,n),codeToHast:(e,n)=>cM(t,e,n),codeToHtml:(e,n)=>mM(t,e,n),getBundledLanguages:()=>({}),getBundledThemes:()=>({}),...t,getInternalContext:()=>t}}function MM(e){return e.replace(/[- _]+/g,``).toLowerCase()}var NM=t((()=>{})),PM,FM=t((()=>{PM=String.raw`\(\?(?:[:=!>A-Za-z\-]|<[=!]|\(DEFINE\))`})),IM=t((()=>{Object.freeze({DEFAULT:`DEFAULT`,CHAR_CLASS:`CHAR_CLASS`})})),LM,RM=t((()=>{FM(),IM(),new RegExp(String.raw`(?${PM})|(?\((?:\?<[^>]+>)?)|\\?.`,`gsu`),LM=String.raw`(?:[?*+]|\{\d+(?:,\d*)?\})`,new RegExp(String.raw` +\\(?: \d+ + | c[A-Za-z] + | [gk]<[^>]+> + | [pPu]\{[^\}]+\} + | u[A-Fa-f\d]{4} + | x[A-Fa-f\d]{2} + ) +| \((?: \? (?: [:=!>] + | <(?:[=!]|[^>]+>) + | [A-Za-z\-]+: + | \(DEFINE\) + ))? +| (?${LM})(?[?+]?)(?[?*+\{]?) +| \\?. +`.replace(/\s+/g,``),`gsu`)})),zM=t((()=>{RM()})),BM,VM,HM,UM=t((()=>{IM(),BM=String.raw,VM=BM`\(\?R=(?[^\)]+)\)|${BM`\\g<(?[^>&]+)&R=(?[^>]+)>`}`,HM=BM`\(\?<(?![=!])(?[^>]+)>`,BM`${HM}|(?\()(?!\?)`,new RegExp(BM`${HM}|${VM}|\(\?|\\?.`,`gsu`)}));function WM(e,t,n){return e.has(t)||e.set(t,n),e.get(t)}function GM(e,t){let n=[];for(let r=e;r<=t;r++)n.push(r);return n}function KM(e){let t=ZM(e);return[t.toLowerCase(),t]}function qM(e,t){return GM(e,t).map(e=>KM(e))}function JM(e,t,n,r){if(e.index+=t,e.input=n,r){let n=e.indices;for(let e=0;e{let n=r[e];n&&(r[e]=[n[0]+t,n[1]+t])})}}function YM(e,t){let n=new Map;for(let t of e)n.set(t,{hidden:!0});for(let[e,r]of t)for(let t of r)WM(n,t,{}).transferTo=e;return n}function XM(e){let t=/(?\((?:\?<(?![=!])(?[^>]+)>|(?!\?)))|\\?./gsu,n=new Map,r=0,i=0,a;for(;a=t.exec(e);){let{0:e,groups:{capture:t,name:o}}=a;e===`[`?r++:r?e===`]`&&r--:t&&(i++,o&&n.set(i,o))}return n}var ZM,QM,$M,X,eN=t((()=>{NM(),zM(),UM(),ZM=String.fromCodePoint,QM=String.raw,$M={flagGroups:(()=>!0)(),unicodeSets:(()=>!0)()},$M.bugFlagVLiteralHyphenIsRange=$M.unicodeSets?(()=>{try{new RegExp(QM`[\d\-a]`,`v`)}catch{return!0}return!1})():!1,$M.bugNestedClassIgnoresNegation=$M.unicodeSets&&RegExp(`[[^a]]`,`v`).test(`a`),ZM(304),ZM(305),QM`[\p{L}\p{M}\p{N}\p{Pc}]`,`C Other +Cc Control cntrl +Cf Format +Cn Unassigned +Co Private_Use +Cs Surrogate +L Letter +LC Cased_Letter +Ll Lowercase_Letter +Lm Modifier_Letter +Lo Other_Letter +Lt Titlecase_Letter +Lu Uppercase_Letter +M Mark Combining_Mark +Mc Spacing_Mark +Me Enclosing_Mark +Mn Nonspacing_Mark +N Number +Nd Decimal_Number digit +Nl Letter_Number +No Other_Number +P Punctuation punct +Pc Connector_Punctuation +Pd Dash_Punctuation +Pe Close_Punctuation +Pf Final_Punctuation +Pi Initial_Punctuation +Po Other_Punctuation +Ps Open_Punctuation +S Symbol +Sc Currency_Symbol +Sk Modifier_Symbol +Sm Math_Symbol +So Other_Symbol +Z Separator +Zl Line_Separator +Zp Paragraph_Separator +Zs Space_Separator +ASCII +ASCII_Hex_Digit AHex +Alphabetic Alpha +Any +Assigned +Bidi_Control Bidi_C +Bidi_Mirrored Bidi_M +Case_Ignorable CI +Cased +Changes_When_Casefolded CWCF +Changes_When_Casemapped CWCM +Changes_When_Lowercased CWL +Changes_When_NFKC_Casefolded CWKCF +Changes_When_Titlecased CWT +Changes_When_Uppercased CWU +Dash +Default_Ignorable_Code_Point DI +Deprecated Dep +Diacritic Dia +Emoji +Emoji_Component EComp +Emoji_Modifier EMod +Emoji_Modifier_Base EBase +Emoji_Presentation EPres +Extended_Pictographic ExtPict +Extender Ext +Grapheme_Base Gr_Base +Grapheme_Extend Gr_Ext +Hex_Digit Hex +IDS_Binary_Operator IDSB +IDS_Trinary_Operator IDST +ID_Continue IDC +ID_Start IDS +Ideographic Ideo +Join_Control Join_C +Logical_Order_Exception LOE +Lowercase Lower +Math +Noncharacter_Code_Point NChar +Pattern_Syntax Pat_Syn +Pattern_White_Space Pat_WS +Quotation_Mark QMark +Radical +Regional_Indicator RI +Sentence_Terminal STerm +Soft_Dotted SD +Terminal_Punctuation Term +Unified_Ideograph UIdeo +Uppercase Upper +Variation_Selector VS +White_Space space +XID_Continue XIDC +XID_Start XIDS`.split(/\s/).map(e=>[MM(e),e]),ZM(383),ZM(383),ZM(223),ZM(7838),ZM(107),ZM(8490),ZM(229),ZM(8491),ZM(969),ZM(8486),new Map([KM(453),KM(456),KM(459),KM(498),...qM(8072,8079),...qM(8088,8095),...qM(8104,8111),KM(8124),KM(8140),KM(8188)]),QM`[\p{Alpha}\p{Nd}]`,QM`\p{Alpha}`,QM`\p{ASCII}`,QM`[\p{Zs}\t]`,QM`\p{Cc}`,QM`\p{Nd}`,QM`[\P{space}&&\P{Cc}&&\P{Cn}&&\P{Cs}]`,QM`\p{Lower}`,QM`[[\P{space}&&\P{Cc}&&\P{Cn}&&\P{Cs}]\p{Zs}]`,QM`[\p{P}\p{S}]`,QM`\p{space}`,QM`\p{Upper}`,QM`[\p{Alpha}\p{M}\p{Nd}\p{Pc}]`,QM`\p{AHex}`,QM`\t`,QM`\n`,QM`\v`,QM`\f`,QM`\r`,QM`\u2028`,QM`\u2029`,QM`\uFEFF`,X=class e extends RegExp{#e=new Map;#t=null;#n;#r=null;#i=null;rawOptions={};get source(){return this.#n||`(?:)`}constructor(t,n,r){let i=!!r?.lazyCompile;if(t instanceof RegExp){if(r)throw Error(`Cannot provide options when copying a regexp`);let i=t;super(i,n),this.#n=i.source,i instanceof e&&(this.#e=i.#e,this.#r=i.#r,this.#i=i.#i,this.rawOptions=i.rawOptions)}else{let e={hiddenCaptures:[],strategy:null,transfers:[],...r};super(i?``:t,n),this.#n=t,this.#e=YM(e.hiddenCaptures,e.transfers),this.#i=e.strategy,this.rawOptions=r??{}}i||(this.#t=this)}exec(t){if(!this.#t){let{lazyCompile:t,...n}=this.rawOptions;this.#t=new e(this.#n,this.flags,n)}let n=this.global||this.sticky,r=this.lastIndex;if(this.#i===`clip_search`&&n&&r){this.lastIndex=0;let e=this.#a(t.slice(r));return e&&(JM(e,r,t,this.hasIndices),this.lastIndex+=r),e}return this.#a(t)}#a(e){this.#t.lastIndex=this.lastIndex;let t=super.exec.call(this.#t,e);if(this.lastIndex=this.#t.lastIndex,!t||!this.#e.size)return t;let n=[...t];t.length=1;let r;this.hasIndices&&(r=[...t.indices],t.indices.length=1);let i=[0];for(let e=1;e{if(typeof e!=`string`)return e;let t=r?.get(e);if(t){if(t instanceof RegExp)return t;if(n)return null;throw t}try{let t=i(e);return r?.set(e,t),t}catch(t){if(r?.set(e,t),n)return null;throw t}})}regexps;findNextMatchSync(e,t,n){let r=typeof e==`string`?e:e.content,i=[];function a(e,t,n=0){return{index:e,captureIndices:t.indices.map(e=>e==null?{start:tN,end:tN,length:0}:{start:e[0]+n,end:e[1]+n,length:e[1]-e[0]})}}for(let e=0;ee[1].index));for(let[t,n,r]of i)if(n.index===e)return a(t,n,r)}return null}};function rN(){let e={cache:new Map,regexConstructor:()=>{throw Error(`JavaScriptRawEngine: only support precompiled grammar`)}};return{createScanner(t){return new nN(t,e)},createString(e){return{content:e}}}}var iN=`modulepreload`,aN=function(e,t){return new URL(e,t).href},oN={};const sN=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=aN(t,n),t in oN)return;oN[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:iN,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};var cN,lN,uN=t((()=>{eN(),cN=Object.freeze({displayName:`JavaScript`,name:`javascript`,patterns:[{include:`#directives`},{include:`#statements`},{include:`#shebang`}],repository:{"access-modifier":{match:RegExp(`(?\\?\\[]|^await|[^\\$\\._\\p{Alpha}\\p{Nd}]await|^return|[^\\$\\._\\p{Alpha}\\p{Nd}]return|^yield|[^\\$\\._\\p{Alpha}\\p{Nd}]yield|^throw|[^\\$\\._\\p{Alpha}\\p{Nd}]throw|^in|[^\\$\\._\\p{Alpha}\\p{Nd}]in|^of|[^\\$\\._\\p{Alpha}\\p{Nd}]of|^typeof|[^\\$\\._\\p{Alpha}\\p{Nd}]typeof|&&|\\|\\||\\*)\\p{space}*(\\{)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.block.js`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.block.js`}},name:`meta.objectliteral.js`,patterns:[{include:`#object-member`}]},"array-binding-pattern":{begin:RegExp(`(?:(\\.\\.\\.)\\p{space}*)?(\\[)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.rest.js`},2:{name:`punctuation.definition.binding-pattern.array.js`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`punctuation.definition.binding-pattern.array.js`}},patterns:[{include:`#binding-element`},{include:`#punctuation-comma`}]},"array-binding-pattern-const":{begin:RegExp(`(?:(\\.\\.\\.)\\p{space}*)?(\\[)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.rest.js`},2:{name:`punctuation.definition.binding-pattern.array.js`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`punctuation.definition.binding-pattern.array.js`}},patterns:[{include:`#binding-element-const`},{include:`#punctuation-comma`}]},"array-literal":{begin:RegExp(`\\p{space}*(\\[)`,`dgv`),beginCaptures:{1:{name:`meta.brace.square.js`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`meta.brace.square.js`}},name:`meta.array.literal.js`,patterns:[{include:`#expression`},{include:`#punctuation-comma`}]},"arrow-function":{patterns:[{captures:{1:{name:`storage.modifier.async.js`},2:{name:`variable.parameter.js`}},match:RegExp(`(?:(?)`,`dgv`),name:`meta.arrow.js`},{begin:RegExp(`(?:(?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`}},end:RegExp(`(?==>|\\{|^(\\p{space}*(export|function|class|interface|let|var|\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b|\\bawait\\p{space}+\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b\\b|const|import|enum|namespace|module|type|abstract|declare)\\p{space}+))`,`dgv`),name:`meta.arrow.js`,patterns:[{include:`#comment`},{include:`#type-parameters`},{include:`#function-parameters`},{include:`#arrow-return-type`},{include:`#possibly-arrow-return-type`}]},{begin:RegExp(`=>`,`dgv`),beginCaptures:{0:{name:`storage.type.function.arrow.js`}},end:RegExp(`((?<=[\\}\\P{space}])(?)|((?!\\{)(?=\\P{space})))(?!\\/[\\*\\/])`,`dgv`),name:`meta.arrow.js`,patterns:[{include:`#single-line-comment-consuming-line-ending`},{include:`#decl-block`},{include:`#expression`}]}]},"arrow-return-type":{begin:RegExp(`(?<=\\))\\p{space}*(:)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.type.annotation.js`}},end:RegExp(`(?==>|\\{|^(\\p{space}*(export|function|class|interface|let|var|\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b|\\bawait\\p{space}+\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b\\b|const|import|enum|namespace|module|type|abstract|declare)\\p{space}+))`,`dgv`),name:`meta.return.type.arrow.js`,patterns:[{include:`#arrow-return-type-body`}]},"arrow-return-type-body":{patterns:[{begin:RegExp(`(?<=:)(?=\\p{space}*\\{)`,`dgv`),end:RegExp(`(?<=\\})`,`dgv`),patterns:[{include:`#type-object`}]},{include:`#type-predicate-operator`},{include:`#type`}]},"async-modifier":{match:RegExp(`(?\\p{space}*(?=\\n?$))`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.comment.js`}},end:RegExp(`(?=(?=\\n?$))`,`dgv`),name:`comment.line.triple-slash.directive.js`,patterns:[{begin:RegExp(`(<)(reference|amd-dependency|amd-module)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.directive.js`},2:{name:`entity.name.tag.directive.js`}},end:RegExp(`\\/>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.directive.js`}},name:`meta.tag.js`,patterns:[{match:RegExp(`path|types|no-default-lib|lib|name|resolution-mode`,`dgv`),name:`entity.other.attribute-name.directive.js`},{match:RegExp(`=`,`dgv`),name:`keyword.operator.assignment.js`},{include:`#string`}]}]},docblock:{patterns:[{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`constant.language.access-type.jsdoc`}},match:RegExp(`((@)a(?:ccess|pi))\\p{space}+(p(?:rivate|rotected|ublic))\\b`,`dgv`)},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`entity.name.type.instance.jsdoc`},4:{name:`punctuation.definition.bracket.angle.begin.jsdoc`},5:{name:`constant.other.email.link.underline.jsdoc`},6:{name:`punctuation.definition.bracket.angle.end.jsdoc`}},match:RegExp(`((@)author)\\p{space}+([^\\*\\/\\<\\>\\@\\p{space}](?:[^\\*\\/\\<\\>\\@]|\\*[^\\/])*)(?:\\p{space}*(<)([^\\>\\p{space}]+)(>))?`,`dgv`)},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`entity.name.type.instance.jsdoc`},4:{name:`keyword.operator.control.jsdoc`},5:{name:`entity.name.type.instance.jsdoc`}},match:RegExp(`((@)borrows)\\p{space}+((?:[^\\*\\/\\@\\p{space}]|\\*[^\\/])+)\\p{space}+(as)\\p{space}+((?:[^\\*\\/\\@\\p{space}]|\\*[^\\/])+)`,`dgv`)},{begin:RegExp(`((@)example)\\p{space}+`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},end:RegExp(`(?=@|\\*\\/)`,`dgv`),name:`meta.example.jsdoc`,patterns:[{match:RegExp(`^\\p{space}\\*\\p{space}+`,`dgv`)},{begin:RegExp(`(<)caption(>)`,`dgvy`),beginCaptures:{0:{name:`entity.name.tag.inline.jsdoc`},1:{name:`punctuation.definition.bracket.angle.begin.jsdoc`},2:{name:`punctuation.definition.bracket.angle.end.jsdoc`}},contentName:`constant.other.description.jsdoc`,end:RegExp(`(<\\/)caption(>)|(?=\\*\\/)`,`dgv`),endCaptures:{0:{name:`entity.name.tag.inline.jsdoc`},1:{name:`punctuation.definition.bracket.angle.begin.jsdoc`},2:{name:`punctuation.definition.bracket.angle.end.jsdoc`}}},{captures:{0:{name:`source.embedded.js`}},match:RegExp(`[^\\*\\@\\p{space}](?:[^\\*]|\\*[^\\/])*`,`dgv`)}]},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`constant.language.symbol-type.jsdoc`}},match:RegExp(`((@)kind)\\p{space}+(class|constant|event|external|file|function|member|mixin|module|namespace|typedef)\\b`,`dgv`)},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`variable.other.link.underline.jsdoc`},4:{name:`entity.name.type.instance.jsdoc`}},match:RegExp(`((@)see)\\p{space}+(?:((?=https?:\\/\\/)(?:[^\\*\\p{space}]|\\*[^\\/])+)|((?!https?:\\/\\/|(?:\\[[^\\]\\[]*\\])?\\{@(?:link|linkcode|linkplain|tutorial)\\b)(?:[^\\*\\/\\@\\p{space}]|\\*[^\\/])+))`,`dgv`)},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`variable.other.jsdoc`}},match:RegExp(`((@)template)\\p{space}+([\\$A-Z_a-z][\\]\\$\\.\\[\\p{L}\\p{M}\\p{N}\\p{Pc}]*(?:\\p{space}*,\\p{space}*[\\$A-Z_a-z][\\]\\$\\.\\[\\p{L}\\p{M}\\p{N}\\p{Pc}]*)*)`,`dgv`)},{begin:RegExp(`((@)template)\\p{space}+(?=\\{)`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},end:RegExp(`(?=\\p{space}|\\*\\/|[^\\]\\$A-\\[_a-\\{\\}])`,`dgv`),patterns:[{include:`#jsdoctype`},{match:RegExp(`([\\$A-Z_a-z][\\]\\$\\.\\[\\p{L}\\p{M}\\p{N}\\p{Pc}]*)`,`dgv`),name:`variable.other.jsdoc`}]},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`variable.other.jsdoc`}},match:RegExp(`((@)(?:arg|argument|const|constant|member|namespace|param|var))\\p{space}+([\\$A-Z_a-z][\\]\\$\\.\\[\\p{L}\\p{M}\\p{N}\\p{Pc}]*)`,`dgv`)},{begin:RegExp(`((@)typedef)\\p{space}+(?=\\{)`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},end:RegExp(`(?=\\p{space}|\\*\\/|[^\\]\\$A-\\[_a-\\{\\}])`,`dgv`),patterns:[{include:`#jsdoctype`},{match:RegExp(`(?:[^\\*\\/\\@\\p{space}]|\\*[^\\/])+`,`dgv`),name:`entity.name.type.instance.jsdoc`}]},{begin:RegExp(`((@)(?:arg|argument|const|constant|member|namespace|param|prop|property|var))\\p{space}+(?=\\{)`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},end:RegExp(`(?=\\p{space}|\\*\\/|[^\\]\\$A-\\[_a-\\{\\}])`,`dgv`),patterns:[{include:`#jsdoctype`},{match:RegExp(`([\\$A-Z_a-z][\\]\\$\\.\\[\\p{L}\\p{M}\\p{N}\\p{Pc}]*)`,`dgv`),name:`variable.other.jsdoc`},{captures:{1:{name:`punctuation.definition.optional-value.begin.bracket.square.jsdoc`},2:{name:`keyword.operator.assignment.jsdoc`},3:{name:`source.embedded.js`},4:{name:`punctuation.definition.optional-value.end.bracket.square.jsdoc`},5:{name:`invalid.illegal.syntax.jsdoc`}},match:new X(`(\\[)\\p{space}*[\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]+(?:(?:\\[\\])?\\.[\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]+)*(?:\\p{space}*(=)\\p{space}*((?:(?=("(?:\\*(?!/)|\\\\(?!")|[^\\*\\\\])*?"|'(?:\\*(?!/)|\\\\(?!')|[^\\*\\\\])*?'|\\[(?:\\*(?!/)|[^\\*])*?\\]|(?:\\*(?!/)|\\p{space}(?!\\p{space}*\\])|\\[[^\\n]*?(?:\\]|(?=\\*/))|[^\\]\\*\\[\\p{space}])*))\\4)*))?\\p{space}*(?:(\\])((?:[^\\*\\p{space}]|\\*[^\\/\\p{space}])+)?|(?=\\*/))`,`dgv`,{hiddenCaptures:[4]}),name:`variable.other.jsdoc`}]},{begin:RegExp(`((@)(?:define|enum|exception|export|extends|lends|implements|modifies|namespace|private|protected|returns?|satisfies|suppress|this|throws|type|yields?))\\p{space}+(?=\\{)`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},end:RegExp(`(?=\\p{space}|\\*\\/|[^\\]\\$A-\\[_a-\\{\\}])`,`dgv`),patterns:[{include:`#jsdoctype`}]},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`entity.name.type.instance.jsdoc`}},match:RegExp(`((@)(?:alias|augments|callback|constructs|emits|event|fires|exports?|extends|external|function|func|host|lends|listens|interface|memberof!?|method|module|mixes|mixin|name|requires|see|this|typedef|uses))\\p{space}+((?:[^\\*\\@\\{\\}\\p{space}]|\\*[^\\/])+)`,`dgv`)},{begin:RegExp(`((@)(?:default(?:value)?|license|version))\\p{space}+((["']))`,`dgv`),beginCaptures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`variable.other.jsdoc`},4:{name:`punctuation.definition.string.begin.jsdoc`}},contentName:`variable.other.jsdoc`,end:RegExp(`(\\3)|(?=(?=\\n?$)|\\*\\/)()()`,`dgv`),endCaptures:{0:{name:`variable.other.jsdoc`},1:{name:`punctuation.definition.string.end.jsdoc`}}},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`},3:{name:`variable.other.jsdoc`}},match:RegExp(`((@)(?:default(?:value)?|license|tutorial|variation|version))\\p{space}+([^\\*\\p{space}]+)`,`dgv`)},{captures:{1:{name:`punctuation.definition.block.tag.jsdoc`}},match:RegExp(`(@)(?:abstract|access|alias|api|arg|argument|async|attribute|augments|author|beta|borrows|bubbles|callback|chainable|class|classdesc|code|config|const|constant|constructor|constructs|copyright|default|defaultvalue|define|deprecated|desc|description|dict|emits|enum|event|example|exception|exports?|extends|extension(?:_?for)?|external|externs|file|fileoverview|final|fires|for|func|function|generator|global|hideconstructor|host|ignore|implements|implicitCast|inherit[Dd]oc|inner|instance|interface|internal|kind|lends|license|listens|main|member|memberof!?|method|mixes|mixins?|modifies|module|name|namespace|noalias|nocollapse|nocompile|nosideeffects|override|overview|package|param|polymer(?:Behavior)?|preserve|private|prop|property|protected|public|read[Oo]nly|record|require[ds]|returns?|see|since|static|struct|submodule|summary|suppress|template|this|throws|todo|tutorial|type|typedef|unrestricted|uses|var|variation|version|virtual|writeOnce|yields?)\\b`,`dgv`),name:`storage.type.class.jsdoc`},{include:`#inline-tags`},{captures:{1:{name:`storage.type.class.jsdoc`},2:{name:`punctuation.definition.block.tag.jsdoc`}},match:RegExp(`((@)[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(?=\\p{space}+)`,`dgv`)}]},"enum-declaration":{begin:RegExp(`(?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))|(:\\p{space}*((<)|(\\(\\p{space}*((\\))|(\\.\\.\\.)|([\\$_\\p{Alpha}\\p{Nd}]+\\p{space}*(([\\,\\:\\=\\?])|(\\)\\p{space}*=>)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))))|(:\\p{space}*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^\\<\\>]*>)|[^\\(\\)\\,\\<\\=\\>])+=\\p{space}*(((async\\p{space}+)?((function\\p{space}*[\\(\\*\\<])|(function\\p{space}+)|([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*=>)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>))))))`,`dgv`,{lazyCompile:!0})},{captures:{1:{name:`storage.modifier.js`},2:{name:`keyword.operator.rest.js`},3:{name:`variable.parameter.js variable.language.this.js`},4:{name:`variable.parameter.js`},5:{name:`keyword.operator.optional.js`}},match:RegExp(`(?:(?\\?\\}]|\\|\\||&&|!==|(?=\\n?$)|((?>>??|\\|)=`,`dgv`),name:`keyword.operator.assignment.compound.bitwise.js`},{match:RegExp(`<<|>>>?`,`dgv`),name:`keyword.operator.bitwise.shift.js`},{match:RegExp(`[\\!\\=]==?`,`dgv`),name:`keyword.operator.comparison.js`},{match:RegExp(`<=|>=|<>|[\\<\\>]`,`dgv`),name:`keyword.operator.relational.js`},{captures:{1:{name:`keyword.operator.logical.js`},2:{name:`keyword.operator.assignment.compound.js`},3:{name:`keyword.operator.arithmetic.js`}},match:RegExp(`(?<=[\\$_\\p{Alpha}\\p{Nd}])(!)\\p{space}*(?:(\\/=)|(\\/)(?![\\*\\/]))`,`dgv`)},{match:RegExp(`!|&&|\\|\\||\\?\\?`,`dgv`),name:`keyword.operator.logical.js`},{match:RegExp(`[\\&\\^\\|\\~]`,`dgv`),name:`keyword.operator.bitwise.js`},{match:RegExp(`=`,`dgv`),name:`keyword.operator.assignment.js`},{match:RegExp(`--`,`dgv`),name:`keyword.operator.decrement.js`},{match:RegExp(`\\+\\+`,`dgv`),name:`keyword.operator.increment.js`},{match:RegExp(`[\\-\\%\\*\\+\\/]`,`dgv`),name:`keyword.operator.arithmetic.js`},{begin:RegExp(`(?<=[\\]\\$\\)_\\p{Alpha}\\p{Nd}])\\p{space}*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)+(?:(\\/=)|(\\/)(?![\\*\\/])))`,`dgv`),end:RegExp(`(\\/=)|(\\/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)`,`dgv`),endCaptures:{1:{name:`keyword.operator.assignment.compound.js`},2:{name:`keyword.operator.arithmetic.js`}},patterns:[{include:`#comment`}]},{captures:{1:{name:`keyword.operator.assignment.compound.js`},2:{name:`keyword.operator.arithmetic.js`}},match:RegExp(`(?<=[\\]\\$\\)_\\p{Alpha}\\p{Nd}])\\p{space}*(?:(\\/=)|(\\/)(?![\\*\\/]))`,`dgv`)}]},expressionPunctuations:{patterns:[{include:`#punctuation-comma`},{include:`#punctuation-accessor`}]},expressionWithoutIdentifiers:{patterns:[{include:`#jsx`},{include:`#string`},{include:`#regex`},{include:`#comment`},{include:`#function-expression`},{include:`#class-expression`},{include:`#arrow-function`},{include:`#paren-expression-possibly-arrow`},{include:`#cast`},{include:`#ternary-expression`},{include:`#new-expr`},{include:`#instanceof-expr`},{include:`#object-literal`},{include:`#expression-operators`},{include:`#function-call`},{include:`#literal`},{include:`#support-objects`},{include:`#paren-expression`}]},"field-declaration":{begin:RegExp(`(?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))|(:\\p{space}*((<)|(\\(\\p{space}*((\\))|(\\.\\.\\.)|([\\$_\\p{Alpha}\\p{Nd}]+\\p{space}*(([\\,\\:\\=\\?])|(\\)\\p{space}*=>)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))))|(:\\p{space}*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^\\<\\>]*>)|[^\\(\\)\\,\\<\\=\\>])+=\\p{space}*(((async\\p{space}+)?((function\\p{space}*[\\(\\*\\<])|(function\\p{space}+)|([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*=>)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>))))))`,`dgv`,{lazyCompile:!0})},{match:RegExp(`#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*`,`dgv`),name:`meta.definition.property.js variable.object.property.js`},{match:RegExp(`\\?`,`dgv`),name:`keyword.operator.optional.js`},{match:RegExp(`!`,`dgv`),name:`keyword.operator.definiteassignment.js`}]},"for-loop":{begin:RegExp(`(?\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)?\\())`,`dgv`),end:RegExp(`(?<=\\))(?!(((([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(\\p{space}*\\??\\.\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))*)|(\\??\\.\\p{space}*#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))|(?<=\\)))\\p{space}*(?:(\\?\\.\\p{space}*)|(!))?((<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)?\\())`,`dgv`),patterns:[{begin:RegExp(`(?=(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(\\p{space}*\\??\\.\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))*)|(\\??\\.\\p{space}*#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))`,`dgv`),end:RegExp(`(?=\\p{space}*(?:(\\?\\.\\p{space}*)|(!))?((<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)?\\())`,`dgv`),name:`meta.function-call.js`,patterns:[{include:`#function-call-target`}]},{include:`#comment`},{include:`#function-call-optionals`},{include:`#type-arguments`},{include:`#paren-expression`}]},{begin:RegExp(`(?=(((([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(\\p{space}*\\??\\.\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))*)|(\\??\\.\\p{space}*#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))|(?<=\\)))(<\\p{space}*[\\(\\[\\{]\\p{space}*)(?=\\n?$))`,`dgv`),end:RegExp(`(?<=>)(?!(((([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(\\p{space}*\\??\\.\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))*)|(\\??\\.\\p{space}*#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))|(?<=\\)))(<\\p{space}*[\\(\\[\\{]\\p{space}*)(?=\\n?$))`,`dgv`),patterns:[{begin:RegExp(`(?=(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)(\\p{space}*\\??\\.\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))*)|(\\??\\.\\p{space}*#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))`,`dgv`),end:RegExp(`(?=(<\\p{space}*[\\(\\[\\{]\\p{space}*)(?=\\n?$))`,`dgv`),name:`meta.function-call.js`,patterns:[{include:`#function-call-target`}]},{include:`#comment`},{include:`#function-call-optionals`},{include:`#type-arguments`}]}]},"function-call-optionals":{patterns:[{match:RegExp(`\\?\\.`,`dgv`),name:`meta.function-call.js punctuation.accessor.optional.js`},{match:RegExp(`!`,`dgv`),name:`meta.function-call.js keyword.operator.definiteassignment.js`}]},"function-call-target":{patterns:[{include:`#support-function-call-identifiers`},{match:RegExp(`(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)`,`dgv`),name:`entity.name.function.js`}]},"function-declaration":{begin:RegExp(`(?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))`,`dgv`)},{captures:{1:{name:`punctuation.accessor.js`},2:{name:`punctuation.accessor.optional.js`},3:{name:`variable.other.constant.property.js`}},match:RegExp(`(?:(\\.)|(\\?\\.(?!\\p{space}*\\p{Nd})))\\p{space}*(#?\\p{Upper}[\\$_\\p{Nd}\\p{Upper}]*)(?![\\$_\\p{Alpha}\\p{Nd}])`,`dgv`)},{captures:{1:{name:`punctuation.accessor.js`},2:{name:`punctuation.accessor.optional.js`},3:{name:`variable.other.property.js`}},match:RegExp(`(?:(\\.)|(\\?\\.(?!\\p{space}*\\p{Nd})))\\p{space}*(#?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)`,`dgv`)},{match:RegExp(`(\\p{Upper}[\\$_\\p{Nd}\\p{Upper}]*)(?![\\$_\\p{Alpha}\\p{Nd}])`,`dgv`),name:`variable.other.constant.js`},{match:RegExp(`[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*`,`dgv`),name:`variable.other.readwrite.js`}]},"if-statement":{patterns:[{begin:RegExp(`(?\\?\\}]|\\|\\||&&|!==|(?=\\n?$)|([\\!\\=]==?)|(([\\&\\^\\|\\~]\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+instanceof(?![\\$_\\p{Alpha}\\p{Nd}])(?:(?=\\.\\.\\.)|(?!\\.)))|((?))`,`dgv`),end:RegExp(`(\\/>)|(<\\/)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.end.js`},2:{name:`punctuation.definition.tag.begin.js`},3:{name:`entity.name.tag.namespace.js`},4:{name:`punctuation.separator.namespace.js`},5:{name:`entity.name.tag.js`},6:{name:`support.class.component.js`},7:{name:`punctuation.definition.tag.end.js`}},name:`meta.tag.js`,patterns:[{begin:RegExp(`(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.js`},2:{name:`entity.name.tag.namespace.js`},3:{name:`punctuation.separator.namespace.js`},4:{name:`entity.name.tag.js`},5:{name:`support.class.component.js`}},end:RegExp(`(?=\\/?>)`,`dgv`),patterns:[{include:`#comment`},{include:`#type-arguments`},{include:`#jsx-tag-attributes`}]},{begin:RegExp(`(>)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.end.js`}},contentName:`meta.jsx.children.js`,end:RegExp(`(?=<\\/)`,`dgv`),patterns:[{include:`#jsx-children`}]}]},"jsx-tag-attribute-assignment":{match:RegExp(`=(?=\\p{space}*(?:["'\\{]|\\/\\*|\\/\\/|\\n))`,`dgv`),name:`keyword.operator.assignment.js`},"jsx-tag-attribute-name":{captures:{1:{name:`entity.other.attribute-name.namespace.js`},2:{name:`punctuation.separator.namespace.js`},3:{name:`entity.other.attribute-name.js`}},match:RegExp(`\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(:))?([\\$_\\p{Alpha}][\\-\\$_\\p{Alpha}\\p{Nd}]*)(?=[\\=\\p{space}]|\\/?>|\\/\\*|\\/\\/)`,`dgv`)},"jsx-tag-attributes":{begin:RegExp(`\\p{space}+`,`dgv`),end:RegExp(`(?=\\/?>)`,`dgv`),name:`meta.tag.attributes.js`,patterns:[{include:`#comment`},{include:`#jsx-tag-attribute-name`},{include:`#jsx-tag-attribute-assignment`},{include:`#jsx-string-double-quoted`},{include:`#jsx-string-single-quoted`},{include:`#jsx-evaluated-code`},{include:`#jsx-tag-attributes-illegal`}]},"jsx-tag-attributes-illegal":{match:RegExp(`\\P{space}+`,`dgv`),name:`invalid.illegal.attribute.js`},"jsx-tag-in-expression":{begin:RegExp(`(?\\?\\[\\{]|&&|\\|\\||\\?|\\*\\/|^await|[^\\$\\._\\p{Alpha}\\p{Nd}]await|^return|[^\\$\\._\\p{Alpha}\\p{Nd}]return|^default|[^\\$\\._\\p{Alpha}\\p{Nd}]default|^yield|[^\\$\\._\\p{Alpha}\\p{Nd}]yield|^)\\p{space}*(?!<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*((\\p{space}+extends\\p{space}+[^\\=\\>])|,))(?=(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?))`,`dgv`),end:RegExp(`(?!(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?))`,`dgv`),patterns:[{include:`#jsx-tag`}]},"jsx-tag-without-attributes":{begin:RegExp(`(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.js`},2:{name:`entity.name.tag.namespace.js`},3:{name:`punctuation.separator.namespace.js`},4:{name:`entity.name.tag.js`},5:{name:`support.class.component.js`},6:{name:`punctuation.definition.tag.end.js`}},contentName:`meta.jsx.children.js`,end:RegExp(`(<\\/)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.begin.js`},2:{name:`entity.name.tag.namespace.js`},3:{name:`punctuation.separator.namespace.js`},4:{name:`entity.name.tag.js`},5:{name:`support.class.component.js`},6:{name:`punctuation.definition.tag.end.js`}},name:`meta.tag.without-attributes.js`,patterns:[{include:`#jsx-children`}]},"jsx-tag-without-attributes-in-expression":{begin:RegExp(`(?\\?\\[\\{]|&&|\\|\\||\\?|\\*\\/|^await|[^\\$\\._\\p{Alpha}\\p{Nd}]await|^return|[^\\$\\._\\p{Alpha}\\p{Nd}]return|^default|[^\\$\\._\\p{Alpha}\\p{Nd}]default|^yield|[^\\$\\._\\p{Alpha}\\p{Nd}]yield|^)\\p{space}*(?=(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?))`,`dgv`),end:RegExp(`(?!(<)\\p{space}*(?:([\\$_\\p{Alpha}][\\-\\$\\._\\p{Alpha}\\p{Nd}]*)(?))`,`dgv`),patterns:[{include:`#jsx-tag-without-attributes`}]},label:{patterns:[{begin:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)\\p{space}*(:)(?=\\p{space}*\\{)`,`dgv`),beginCaptures:{1:{name:`entity.name.label.js`},2:{name:`punctuation.separator.label.js`}},end:RegExp(`(?<=\\})`,`dgv`),patterns:[{include:`#decl-block`}]},{captures:{1:{name:`entity.name.label.js`},2:{name:`punctuation.separator.label.js`}},match:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)\\p{space}*(:)`,`dgv`)}]},literal:{patterns:[{include:`#numeric-literal`},{include:`#boolean-literal`},{include:`#null-literal`},{include:`#undefined-literal`},{include:`#numericConstant-literal`},{include:`#array-literal`},{include:`#this-literal`},{include:`#super-literal`}]},"method-declaration":{patterns:[{begin:RegExp(`(?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.js`},2:{name:`storage.modifier.js`},3:{name:`storage.modifier.js`},4:{name:`storage.modifier.async.js`},5:{name:`keyword.operator.new.js`},6:{name:`keyword.generator.asterisk.js`}},end:RegExp(`(?=[\\,\\;\\}]|(?=\\n?$))|(?<=\\})`,`dgv`),name:`meta.method.declaration.js`,patterns:[{include:`#method-declaration-name`},{include:`#function-body`}]},{begin:RegExp(`(?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.js`},2:{name:`storage.modifier.js`},3:{name:`storage.modifier.js`},4:{name:`storage.modifier.async.js`},5:{name:`storage.type.property.js`},6:{name:`keyword.generator.asterisk.js`}},end:RegExp(`(?=[\\,\\;\\}]|(?=\\n?$))|(?<=\\})`,`dgv`),name:`meta.method.declaration.js`,patterns:[{include:`#method-declaration-name`},{include:`#function-body`}]}]},"method-declaration-name":{begin:RegExp(`(?=(\\b((?\\?\\}]|\\|\\||&&|!==|(?=\\n?$)|((?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`},2:{name:`storage.type.property.js`},3:{name:`keyword.generator.asterisk.js`}},end:RegExp(`(?=[\\,\\;\\}])|(?<=\\})`,`dgv`),name:`meta.method.declaration.js`,patterns:[{include:`#method-declaration-name`},{include:`#function-body`},{begin:RegExp(`(?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`},2:{name:`storage.type.property.js`},3:{name:`keyword.generator.asterisk.js`}},end:RegExp(`(?=[\\(\\<])`,`dgv`),patterns:[{include:`#method-declaration-name`}]}]},"object-member":{patterns:[{include:`#comment`},{include:`#object-literal-method-declaration`},{begin:RegExp(`(?=\\[)`,`dgv`),end:RegExp(`(?=:)|((?<=\\])(?=\\p{space}*[\\(\\<]))`,`dgv`),name:`meta.object.member.js meta.object-literal.key.js`,patterns:[{include:`#comment`},{include:`#array-literal`}]},{begin:RegExp(`(?=["'\\\`])`,`dgv`),end:RegExp(`(?=:)|((?<=["'\\\`])(?=((\\p{space}*[\\(\\,\\<\\}])|(\\p{space}+(as|satisifies)\\p{space}+))))`,`dgv`),name:`meta.object.member.js meta.object-literal.key.js`,patterns:[{include:`#comment`},{include:`#string`}]},{begin:RegExp(`(?=\\b((?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))`,`dgv`),name:`meta.object.member.js`},{captures:{0:{name:`meta.object-literal.key.js`}},match:RegExp(`[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\p{space}*)*:)`,`dgv`),name:`meta.object.member.js`},{begin:RegExp(`\\.\\.\\.`,`dgv`),beginCaptures:{0:{name:`keyword.operator.spread.js`}},end:RegExp(`(?=[\\,\\}])`,`dgv`),name:`meta.object.member.js`,patterns:[{include:`#expression`}]},{captures:{1:{name:`variable.other.readwrite.js`}},match:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)\\p{space}*(?=[\\,\\}]|(?=\\n?$)|\\/\\/|\\/\\*)`,`dgv`),name:`meta.object.member.js`},{captures:{1:{name:`keyword.control.as.js`},2:{name:`storage.modifier.js`}},match:RegExp(`(?\\?\\}]|\\|\\||&&|!==|(?=\\n?$)|^|((?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`}},end:RegExp(`(?<=\\))`,`dgv`),patterns:[{include:`#type-parameters`},{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},patterns:[{include:`#expression-inside-possibly-arrow-parens`}]}]},{begin:RegExp(`(?<=:)\\p{space}*(async)?\\p{space}*(\\()(?=\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`},2:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},patterns:[{include:`#expression-inside-possibly-arrow-parens`}]},{begin:RegExp(`(?<=:)\\p{space}*(async)?\\p{space}*(?=<\\p{space}*(?=\\n?$))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`}},end:RegExp(`(?<=>)`,`dgv`),patterns:[{include:`#type-parameters`}]},{begin:RegExp(`(?<=>)\\p{space}*(\\()(?=\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))`,`dgv`),beginCaptures:{1:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},patterns:[{include:`#expression-inside-possibly-arrow-parens`}]},{include:`#possibly-arrow-return-type`},{include:`#expression`}]},{include:`#punctuation-comma`},{include:`#decl-block`}]},"parameter-array-binding-pattern":{begin:RegExp(`(?:(\\.\\.\\.)\\p{space}*)?(\\[)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.rest.js`},2:{name:`punctuation.definition.binding-pattern.array.js`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`punctuation.definition.binding-pattern.array.js`}},patterns:[{include:`#parameter-binding-element`},{include:`#punctuation-comma`}]},"parameter-binding-element":{patterns:[{include:`#comment`},{include:`#string`},{include:`#numeric-literal`},{include:`#regex`},{include:`#parameter-object-binding-pattern`},{include:`#parameter-array-binding-pattern`},{include:`#destructuring-parameter-rest`},{include:`#variable-initializer`}]},"parameter-name":{patterns:[{captures:{1:{name:`storage.modifier.js`}},match:RegExp(`(?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))|(:\\p{space}*((<)|(\\(\\p{space}*((\\))|(\\.\\.\\.)|([\\$_\\p{Alpha}\\p{Nd}]+\\p{space}*(([\\,\\:\\=\\?])|(\\)\\p{space}*=>)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))))|(:\\p{space}*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^\\<\\>]*>)|[^\\(\\)\\,\\<\\=\\>])+=\\p{space}*(((async\\p{space}+)?((function\\p{space}*[\\(\\*\\<])|(function\\p{space}+)|([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*=>)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>))))))`,`dgv`,{lazyCompile:!0})},{captures:{1:{name:`storage.modifier.js`},2:{name:`keyword.operator.rest.js`},3:{name:`variable.parameter.js variable.language.this.js`},4:{name:`variable.parameter.js`},5:{name:`keyword.operator.optional.js`}},match:RegExp(`(?:(?])`,`dgv`),name:`meta.type.annotation.js`,patterns:[{include:`#type`}]}]},"paren-expression":{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},patterns:[{include:`#expression`}]},"paren-expression-possibly-arrow":{patterns:[{begin:RegExp(`(?<=[\\(\\,\\=])\\p{space}*(async)?(?=\\p{space}*((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`}},end:RegExp(`(?<=\\))`,`dgv`),patterns:[{include:`#paren-expression-possibly-arrow-with-typeparameters`}]},{begin:RegExp(`(?<=[\\(\\,\\=]|=>|^return|[^\\$\\._\\p{Alpha}\\p{Nd}]return)\\p{space}*(async)?(?=\\p{space}*((((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*))?\\()|(<)|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)))\\p{space}*(?=\\n?$))`,`dgv`),beginCaptures:{1:{name:`storage.modifier.async.js`}},end:RegExp(`(?<=\\))`,`dgv`),patterns:[{include:`#paren-expression-possibly-arrow-with-typeparameters`}]},{include:`#possibly-arrow-return-type`}]},"paren-expression-possibly-arrow-with-typeparameters":{patterns:[{include:`#type-parameters`},{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},patterns:[{include:`#expression-inside-possibly-arrow-parens`}]}]},"possibly-arrow-return-type":{begin:RegExp(`(?<=\\)|^)\\p{space}*(:)(?=\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*=>)`,`dgv`),beginCaptures:{1:{name:`meta.arrow.js meta.return.type.arrow.js keyword.operator.type.annotation.js`}},contentName:`meta.arrow.js meta.return.type.arrow.js`,end:RegExp(`(?==>|\\{|^(\\p{space}*(export|function|class|interface|let|var|\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b|\\bawait\\p{space}+\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b\\b|const|import|enum|namespace|module|type|abstract|declare)\\p{space}+))`,`dgv`),patterns:[{include:`#arrow-return-type-body`}]},"property-accessor":{match:RegExp(`(?|&&|\\|\\||\\*\\/)\\p{space}*(\\/)(?![\\*\\/])(?=(?:[^\\(\\)\\/\\[\\\\]|\\\\[^\\n]|\\[([^\\]\\\\]|\\\\[^\\n])+\\]|\\(([^\\)\\\\]|\\\\[^\\n])+\\))+\\/([dgimsuvy]+|(?![\\*\\/])|(?=\\/\\*))(?!\\p{space}*[\\$0-9A-Z_a-z]))`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.string.begin.js`}},end:RegExp(`(\\/)([dgimsuvy]*)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.string.end.js`},2:{name:`keyword.other.js`}},name:`string.regexp.js`,patterns:[{include:`#regexp`}]},{begin:RegExp(`((?)`,`dgv`)},{match:RegExp(`[\\*\\+\\?]|\\{(\\p{Nd}+,\\p{Nd}+|\\p{Nd}+,|,\\p{Nd}+|\\p{Nd}+)\\}\\??`,`dgv`),name:`keyword.operator.quantifier.regexp`},{match:RegExp(`\\|`,`dgv`),name:`keyword.operator.or.regexp`},{begin:RegExp(`(\\()((\\?=)|(\\?!)|(\\?<=)|(\\?)?`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.group.regexp`},1:{name:`punctuation.definition.group.no-capture.regexp`},2:{name:`variable.other.regexp`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.group.regexp`}},name:`meta.group.regexp`,patterns:[{include:`#regexp`}]},{begin:RegExp(`(\\[)(\\^)?`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.character-class.regexp`},2:{name:`keyword.operator.negation.regexp`}},end:RegExp(`(\\])`,`dgv`),endCaptures:{1:{name:`punctuation.definition.character-class.regexp`}},name:`constant.other.character-class.set.regexp`,patterns:[{captures:{1:{name:`constant.character.numeric.regexp`},2:{name:`constant.character.control.regexp`},3:{name:`constant.character.escape.backslash.regexp`},4:{name:`constant.character.numeric.regexp`},5:{name:`constant.character.control.regexp`},6:{name:`constant.character.escape.backslash.regexp`}},match:RegExp(`(?:[^\\n]|(\\\\(?:[0-7]{3}|x\\p{AHex}{2}|u\\p{AHex}{4}))|(\\\\c[A-Z])|(\\\\[^\\n]))-(?:[^\\]\\\\]|(\\\\(?:[0-7]{3}|x\\p{AHex}{2}|u\\p{AHex}{4}))|(\\\\c[A-Z])|(\\\\[^\\n]))`,`dgv`),name:`constant.other.character-class.range.regexp`},{include:`#regex-character-class`}]},{include:`#regex-character-class`}]},"return-type":{patterns:[{begin:RegExp(`(?<=\\))\\p{space}*(:)(?=\\p{space}*\\P{space})`,`dgv`),beginCaptures:{1:{name:`keyword.operator.type.annotation.js`}},end:RegExp(`(?]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\()|(EPSILON|MAX_SAFE_INTEGER|MAX_VALUE|MIN_SAFE_INTEGER|MIN_VALUE|NEGATIVE_INFINITY|POSITIVE_INFINITY)\\b(?!\\$))`,`dgv`)},{captures:{1:{name:`support.type.object.module.js`},2:{name:`support.type.object.module.js`},3:{name:`punctuation.accessor.js`},4:{name:`punctuation.accessor.optional.js`},5:{name:`support.type.object.module.js`}},match:RegExp(`(?\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)?`)",`dgv`),end:RegExp("(?=`)",`dgv`),patterns:[{begin:RegExp(`(?=(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*\\??\\.\\p{space}*)*|(\\??\\.\\p{space}*)?)([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*))`,`dgv`),end:RegExp("(?=(<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)?`)",`dgv`),patterns:[{include:`#support-function-call-identifiers`},{match:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)`,`dgv`),name:`entity.name.function.tagged-template.js`}]},{include:`#type-arguments`}]},{begin:RegExp("([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)?\\p{space}*(?=(<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>|<\\p{space}*(((keyof|infer|typeof|readonly)\\p{space}+)|(([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|('([^'\\\\]|\\\\[^\\n])*')|(\"([^\"\\\\]|\\\\[^\\n])*\")|(`([^\\\\\\`]|\\\\[^\\n])*`))(?=\\p{space}*([\\,\\.\\<\\>\\[]|=>|&(?!&)|\\|(?!\\|)))))([^\\(\\<\\>]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)>)*(?))*(?)*(?\\p{space}*)`)",`dgv`),beginCaptures:{1:{name:`entity.name.function.tagged-template.js`}},end:RegExp("(?=`)",`dgv`),patterns:[{include:`#type-arguments`}]}]},"template-substitution-element":{begin:RegExp(`\\$\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.template-expression.begin.js`}},contentName:`meta.embedded.line.js`,end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.template-expression.end.js`}},name:`meta.template.expression.js`,patterns:[{include:`#expression`}]},"template-type":{patterns:[{include:`#template-call`},{begin:RegExp("([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)?(`)",`dgv`),beginCaptures:{1:{name:`entity.name.function.tagged-template.js`},2:{name:`string.template.js punctuation.definition.string.template.begin.js`}},contentName:`string.template.js`,end:RegExp("`",`dgv`),endCaptures:{0:{name:`string.template.js punctuation.definition.string.template.end.js`}},patterns:[{include:`#template-type-substitution-element`},{include:`#string-character-escape`}]}]},"template-type-substitution-element":{begin:RegExp(`\\$\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.template-expression.begin.js`}},contentName:`meta.embedded.line.js`,end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.template-expression.end.js`}},name:`meta.template.expression.js`,patterns:[{include:`#type`}]},"ternary-expression":{begin:RegExp(`(?!\\?\\.\\p{space}*\\P{Nd})(\\?)(?!\\?)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.ternary.js`}},end:RegExp(`\\p{space}*(:)`,`dgv`),endCaptures:{1:{name:`keyword.operator.ternary.js`}},patterns:[{include:`#expression`}]},"this-literal":{match:RegExp(`(?])|((?<=[\\]\\$\\)\\>_\\}\\p{Alpha}])\\p{space}*(?=\\{)))`,`dgv`),name:`meta.type.annotation.js`,patterns:[{include:`#type`}]},{begin:RegExp(`(:)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.type.annotation.js`}},end:RegExp(`(?])|(?=^\\p{space}*(?=\\n?$))|((?<=[\\]\\$\\)\\>_\\}\\p{Alpha}])\\p{space}*(?=\\{)))`,`dgv`),name:`meta.type.annotation.js`,patterns:[{include:`#type`}]}]},"type-arguments":{begin:RegExp(`<`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.typeparameters.begin.js`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.typeparameters.end.js`}},name:`meta.type.parameters.js`,patterns:[{include:`#type-arguments-body`}]},"type-arguments-body":{patterns:[{captures:{0:{name:`keyword.operator.type.js`}},match:RegExp(`(?)`,`dgv`),patterns:[{include:`#comment`},{include:`#type-parameters`}]},{begin:RegExp(`(?))))))`,`dgv`),end:RegExp(`(?<=\\))`,`dgv`),name:`meta.type.function.js`,patterns:[{include:`#function-parameters`}]}]},"type-function-return-type":{patterns:[{begin:RegExp(`(=>)(?=\\p{space}*\\P{space})`,`dgv`),beginCaptures:{1:{name:`storage.type.function.arrow.js`}},end:RegExp(`(?)(?\\?\\{\\}]|\\/\\/|(?=\\n?$))`,`dgv`),name:`meta.type.function.return.js`,patterns:[{include:`#type-function-return-type-core`}]},{begin:RegExp(`=>`,`dgv`),beginCaptures:{0:{name:`storage.type.function.arrow.js`}},end:RegExp(`(?)(?\\?\\{\\}]|\\/\\/|^\\p{space}*(?=\\n?$))|((?<=\\P{space})(?=\\p{space}*(?=\\n?$))))`,`dgv`),name:`meta.type.function.return.js`,patterns:[{include:`#type-function-return-type-core`}]}]},"type-function-return-type-core":{patterns:[{include:`#comment`},{begin:RegExp(`(?<==>)(?=\\p{space}*\\{)`,`dgv`),end:RegExp(`(?<=\\})`,`dgv`),patterns:[{include:`#type-object`}]},{include:`#type-predicate-operator`},{include:`#type`}]},"type-infer":{patterns:[{captures:{1:{name:`keyword.operator.expression.infer.js`},2:{name:`entity.name.type.js`},3:{name:`keyword.operator.expression.extends.js`}},match:RegExp(`(?)`,`dgv`),endCaptures:{1:{name:`meta.type.parameters.js punctuation.definition.typeparameters.end.js`}},patterns:[{include:`#type-arguments-body`}]},{begin:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)\\p{space}*(<)`,`dgv`),beginCaptures:{1:{name:`entity.name.type.js`},2:{name:`meta.type.parameters.js punctuation.definition.typeparameters.begin.js`}},contentName:`meta.type.parameters.js`,end:RegExp(`(>)`,`dgv`),endCaptures:{1:{name:`meta.type.parameters.js punctuation.definition.typeparameters.end.js`}},patterns:[{include:`#type-arguments-body`}]},{captures:{1:{name:`entity.name.type.module.js`},2:{name:`punctuation.accessor.js`},3:{name:`punctuation.accessor.optional.js`}},match:RegExp(`([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*)\\p{space}*(?:(\\.)|(\\?\\.(?!\\p{space}*\\p{Nd})))`,`dgv`)},{match:RegExp(`[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*`,`dgv`),name:`entity.name.type.js`}]},"type-object":{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.block.js`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.block.js`}},name:`meta.object.type.js`,patterns:[{include:`#comment`},{include:`#method-declaration`},{include:`#indexer-declaration`},{include:`#indexer-mapped-type-declaration`},{include:`#field-declaration`},{include:`#type-annotation`},{begin:RegExp(`\\.\\.\\.`,`dgv`),beginCaptures:{0:{name:`keyword.operator.spread.js`}},end:RegExp(`(?=[\\,\\;\\}]|(?=\\n?$))|(?<=\\})`,`dgv`),patterns:[{include:`#type`}]},{include:`#punctuation-comma`},{include:`#punctuation-semicolon`},{include:`#type`}]},"type-operators":{patterns:[{include:`#typeof-operator`},{include:`#type-infer`},{begin:RegExp(`([\\&\\|])(?=\\p{space}*\\{)`,`dgv`),beginCaptures:{0:{name:`keyword.operator.type.js`}},end:RegExp(`(?<=\\})`,`dgv`),patterns:[{include:`#type-object`}]},{begin:RegExp(`[\\&\\|]`,`dgv`),beginCaptures:{0:{name:`keyword.operator.type.js`}},end:RegExp(`(?=\\P{space})`,`dgv`)},{match:RegExp(`(?)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.typeparameters.end.js`}},name:`meta.type.parameters.js`,patterns:[{include:`#comment`},{match:RegExp(`(?)`,`dgv`),name:`keyword.operator.assignment.js`}]},"type-paren-or-function-parameters":{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`meta.brace.round.js`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`meta.brace.round.js`}},name:`meta.type.paren.cover.js`,patterns:[{captures:{1:{name:`storage.modifier.js`},2:{name:`keyword.operator.rest.js`},3:{name:`entity.name.function.js variable.language.this.js`},4:{name:`entity.name.function.js`},5:{name:`keyword.operator.optional.js`}},match:RegExp(`(?:(?)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))))`,`dgv`)},{captures:{1:{name:`storage.modifier.js`},2:{name:`keyword.operator.rest.js`},3:{name:`variable.parameter.js variable.language.this.js`},4:{name:`variable.parameter.js`},5:{name:`keyword.operator.optional.js`}},match:RegExp(`(?:(?\\?\\{\\|\\}]|(extends\\p{space}+)|(?=\\n?$)|;|^\\p{space}*(?=\\n?$)|^\\p{space}*(?:abstract|async|\\bawait\\p{space}+\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b\\b|break|case|catch|class|const|continue|declare|do|else|enum|export|finally|function|for|goto|if|import|interface|let|module|namespace|switch|return|throw|try|type|\\busing(?=\\p{space}+(?!in\\b|of\\b(?!\\p{space}*(?:of\\b|=)))[\\$_\\p{Alpha}])\\b|var|while)\\b)`,`dgv`),patterns:[{include:`#type-arguments`},{include:`#expression`}]},"undefined-literal":{match:RegExp(`(?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))|(:\\p{space}*((<)|(\\(\\p{space}*((\\))|(\\.\\.\\.)|([\\$_\\p{Alpha}\\p{Nd}]+\\p{space}*(([\\,\\:\\=\\?])|(\\)\\p{space}*=>)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))))|(:\\p{space}*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^\\<\\>]*>)|[^\\(\\)\\,\\<\\=\\>])+=\\p{space}*(((async\\p{space}+)?((function\\p{space}*[\\(\\*\\<])|(function\\p{space}+)|([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*=>)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>))))))`,`dgv`,{lazyCompile:!0}),beginCaptures:{1:{name:`meta.definition.variable.js variable.other.constant.js entity.name.function.js`}},end:RegExp(`(?=(?=\\n?$)|^|[\\,\\;\\=\\}]|((?)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>)))))|(:\\p{space}*((<)|(\\(\\p{space}*((\\))|(\\.\\.\\.)|([\\$_\\p{Alpha}\\p{Nd}]+\\p{space}*(([\\,\\:\\=\\?])|(\\)\\p{space}*=>)))))))|(:\\p{space}*(?\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))))))|(:\\p{space}*(=>|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(<[^\\<\\>]*>)|[^\\(\\)\\,\\<\\=\\>])+=\\p{space}*(((async\\p{space}+)?((function\\p{space}*[\\(\\*\\<])|(function\\p{space}+)|([\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*=>)))|((async\\p{space}*)?(((<\\p{space}*)(?=\\n?$)|(\\(\\p{space}*((([\\[\\{]\\p{space}*)?)(?=\\n?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\p{space}*((:\\p{space}*\\{?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*)))|((\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])\\p{space}*((:\\p{space}*\\[?)(?=\\n?$)|((\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\p{space}*)?=\\p{space}*))))))|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*((\\)\\p{space}*:)|((\\.\\.\\.\\p{space}*)?[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}*:)))|(<\\p{space}*[\\$_\\p{Alpha}][\\$_\\p{Alpha}\\p{Nd}]*\\p{space}+extends\\p{space}*[^\\=\\>])|((<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<]|<\\p{space}*(((const\\p{space}+)?[\\$_\\p{Alpha}])|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\]))([^\\<\\=\\>]|=[^\\<])*>)*>)*>\\p{space}*)?\\(\\p{space}*(/\\*([^\\*]|(\\*[^\\/]))*\\*/\\p{space}*)*(([\\$_\\p{Alpha}]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\[([^\\]\\[]|(\\[([^\\]\\[]|\\[[^\\]\\[]*\\])*\\]))*\\])|(\\.\\.\\.\\p{space}*[\\$_\\p{Alpha}]))([^"'\\(\\)\\\`]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|('([^'\\\\]|\\\\[^\\n])*')|("([^"\\\\]|\\\\[^\\n])*")|(\`([^\\\\\\\`]|\\\\[^\\n])*\`))*)?\\)(\\p{space}*:\\p{space}*([^\\(\\)\\<\\>\\{\\}]|<([^\\<\\>]|<([^\\<\\>]|<[^\\<\\>]+>)+>)+>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)?\\p{space}*=>))))))`,`dgv`,{lazyCompile:!0}),beginCaptures:{1:{name:`meta.definition.variable.js entity.name.function.js`},2:{name:`keyword.operator.definiteassignment.js`}},end:RegExp(`(?=(?=\\n?$)|^|[\\,\\;\\=\\}]|((?\\p{space}*(?=\\n?$))`,`dgv`),beginCaptures:{1:{name:`keyword.operator.assignment.js`}},end:RegExp(`(?=(?=\\n?$)|^|[\\]\\)\\,\\;\\}]|((?{eN(),dN=Object.freeze({displayName:`CSS`,name:`css`,patterns:[{include:`#comment-block`},{include:`#escapes`},{include:`#combinators`},{include:`#selector`},{include:`#at-rules`},{include:`#rule-list`}],repository:{"at-rules":{patterns:[{begin:RegExp(`^\\uFEFF?(?=\\p{space}*@charset\\b)`,`dgiv`),end:RegExp(`;|(?=(?=\\n?$))`,`dgv`),endCaptures:{0:{name:`punctuation.terminator.rule.css`}},name:`meta.at-rule.charset.css`,patterns:[{captures:{1:{name:`invalid.illegal.not-lowercase.charset.css`},2:{name:`invalid.illegal.leading-whitespace.charset.css`},3:{name:`invalid.illegal.no-whitespace.charset.css`},4:{name:`invalid.illegal.whitespace.charset.css`},5:{name:`invalid.illegal.not-double-quoted.charset.css`},6:{name:`invalid.illegal.unclosed-string.charset.css`},7:{name:`invalid.illegal.unexpected-characters.charset.css`}},match:new X(`^((?!@charset)@[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)|^(\\p{space}+)|(@charset\\P{space}[^\\;]*)|(?<=@charset)( {2,}|\\t+)|(?<=@charset )([^"\\;]+)|("[^"]+)(?=\\n?$)|(?<=")([^\\;]+)`,`dgv`,{strategy:`clip_search`})},{captures:{1:{name:`keyword.control.at-rule.charset.css`},2:{name:`punctuation.definition.keyword.css`}},match:RegExp(`((@)charset)(?=\\p{space})`,`dgv`)},{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.css`}},end:RegExp(`"|(?=\\n?$)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.css`}},name:`string.quoted.double.css`,patterns:[{begin:new X(`(?:^|^)(?=[^"]+(?=\\n?$))`,`dgv`,{strategy:`clip_search`}),end:RegExp(`(?=\\n?$)`,`dgv`),name:`invalid.illegal.unclosed.string.css`}]}]},{begin:RegExp(`((@)import)(?:\\p{space}+|(?=\\n?$)|(?=["']|\\/\\*))`,`dgiv`),beginCaptures:{1:{name:`keyword.control.at-rule.import.css`},2:{name:`punctuation.definition.keyword.css`}},end:RegExp(`;`,`dgv`),endCaptures:{0:{name:`punctuation.terminator.rule.css`}},name:`meta.at-rule.import.css`,patterns:[{begin:RegExp(`\\p{space}*(?=\\/\\*)`,`dgvy`),end:RegExp(`(?<=\\*\\/)\\p{space}*`,`dgv`),patterns:[{include:`#comment-block`}]},{include:`#string`},{include:`#url`},{include:`#media-query-list`}]},{begin:RegExp(`((@)font-face)(?=\\p{space}*|\\{|\\/\\*|(?=\\n?$))`,`dgiv`),beginCaptures:{1:{name:`keyword.control.at-rule.font-face.css`},2:{name:`punctuation.definition.keyword.css`}},end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),name:`meta.at-rule.font-face.css`,patterns:[{include:`#comment-block`},{include:`#escapes`},{include:`#rule-list`}]},{begin:RegExp(`(@)page(?=[\\:\\{\\p{space}]|\\/\\*|(?=\\n?$))`,`dgiv`),captures:{0:{name:`keyword.control.at-rule.page.css`},1:{name:`punctuation.definition.keyword.css`}},end:RegExp(`(?=\\p{space}*((?=\\n?$)|[\\:\\;\\{]))`,`dgv`),name:`meta.at-rule.page.css`,patterns:[{include:`#rule-list`}]},{begin:RegExp(`(?=@media([\\(\\p{space}]|\\/\\*|(?=\\n?$)))`,`dgiv`),end:new X(`(?<=\\})(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`(@)media`,`dgivy`),beginCaptures:{0:{name:`keyword.control.at-rule.media.css`},1:{name:`punctuation.definition.keyword.css`}},end:RegExp(`(?=\\p{space}*[\\;\\{])`,`dgv`),name:`meta.at-rule.media.header.css`,patterns:[{include:`#media-query-list`}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.media.begin.bracket.curly.css`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.media.end.bracket.curly.css`}},name:`meta.at-rule.media.body.css`,patterns:[{include:`$self`}]}]},{begin:RegExp(`(?=@counter-style(["'\\;\\{\\p{space}]|\\/\\*|(?=\\n?$)))`,`dgiv`),end:new X(`(?<=\\})(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`(@)counter-style`,`dgivy`),beginCaptures:{0:{name:`keyword.control.at-rule.counter-style.css`},1:{name:`punctuation.definition.keyword.css`}},end:RegExp(`(?=\\p{space}*\\{)`,`dgv`),name:`meta.at-rule.counter-style.header.css`,patterns:[{include:`#comment-block`},{include:`#escapes`},{captures:{0:{patterns:[{include:`#escapes`}]}},match:RegExp(`[\\-A-Z_a-z[^\\x00-\\x7F]](?:[\\-0-9A-Z_a-z[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))*`,`dgv`),name:`variable.parameter.style-name.css`}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.property-list.begin.bracket.curly.css`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.property-list.end.bracket.curly.css`}},name:`meta.at-rule.counter-style.body.css`,patterns:[{include:`#comment-block`},{include:`#escapes`},{include:`#rule-list-innards`}]}]},{begin:RegExp(`(?=@document(["'\\;\\{\\p{space}]|\\/\\*|(?=\\n?$)))`,`dgiv`),end:new X(`(?<=\\})(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`(@)document`,`dgivy`),beginCaptures:{0:{name:`keyword.control.at-rule.document.css`},1:{name:`punctuation.definition.keyword.css`}},end:RegExp(`(?=\\p{space}*[\\;\\{])`,`dgv`),name:`meta.at-rule.document.header.css`,patterns:[{begin:RegExp(`(?>>`,`dgv`),name:`invalid.deprecated.combinator.css`},{match:RegExp(`>>|[\\+\\>\\~]`,`dgv`),name:`keyword.operator.combinator.css`}]},commas:{match:RegExp(`,`,`dgv`),name:`punctuation.separator.list.comma.css`},"comment-block":{begin:RegExp(`\\/\\*`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.begin.css`}},end:RegExp(`\\*\\/`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.end.css`}},name:`comment.block.css`},escapes:{patterns:[{match:RegExp(`\\\\\\p{AHex}{1,6}`,`dgv`),name:`constant.character.escape.codepoint.css`},{begin:RegExp(`\\\\(?=\\n?$)\\p{space}*`,`dgv`),end:new X(`^(?]|\\/\\*)`,`dgiv`)},"media-query":{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(?=\\p{space}*[\\;\\{])`,`dgv`),patterns:[{include:`#comment-block`},{include:`#escapes`},{include:`#media-types`},{match:RegExp(`(?<=\\p{space}|^|,|\\*\\/)(only|not)(?=[\\{\\p{space}]|\\/\\*|(?=\\n?$))`,`dgiv`),name:`keyword.operator.logical.$1.media.css`},{match:RegExp(`(?<=\\p{space}|^|\\*\\/|\\))and(?=\\p{space}|\\/\\*|(?=\\n?$))`,`dgiv`),name:`keyword.operator.logical.and.media.css`},{match:RegExp(`,(?:(?:\\p{space}*,)+|(?=\\p{space}*[\\)\\;\\{]))`,`dgv`),name:`invalid.illegal.comma.css`},{include:`#commas`},{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.parameters.begin.bracket.round.css`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.css`}},patterns:[{include:`#media-features`},{include:`#media-feature-keywords`},{match:RegExp(`:`,`dgv`),name:`punctuation.separator.key-value.css`},{match:RegExp(`>=|<=|[\\<\\=\\>]`,`dgv`),name:`keyword.operator.comparison.css`},{captures:{1:{name:`constant.numeric.css`},2:{name:`keyword.operator.arithmetic.css`},3:{name:`constant.numeric.css`}},match:RegExp(`(\\p{Nd}+)\\p{space}*(\\/)\\p{space}*(\\p{Nd}+)`,`dgv`),name:`meta.ratio.css`},{include:`#numeric-values`},{include:`#comment-block`}]}]},"media-query-list":{begin:RegExp(`(?=\\p{space}*[^\\;\\{])`,`dgv`),end:RegExp(`(?=\\p{space}*[\\;\\{])`,`dgv`),patterns:[{include:`#media-query`}]},"media-types":{captures:{1:{name:`support.constant.media.css`},2:{name:`invalid.deprecated.constant.media.css`}},match:RegExp(`(?<=^|[\\,\\p{space}]|\\*\\/)(?:(all|print|screen|speech)|(aural|braille|embossed|handheld|projection|tty|tv))(?=(?=\\n?$)|[\\,\\;\\{\\p{space}]|\\/\\*)`,`dgiv`)},"numeric-values":{patterns:[{captures:{1:{name:`punctuation.definition.constant.css`}},match:RegExp(`(#)(?:\\p{AHex}{3,4}|\\p{AHex}{6}|\\p{AHex}{8})\\b`,`dgv`),name:`constant.other.color.rgb-value.hex.css`},{captures:{1:{name:`keyword.other.unit.percentage.css`},2:{name:"keyword.other.unit.${2:/downcase}.css"}},match:RegExp(`(?\\[\\{\\|\\~\\p{space}]|\\/\\*)|(?:[\\-0-9A-Z_a-z[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))*(?:[\\]\\!"\\%-\\(\\*\\;\\<\\?\\@\\^\\`\\|\\}]|\\/(?!\\*))+)(?:[\\-0-9A-Z_a-z[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))*)',`dgv`),name:`invalid.illegal.bad-identifier.css`},{captures:{1:{name:`punctuation.definition.entity.css`},2:{patterns:[{include:`#escapes`}]}},match:RegExp(`(\\.)((?:[\\-0-9A-Z_a-z[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))+)(?=(?=\\n?$)|[\\#\\)\\+\\,\\.\\:\\>\\[\\{\\|\\~\\p{space}]|\\/\\*)`,`dgv`),name:`entity.other.attribute-name.class.css`},{captures:{1:{name:`punctuation.definition.entity.css`},2:{patterns:[{include:`#escapes`}]}},match:RegExp(`(#)(-?(?![0-9])(?:[\\-0-9A-Z_a-z[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))+)(?=(?=\\n?$)|[\\#\\)\\+\\,\\.\\:\\>\\[\\{\\|\\~\\p{space}]|\\/\\*)`,`dgv`),name:`entity.other.attribute-name.id.css`},{begin:RegExp(`\\[`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.entity.begin.bracket.square.css`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`punctuation.definition.entity.end.bracket.square.css`}},name:`meta.attribute-selector.css`,patterns:[{include:`#comment-block`},{include:`#string`},{captures:{1:{name:`storage.modifier.ignore-case.css`}},match:RegExp(`(?<=["'\\p{space}]|^|\\*\\/)\\p{space}*([Ii])\\p{space}*(?=[\\]\\p{space}]|\\/\\*|(?=\\n?$))`,`dgv`)},{captures:{1:{name:`string.unquoted.attribute-value.css`,patterns:[{include:`#escapes`}]}},match:RegExp(`(?<==)\\p{space}*((?!\\/\\*)(?:[^\\]"'\\\\\\p{space}]|\\\\[^\\n])+)`,`dgv`)},{include:`#escapes`},{match:RegExp(`[\\$\\*\\^\\|\\~]?=`,`dgv`),name:`keyword.operator.pattern.css`},{match:RegExp(`\\|`,`dgv`),name:`punctuation.separator.css`},{captures:{1:{name:`entity.other.namespace-prefix.css`,patterns:[{include:`#escapes`}]}},match:RegExp(`(-?(?!\\p{Nd})(?:[\\-\\p{L}\\p{M}\\p{N}\\p{Pc}[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n]))+|\\*)(?=\\|(?![\\=\\p{space}]|(?=\\n?$)|\\])(?:-?(?!\\p{Nd})|[\\-\\\\\\p{L}\\p{M}\\p{N}\\p{Pc}[^\\x00-\\x7F]]))`,`dgv`)},{captures:{1:{name:`entity.other.attribute-name.css`,patterns:[{include:`#escapes`}]}},match:new X(`(-?(?!\\p{Nd})(?:(?=([\\-\\p{L}\\p{M}\\p{N}\\p{Pc}[^\\x00-\\x7F]]|\\\\(?:\\p{AHex}{1,6}|[^\\n])))\\2)+)\\p{space}*(?=[\\]\\$\\*\\=\\^\\|\\~]|/\\*)`,`dgv`,{hiddenCaptures:[2]})}]},{include:`#pseudo-classes`},{include:`#pseudo-elements`},{include:`#functional-pseudo-classes`},{match:RegExp(`(?\\[\\{\\|\\~\\p{space}]|\\/\\*|(?=\\n?$))`,`dgiv`),name:`entity.name.tag.css`},"unicode-range":{captures:{0:{name:`constant.other.unicode-range.css`},1:{name:`punctuation.separator.dash.unicode-range.css`}},match:RegExp(`(?{eN(),uN(),pN(),mN=Object.freeze({displayName:`HTML`,injections:{"R:text.html - (comment.block, text.html meta.embedded, meta.tag.*.*.html, meta.tag.*.*.*.html, meta.tag.*.*.*.*.html)":{patterns:[{match:RegExp(`<`,`dgv`),name:`invalid.illegal.bad-angle-bracket.html`}]}},name:`html`,patterns:[{include:`#xml-processing`},{include:`#comment`},{include:`#doctype`},{include:`#cdata`},{include:`#tags-valid`},{include:`#tags-invalid`},{include:`#entities`}],repository:{attribute:{patterns:[{begin:RegExp(`(s(hape|cope|t(ep|art)|ize(s)?|p(ellcheck|an)|elected|lot|andbox|rc(set|doc|lang)?)|h(ttp-equiv|i(dden|gh)|e(ight|aders)|ref(lang)?)|n(o(nce|validate|module)|ame)|c(h(ecked|arset)|ite|o(nt(ent(editable)?|rols)|ords|l(s(pan)?|or))|lass|rossorigin)|t(ype(mustmatch)?|itle|a(rget|bindex)|ranslate)|i(s(map)?|n(tegrity|putmode)|tem(scope|type|id|prop|ref)|d)|op(timum|en)|d(i(sabled|r(name)?)|ownload|e(coding|f(er|ault))|at(etime|a)|raggable)|usemap|p(ing|oster|la(ysinline|ceholder)|attern|reload)|enctype|value|kind|for(m(novalidate|target|enctype|action|method)?)?|w(idth|rap)|l(ist|o(op|w)|a(ng|bel))|a(s(ync)?|c(ce(sskey|pt(-charset)?)|tion)|uto(c(omplete|apitalize)|play|focus)|l(t|low(usermedia|paymentrequest|fullscreen))|bbr)|r(ows(pan)?|e(versed|quired|ferrerpolicy|l|adonly))|m(in(length)?|u(ted|ltiple)|e(thod|dia)|a(nifest|x(length)?)))(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.$1.html`,patterns:[{include:`#attribute-interior`}]},{begin:RegExp(`style(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.style.html`,patterns:[{begin:RegExp(`=`,`dgv`),beginCaptures:{0:{name:`punctuation.separator.key-value.html`}},end:RegExp(`(?<=[^\\=\\p{space}])(?!\\p{space}*=)|(?=\\/?>)`,`dgv`),patterns:[{begin:RegExp("(?=[^\\/\\<\\=\\>\\`\\p{space}]|\\/(?!>))",`dgv`),end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),name:`meta.embedded.line.css`,patterns:[{captures:{0:{name:`source.css`}},match:RegExp(`([^"'\\/\\<\\=\\>\\\`\\p{space}]|\\/(?!>))+`,`dgv`),name:`string.unquoted.html`},{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},contentName:`source.css`,end:RegExp(`(")`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`},1:{name:`source.css`}},name:`string.quoted.double.html`,patterns:[{include:`#entities`}]},{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},contentName:`source.css`,end:RegExp(`(')`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`},1:{name:`source.css`}},name:`string.quoted.single.html`,patterns:[{include:`#entities`}]}]},{match:RegExp(`=`,`dgv`),name:`invalid.illegal.unexpected-equals-sign.html`}]}]},{begin:RegExp(`on(s(croll|t(orage|alled)|u(spend|bmit)|e(curitypolicyviolation|ek(ing|ed)|lect))|hashchange|c(hange|o(ntextmenu|py)|u(t|echange)|l(ick|ose)|an(cel|play(through)?))|t(imeupdate|oggle)|in(put|valid)|o((?:n|ff)line)|d(urationchange|r(op|ag(start|over|e(n(ter|d)|xit)|leave)?)|blclick)|un(handledrejection|load)|p(opstate|lay(ing)?|a(ste|use|ge(show|hide))|rogress)|e(nded|rror|mptied)|volumechange|key(down|up|press)|focus|w(heel|aiting)|l(oad(start|e(nd|d((?:|meta)data)))?|anguagechange)|a(uxclick|fterprint|bort)|r(e(s(ize|et)|jectionhandled)|atechange)|m(ouse(o(ut|ver)|down|up|enter|leave|move)|essage(error)?)|b(efore(unload|print)|lur))(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.event-handler.$1.html`,patterns:[{begin:RegExp(`=`,`dgv`),beginCaptures:{0:{name:`punctuation.separator.key-value.html`}},end:RegExp(`(?<=[^\\=\\p{space}])(?!\\p{space}*=)|(?=\\/?>)`,`dgv`),patterns:[{begin:RegExp("(?=[^\\/\\<\\=\\>\\`\\p{space}]|\\/(?!>))",`dgv`),end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),name:`meta.embedded.line.js`,patterns:[{captures:{0:{name:`source.js`},1:{patterns:[{include:`source.js`}]}},match:RegExp(`(([^"'\\/\\<\\=\\>\\\`\\p{space}]|\\/(?!>))+)`,`dgv`),name:`string.unquoted.html`},{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},contentName:`source.js`,end:RegExp(`(")`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`},1:{name:`source.js`}},name:`string.quoted.double.html`,patterns:[{captures:{0:{patterns:[{include:`source.js`}]}},match:RegExp(`([^\\n"\\/]|\\/(?![\\*\\/]))+`,`dgv`)},{begin:RegExp(`\\/\\/`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.js`}},end:RegExp(`(?=")|\\n`,`dgv`),name:`comment.line.double-slash.js`},{begin:RegExp(`\\/\\*`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.begin.js`}},end:RegExp(`(?=")|\\*\\/`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.end.js`}},name:`comment.block.js`}]},{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},contentName:`source.js`,end:RegExp(`(')`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`},1:{name:`source.js`}},name:`string.quoted.single.html`,patterns:[{captures:{0:{patterns:[{include:`source.js`}]}},match:RegExp(`([^\\n'\\/]|\\/(?![\\*\\/]))+`,`dgv`)},{begin:RegExp(`\\/\\/`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.js`}},end:RegExp(`(?=')|\\n`,`dgv`),name:`comment.line.double-slash.js`},{begin:RegExp(`\\/\\*`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.begin.js`}},end:RegExp(`(?=')|\\*\\/`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.end.js`}},name:`comment.block.js`}]}]},{match:RegExp(`=`,`dgv`),name:`invalid.illegal.unexpected-equals-sign.html`}]}]},{begin:RegExp(`(data-[\\-a-z]+)(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.data-x.$1.html`,patterns:[{include:`#attribute-interior`}]},{begin:RegExp(`(align|bgcolor|border)(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`invalid.deprecated.entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.$1.html`,patterns:[{include:`#attribute-interior`}]},{begin:RegExp(`([^\\x00- "'\\/\\<\\=\\>\\x7F-\\x9F﷐-﷯￾￿🿾🿿𯿾𯿿𿿾𿿿\\u{4FFFE}\\u{4FFFF}\\u{5FFFE}\\u{5FFFF}\\u{6FFFE}\\u{6FFFF}\\u{7FFFE}\\u{7FFFF}\\u{8FFFE}\\u{8FFFF}\\u{9FFFE}\\u{9FFFF}\\u{AFFFE}\\u{AFFFF}\\u{BFFFE}\\u{BFFFF}\\u{CFFFE}\\u{CFFFF}\\u{DFFFE}\\u{DFFFF}\\u{EFFFE}\\u{EFFFF}\\u{FFFFE}\\u{FFFFF}\\u{10FFFE}\\u{10FFFF}]+)`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.unrecognized.$1.html`,patterns:[{include:`#attribute-interior`}]},{match:RegExp(`[^\\>\\p{space}]+`,`dgv`),name:`invalid.illegal.character-not-allowed-here.html`}]},"attribute-interior":{patterns:[{begin:RegExp(`=`,`dgv`),beginCaptures:{0:{name:`punctuation.separator.key-value.html`}},end:RegExp(`(?<=[^\\=\\p{space}])(?!\\p{space}*=)|(?=\\/?>)`,`dgv`),patterns:[{match:RegExp(`([^"'\\/\\<\\=\\>\\\`\\p{space}]|\\/(?!>))+`,`dgv`),name:`string.unquoted.html`},{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`}},name:`string.quoted.double.html`,patterns:[{include:`#entities`}]},{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.html`}},end:RegExp(`'`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.html`}},name:`string.quoted.single.html`,patterns:[{include:`#entities`}]},{match:RegExp(`=`,`dgv`),name:`invalid.illegal.unexpected-equals-sign.html`}]}]},cdata:{begin:RegExp(``,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.cdata.html`},comment:{begin:RegExp(``,`dgv`),name:`comment.block.html`,patterns:[{match:RegExp(`-?>`,`dgvy`),name:`invalid.illegal.characters-not-allowed-here.html`},{match:RegExp(`)|(?=-->))`,`dgv`),name:`invalid.illegal.characters-not-allowed-here.html`},{match:RegExp(`--!>`,`dgv`),name:`invalid.illegal.characters-not-allowed-here.html`}]},"core-minus-invalid":{patterns:[{include:`#xml-processing`},{include:`#comment`},{include:`#doctype`},{include:`#cdata`},{include:`#tags-valid`},{include:`#entities`}]},doctype:{begin:RegExp(``,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.doctype.html`,patterns:[{match:RegExp(`DOCTYPE`,`dgivy`),name:`entity.name.tag.html`},{begin:RegExp(`"`,`dgv`),end:RegExp(`"`,`dgv`),name:`string.quoted.double.html`},{match:RegExp(`[^\\>\\p{space}]+`,`dgv`),name:`entity.other.attribute-name.html`}]},entities:{patterns:[{captures:{1:{name:`punctuation.definition.entity.html`},912:{name:`punctuation.definition.entity.html`}},match:new X(`(&)(?=[A-Za-z])((a(s(ymp(eq)?|cr|t)|n(d(slope|[dv]|and)?|g(s(t|ph)|zarr|e|le|rt(vb(d)?)?|msd(a([a-h]))?)?)|c(y|irc|d|ute|E)?|tilde|o(pf|gon)|uml|p(id|os|prox(eq)?|[Ee]|acir)?|elig|f(r)?|w((?:con|)int)|l(pha|e(ph|fsym))|acute|ring|grave|m(p|a(cr|lg))|breve)|A(s(sign|cr)|nd|MP|c(y|irc)|tilde|o(pf|gon)|uml|pplyFunction|fr|Elig|lpha|acute|ring|grave|macr|breve))|(B(scr|cy|opf|umpeq|e(cause|ta|rnoullis)|fr|a(ckslash|r(v|wed))|reve)|b(s(cr|im(e)?|ol(hsub|b)?|emi)|n(ot|e(quiv)?)|c(y|ong)|ig(s(tar|qcup)|c(irc|up|ap)|triangle(down|up)|o(times|dot|plus)|uplus|vee|wedge)|o(t(tom)?|pf|wtie|x(h([DUdu])?|times|H([DUdu])?|d([LRlr])|u([LRlr])|plus|D([LRlr])|v([HLRhlr])?|U([LRlr])|V([HLRhlr])?|minus|box))|Not|dquo|u(ll(et)?|mp(e(q)?|E)?)|prime|e(caus(e)?|t(h|ween|a)|psi|rnou|mptyv)|karow|fr|l(ock|k(1([24])|34)|a(nk|ck(square|triangle(down|left|right)?|lozenge)))|a(ck(sim(eq)?|cong|prime|epsilon)|r(vee|wed(ge)?))|r(eve|vbar)|brk(tbrk)?))|(c(s(cr|u(p(e)?|b(e)?))|h(cy|i|eck(mark)?)|ylcty|c(irc|ups(sm)?|edil|a(ps|ron))|tdot|ir(scir|c(eq|le(d(R|circ|S|dash|ast)|arrow(left|right)))?|e|fnint|E|mid)?|o(n(int|g(dot)?)|p(y(sr)?|f|rod)|lon(e(q)?)?|m(p(fn|le(xes|ment))?|ma(t)?))|dot|u(darr([lr])|p(s|c([au]p)|or|dot|brcap)?|e(sc|pr)|vee|wed|larr(p)?|r(vearrow(left|right)|ly(eq(succ|prec)|vee|wedge)|arr(m)?|ren))|e(nt(erdot)?|dil|mptyv)|fr|w((?:con|)int)|lubs(uit)?|a(cute|p(s|c([au]p)|dot|and|brcup)?|r(on|et))|r(oss|arr))|C(scr|hi|c(irc|onint|edil|aron)|ircle(Minus|Times|Dot|Plus)|Hcy|o(n(tourIntegral|int|gruent)|unterClockwiseContourIntegral|p(f|roduct)|lon(e)?)|dot|up(Cap)?|OPY|e(nterDot|dilla)|fr|lo(seCurly((?:Double|)Quote)|ckwiseContourIntegral)|a(yleys|cute|p(italDifferentialD)?)|ross))|(d(s(c([ry])|trok|ol)|har([lr])|c(y|aron)|t(dot|ri(f)?)|i(sin|e|v(ide(ontimes)?|onx)?|am(s|ond(suit)?)?|gamma)|Har|z(cy|igrarr)|o(t(square|plus|eq(dot)?|minus)?|ublebarwedge|pf|wn(harpoon(left|right)|downarrows|arrow)|llar)|d(otseq|a(rr|gger))?|u(har|arr)|jcy|e(lta|g|mptyv)|f(isht|r)|wangle|lc(orn|rop)|a(sh(v)?|leth|rr|gger)|r(c(orn|rop)|bkarow)|b(karow|lac)|Arr)|D(s(cr|trok)|c(y|aron)|Scy|i(fferentialD|a(critical(Grave|Tilde|Do(t|ubleAcute)|Acute)|mond))|o(t(Dot|Equal)?|uble(Right(Tee|Arrow)|ContourIntegral|Do(t|wnArrow)|Up((?:Down|)Arrow)|VerticalBar|L(ong(RightArrow|Left((?:Right|)Arrow))|eft(RightArrow|Tee|Arrow)))|pf|wn(Right(TeeVector|Vector(Bar)?)|Breve|Tee(Arrow)?|arrow|Left(RightVector|TeeVector|Vector(Bar)?)|Arrow(Bar|UpArrow)?))|Zcy|el(ta)?|D(otrahd)?|Jcy|fr|a(shv|rr|gger)))|(e(s(cr|im|dot)|n(sp|g)|c(y|ir(c)?|olon|aron)|t([ah])|o(pf|gon)|dot|u(ro|ml)|p(si(v|lon)?|lus|ar(sl)?)|e|D(D??ot)|q(s(im|lant(less|gtr))|c(irc|olon)|u(iv(DD)?|est|als)|vparsl)|f(Dot|r)|l(s(dot)?|inters|l)?|a(ster|cute)|r(Dot|arr)|g(s(dot)?|rave)?|x(cl|ist|p(onentiale|ectation))|m(sp(1([34]))?|pty(set|v)?|acr))|E(s(cr|im)|c(y|irc|aron)|ta|o(pf|gon)|NG|dot|uml|TH|psilon|qu(ilibrium|al(Tilde)?)|fr|lement|acute|grave|x(ists|ponentialE)|m(pty((?:|Very)SmallSquare)|acr)))|(f(scr|nof|cy|ilig|o(pf|r(k(v)?|all))|jlig|partint|emale|f(ilig|l(l??ig)|r)|l(tns|lig|at)|allingdotseq|r(own|a(sl|c(1([2-68])|78|2([35])|3([458])|45|5([68])))))|F(scr|cy|illed((?:|Very)SmallSquare)|o(uriertrf|pf|rAll)|fr))|(G(scr|c(y|irc|edil)|t|opf|dot|T|Jcy|fr|amma(d)?|reater(Greater|SlantEqual|Tilde|Equal(Less)?|FullEqual|Less)|g|breve)|g(s(cr|im([el])?)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|irc)|t(c(c|ir)|dot|quest|lPar|r(sim|dot|eq(q?less)|less|a(pprox|rr)))?|imel|opf|dot|jcy|e(s(cc|dot(o(l)?)?|l(es)?)?|q(slant|q)?|l)?|v(nE|ertneqq)|fr|E(l)?|l([Eaj])?|a(cute|p|mma(d)?)|rave|g(g)?|breve))|(h(s(cr|trok|lash)|y(phen|bull)|circ|o(ok((?:lef|righ)tarrow)|pf|arr|rbar|mtht)|e(llip|arts(uit)?|rcon)|ks([ew]arow)|fr|a(irsp|lf|r(dcy|r(cir|w)?)|milt)|bar|Arr)|H(s(cr|trok)|circ|ilbertSpace|o(pf|rizontalLine)|ump(DownHump|Equal)|fr|a(cek|t)|ARDcy))|(i(s(cr|in(s(v)?|dot|[Ev])?)|n(care|t(cal|prod|e(rcal|gers)|larhk)?|odot|fin(tie)?)?|c(y|irc)?|t(ilde)?|i(nfin|i(i??nt)|ota)?|o(cy|ta|pf|gon)|u(kcy|ml)|jlig|prod|e(cy|xcl)|quest|f([fr])|acute|grave|m(of|ped|a(cr|th|g(part|e|line))))|I(scr|n(t(e(rsection|gral))?|visible(Comma|Times))|c(y|irc)|tilde|o(ta|pf|gon)|dot|u(kcy|ml)|Ocy|Jlig|fr|Ecy|acute|grave|m(plies|a(cr|ginaryI))?))|(j(s(cr|ercy)|c(y|irc)|opf|ukcy|fr|math)|J(s(cr|ercy)|c(y|irc)|opf|ukcy|fr))|(k(scr|hcy|c(y|edil)|opf|jcy|fr|appa(v)?|green)|K(scr|c(y|edil)|Hcy|opf|Jcy|fr|appa))|(l(s(h|cr|trok|im([eg])?|q(uo(r)?|b)|aquo)|h(ar(d|u(l)?)|blk)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|ub|e(d??il)|aron)|Barr|t(hree|c(c|ir)|imes|dot|quest|larr|r(i([ef])?|Par))?|Har|o(ng(left((?:|right)arrow)|rightarrow|mapsto)|times|z(enge|f)?|oparrow(left|right)|p(f|lus|ar)|w(ast|bar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|r((?:d|us)har))|ur((?:ds|u)har)|jcy|par(lt)?|e(s(s(sim|dot|eq(q?gtr)|approx|gtr)|cc|dot(o(r)?)?|g(es)?)?|q(slant|q)?|ft(harpoon(down|up)|threetimes|leftarrows|arrow(tail)?|right(squigarrow|harpoons|arrow(s)?))|g)?|v(nE|ertneqq)|f(isht|loor|r)|E(g)?|l(hard|corner|tri|arr)?|a(ng(d|le)?|cute|t(e(s)?|ail)?|p|emptyv|quo|rr(sim|hk|tl|pl|fs|lp|b(fs)?)?|gran|mbda)|r(har(d)?|corner|tri|arr|m)|g(E)?|m(idot|oust(ache)?)|b(arr|r(k(sl([du])|e)|ac([ek]))|brk)|A(tail|arr|rr))|L(s(h|cr|trok)|c(y|edil|aron)|t|o(ng(RightArrow|left((?:|right)arrow)|rightarrow|Left((?:Right|)Arrow))|pf|wer((?:Righ|Lef)tArrow))|T|e(ss(Greater|SlantEqual|Tilde|EqualGreater|FullEqual|Less)|ft(Right(Vector|Arrow)|Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|rightarrow|Floor|A(ngleBracket|rrow(RightArrow|Bar)?)))|Jcy|fr|l(eftarrow)?|a(ng|cute|placetrf|rr|mbda)|midot))|(M(scr|cy|inusPlus|opf|u|e(diumSpace|llintrf)|fr|ap)|m(s(cr|tpos)|ho|nplus|c(y|omma)|i(nus(d(u)?|b)?|cro|d(cir|dot|ast)?)|o(dels|pf)|dash|u((?:lti|)map)?|p|easuredangle|DDot|fr|l(cp|dr)|a(cr|p(sto(down|up|left)?)?|l(t(ese)?|e)|rker)))|(n(s(hort(parallel|mid)|c(cue|[er])?|im(e(q)?)?|u(cc(eq)?|p(set(eq(q)?)?|[Ee])?|b(set(eq(q)?)?|[Ee])?)|par|qsu([bp]e)|mid)|Rightarrow|h(par|arr|Arr)|G(t(v)?|g)|c(y|ong(dot)?|up|edil|a(p|ron))|t(ilde|lg|riangle(left(eq)?|right(eq)?)|gl)|i(s(d)?|v)?|o(t(ni(v([abc]))?|in(dot|v([abc])|E)?)?|pf)|dash|u(m(sp|ero)?)?|jcy|p(olint|ar(sl|t|allel)?|r(cue|e(c(eq)?)?)?)|e(s(im|ear)|dot|quiv|ar(hk|r(ow)?)|xist(s)?|Arr)?|v(sim|infin|Harr|dash|Dash|l(t(rie)?|e|Arr)|ap|r(trie|Arr)|g([et]))|fr|w(near|ar(hk|r(ow)?)|Arr)|V([Dd]ash)|l(sim|t(ri(e)?)?|dr|e(s(s)?|q(slant|q)?|ft((?:|right)arrow))?|E|arr|Arr)|a(ng|cute|tur(al(s)?)?|p(id|os|prox|E)?|bla)|r(tri(e)?|ightarrow|arr([cw])?|Arr)|g(sim|t(r)?|e(s|q(slant|q)?)?|E)|mid|L(t(v)?|eft((?:|right)arrow)|l)|b(sp|ump(e)?))|N(scr|c(y|edil|aron)|tilde|o(nBreakingSpace|Break|t(R(ightTriangle(Bar|Equal)?|everseElement)|Greater(Greater|SlantEqual|Tilde|Equal|FullEqual|Less)?|S(u(cceeds(SlantEqual|Tilde|Equal)?|perset(Equal)?|bset(Equal)?)|quareSu(perset(Equal)?|bset(Equal)?))|Hump(DownHump|Equal)|Nested(GreaterGreater|LessLess)|C(ongruent|upCap)|Tilde(Tilde|Equal|FullEqual)?|DoubleVerticalBar|Precedes((?:Slant|)Equal)?|E(qual(Tilde)?|lement|xists)|VerticalBar|Le(ss(Greater|SlantEqual|Tilde|Equal|Less)?|ftTriangle(Bar|Equal)?))?|pf)|u|e(sted(GreaterGreater|LessLess)|wLine|gative(MediumSpace|Thi((?:n|ck)Space)|VeryThinSpace))|Jcy|fr|acute))|(o(s(cr|ol|lash)|h(m|bar)|c(y|ir(c)?)|ti(lde|mes(as)?)|S|int|opf|d(sold|iv|ot|ash|blac)|uml|p(erp|lus|ar)|elig|vbar|f(cir|r)|l(c(ir|ross)|t|ine|arr)|a(st|cute)|r(slope|igof|or|d(er(of)?|[fm])?|v|arr)?|g(t|on|rave)|m(i(nus|cron|d)|ega|acr))|O(s(cr|lash)|c(y|irc)|ti(lde|mes)|opf|dblac|uml|penCurly((?:Double|)Quote)|ver(B(ar|rac(e|ket))|Parenthesis)|fr|Elig|acute|r|grave|m(icron|ega|acr)))|(p(s(cr|i)|h(i(v)?|one|mmat)|cy|i(tchfork|v)?|o(intint|und|pf)|uncsp|er(cnt|tenk|iod|p|mil)|fr|l(us(sim|cir|two|d([ou])|e|acir|mn|b)?|an(ck(h)?|kv))|ar(s(im|l)|t|a(llel)?)?|r(sim|n(sim|E|ap)|cue|ime(s)?|o(d|p(to)?|f(surf|line|alar))|urel|e(c(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?)?|E|ap)?|m)|P(s(cr|i)|hi|cy|i|o(incareplane|pf)|fr|lusMinus|artialD|r(ime|o(duct|portion(al)?)|ecedes(SlantEqual|Tilde|Equal)?)?))|(q(scr|int|opf|u(ot|est(eq)?|at(int|ernions))|prime|fr)|Q(scr|opf|UOT|fr))|(R(s(h|cr)|ho|c(y|edil|aron)|Barr|ight(Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|Floor|A(ngleBracket|rrow(Bar|LeftArrow)?))|o(undImplies|pf)|uleDelayed|e(verse(UpEquilibrium|E(quilibrium|lement)))?|fr|EG|a(ng|cute|rr(tl)?)|rightarrow)|r(s(h|cr|q(uo(r)?|b)|aquo)|h(o(v)?|ar(d|u(l)?))|nmid|c(y|ub|e(d??il)|aron)|Barr|t(hree|imes|ri([ef]|ltri)?)|i(singdotseq|ng|ght(squigarrow|harpoon(down|up)|threetimes|left(harpoons|arrows)|arrow(tail)?|rightarrows))|Har|o(times|p(f|lus|ar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|ldhar)|uluhar|p(polint|ar(gt)?)|e(ct|al(s|ine|part)?|g)|f(isht|loor|r)|l(har|arr|m)|a(ng([de]|le)?|c(ute|e)|t(io(nals)?|ail)|dic|emptyv|quo|rr(sim|hk|c|tl|pl|fs|w|lp|ap|b(fs)?)?)|rarr|x|moust(ache)?|b(arr|r(k(sl([du])|e)|ac([ek]))|brk)|A(tail|arr|rr)))|(s(s(cr|tarf|etmn|mile)|h(y|c(hcy|y)|ort(parallel|mid)|arp)|c(sim|y|n(sim|E|ap)|cue|irc|polint|e(dil)?|E|a(p|ron))?|t(ar(f)?|r(ns|aight(phi|epsilon)))|i(gma([fv])?|m(ne|dot|plus|e(q)?|l(E)?|rarr|g(E)?)?)|zlig|o(pf|ftcy|l(b(ar)?)?)|dot([be])?|u(ng|cc(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?|p(s(im|u([bp])|et(neq(q)?|eq(q)?)?)|hs(ol|ub)|1|n([Ee])|2|d(sub|ot)|3|plus|e(dot)?|E|larr|mult)?|m|b(s(im|u([bp])|et(neq(q)?|eq(q)?)?)|n([Ee])|dot|plus|e(dot)?|E|rarr|mult)?)|pa(des(uit)?|r)|e(swar|ct|tm(n|inus)|ar(hk|r(ow)?)|xt|mi|Arr)|q(su(p(set(eq)?|e)?|b(set(eq)?|e)?)|c(up(s)?|ap(s)?)|u(f|ar([ef]))?)|fr(own)?|w(nwar|ar(hk|r(ow)?)|Arr)|larr|acute|rarr|m(t(e(s)?)?|i(d|le)|eparsl|a(shp|llsetminus))|bquo)|S(scr|hort((?:Right|Down|Up|Left)Arrow)|c(y|irc|edil|aron)?|tar|igma|H(cy|CHcy)|opf|u(c(hThat|ceeds(SlantEqual|Tilde|Equal)?)|p(set|erset(Equal)?)?|m|b(set(Equal)?)?)|OFTcy|q(uare(Su(perset(Equal)?|bset(Equal)?)|Intersection|Union)?|rt)|fr|acute|mallCircle))|(t(s(hcy|c([ry])|trok)|h(i(nsp|ck(sim|approx))|orn|e(ta(sym|v)?|re(4|fore))|k(sim|ap))|c(y|edil|aron)|i(nt|lde|mes(d|b(ar)?)?)|o(sa|p(cir|f(ork)?|bot)?|ea)|dot|prime|elrec|fr|w(ixt|ohead((?:lef|righ)tarrow))|a(u|rget)|r(i(sb|time|dot|plus|e|angle(down|q|left(eq)?|right(eq)?)?|minus)|pezium|ade)|brk)|T(s(cr|trok)|RADE|h(i((?:n|ck)Space)|e(ta|refore))|c(y|edil|aron)|S(H??cy)|ilde(Tilde|Equal|FullEqual)?|HORN|opf|fr|a([bu])|ripleDot))|(u(scr|h(ar([lr])|blk)|c(y|irc)|t(ilde|dot|ri(f)?)|Har|o(pf|gon)|d(har|arr|blac)|u(arr|ml)|p(si(h|lon)?|harpoon(left|right)|downarrow|uparrows|lus|arrow)|f(isht|r)|wangle|l(c(orn(er)?|rop)|tri)|a(cute|rr)|r(c(orn(er)?|rop)|tri|ing)|grave|m(l|acr)|br(cy|eve)|Arr)|U(scr|n(ion(Plus)?|der(B(ar|rac(e|ket))|Parenthesis))|c(y|irc)|tilde|o(pf|gon)|dblac|uml|p(si(lon)?|downarrow|Tee(Arrow)?|per((?:Righ|Lef)tArrow)|DownArrow|Equilibrium|arrow|Arrow(Bar|DownArrow)?)|fr|a(cute|rr(ocir)?)|ring|grave|macr|br(cy|eve)))|(v(s(cr|u(pn([Ee])|bn([Ee])))|nsu([bp])|cy|Bar(v)?|zigzag|opf|dash|prop|e(e(eq|bar)?|llip|r(t|bar))|Dash|fr|ltri|a(ngrt|r(s(igma|u(psetneq(q)?|bsetneq(q)?))|nothing|t(heta|riangle(left|right))|p(hi|i|ropto)|epsilon|kappa|r(ho)?))|rtri|Arr)|V(scr|cy|opf|dash(l)?|e(e|r(yThinSpace|t(ical(Bar|Separator|Tilde|Line))?|bar))|Dash|vdash|fr|bar))|(w(scr|circ|opf|p|e(ierp|d(ge(q)?|bar))|fr|r(eath)?)|W(scr|circ|opf|edge|fr))|(X(scr|i|opf|fr)|x(s(cr|qcup)|h([Aa]rr)|nis|c(irc|up|ap)|i|o(time|dot|p(f|lus))|dtri|u(tri|plus)|vee|fr|wedge|l([Aa]rr)|r([Aa]rr)|map))|(y(scr|c(y|irc)|icy|opf|u(cy|ml)|en|fr|ac(y|ute))|Y(scr|c(y|irc)|opf|uml|Icy|Ucy|fr|acute|Acy))|(z(scr|hcy|c(y|aron)|igrarr|opf|dot|e(ta|etrf)|fr|w(n?j)|acute)|Z(scr|c(y|aron)|Hcy|opf|dot|e(ta|roWidthSpace)|fr|acute)))(;)`,`dgv`,{lazyCompile:!0}),name:`constant.character.entity.named.$2.html`},{captures:{1:{name:`punctuation.definition.entity.html`},3:{name:`punctuation.definition.entity.html`}},match:RegExp(`(&)#[0-9]+(;)`,`dgv`),name:`constant.character.entity.numeric.decimal.html`},{captures:{1:{name:`punctuation.definition.entity.html`},3:{name:`punctuation.definition.entity.html`}},match:RegExp(`(&)#[Xx]\\p{AHex}+(;)`,`dgv`),name:`constant.character.entity.numeric.hexadecimal.html`},{match:RegExp(`&(?=[0-9A-Za-z]+;)`,`dgv`),name:`invalid.illegal.ambiguous-ampersand.html`}]},math:{patterns:[{begin:RegExp(`(<)(math)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.structure.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)`,`dgv`),endCaptures:{0:{name:`meta.tag.structure.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.structure.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]}],repository:{attribute:{patterns:[{begin:RegExp(`(s(hift|ymmetric|cript(sizemultiplier|level|minsize)|t(ackalign|retchy)|ide|u([bp]scriptshift)|e(parator(s)?|lection)|rc)|h(eight|ref)|n(otation|umalign)|c(haralign|olumn(spa(n|cing)|width|lines|align)|lose|rossout)|i(n(dent(shift(first|last)?|target|align(first|last)?)|fixlinebreakstyle)|d)|o(pen|verflow)|d(i(splay(style)?|r)|e(nomalign|cimalpoint|pth))|position|e(dge|qual(columns|rows))|voffset|f(orm|ence|rame(spacing)?)|width|l(space|ine(thickness|leading|break(style|multchar)?)|o(ngdivstyle|cation)|ength|quote|argeop)|a(c(cent(under)?|tiontype)|l(t(text|img(-(height|valign|width))?)|ign(mentscope)?))|r(space|ow(spa(n|cing)|lines|align)|quote)|groupalign|x(link:href|mlns)|m(in(size|labelspacing)|ovablelimits|a(th(size|color|variant|background)|xsize))|bevelled)(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.$1.html`,patterns:[{include:`#attribute-interior`}]},{begin:RegExp(`([^\\x00- "'\\/\\<\\=\\>\\x7F-\\x9F﷐-﷯￾￿🿾🿿𯿾𯿿𿿾𿿿\\u{4FFFE}\\u{4FFFF}\\u{5FFFE}\\u{5FFFF}\\u{6FFFE}\\u{6FFFF}\\u{7FFFE}\\u{7FFFF}\\u{8FFFE}\\u{8FFFF}\\u{9FFFE}\\u{9FFFF}\\u{AFFFE}\\u{AFFFF}\\u{BFFFE}\\u{BFFFF}\\u{CFFFE}\\u{CFFFF}\\u{DFFFE}\\u{DFFFF}\\u{EFFFE}\\u{EFFFF}\\u{FFFFE}\\u{FFFFF}\\u{10FFFE}\\u{10FFFF}]+)`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.unrecognized.$1.html`,patterns:[{include:`#attribute-interior`}]},{match:RegExp(`[^\\>\\p{space}]+`,`dgv`),name:`invalid.illegal.character-not-allowed-here.html`}]},tags:{patterns:[{include:`#comment`},{include:`#cdata`},{captures:{0:{name:`meta.tag.structure.math.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(annotation|annotation-xml|semantics|menclose|merror|mfenced|mfrac|mpadded|mphantom|mroot|mrow|msqrt|mstyle|mmultiscripts|mover|mprescripts|msub|msubsup|msup|munder|munderover|none|mlabeledtr|mtable|mtd|mtr|mlongdiv|mscarries|mscarry|msgroup|msline|msrow|mstack|maction)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.structure.math.$2.html`},{begin:RegExp(`(<)(annotation|annotation-xml|semantics|menclose|merror|mfenced|mfrac|mpadded|mphantom|mroot|mrow|msqrt|mstyle|mmultiscripts|mover|mprescripts|msub|msubsup|msup|munder|munderover|none|mlabeledtr|mtable|mtd|mtr|mlongdiv|mscarries|mscarry|msgroup|msline|msrow|mstack|maction)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.structure.math.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.structure.math.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.structure.math.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.inline.math.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(m(?:[inos]|space|text|aligngroup|alignmark))(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.inline.math.$2.html`},{begin:RegExp(`(<)(m(?:[inos]|space|text|aligngroup|alignmark))(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.inline.math.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.inline.math.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.inline.math.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.object.math.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(mglyph)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.object.math.$2.html`},{begin:RegExp(`(<)(mglyph)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.object.math.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.object.math.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.object.math.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.other.invalid.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(([\\:\\p{L}\\p{M}\\p{N}\\p{Pc}]+))(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgv`),name:`meta.element.other.invalid.html`},{begin:RegExp(`(<)(([\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\>\\p{space}]*))(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgv`),beginCaptures:{0:{name:`meta.tag.other.invalid.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)(((?!)))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.other.invalid.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{name:`punctuation.definition.tag.end.html`},5:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.other.invalid.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.invalid.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{include:`#tags-invalid`}]}}},svg:{patterns:[{begin:RegExp(`(<)(svg)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.structure.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)`,`dgv`),endCaptures:{0:{name:`meta.tag.structure.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.structure.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]}],repository:{attribute:{patterns:[{begin:RegExp(`(s(hape-rendering|ystemLanguage|cale|t(yle|itchTiles|op-(color|opacity)|dDeviation|em([hv])|artOffset|r(i(ng|kethrough-(thickness|position))|oke(-(opacity|dash(offset|array)|width|line(cap|join)|miterlimit))?))|urfaceScale|p(e(cular(Constant|Exponent)|ed)|acing|readMethod)|eed|lope)|h(oriz-(origin-x|adv-x)|eight|anging|ref(lang)?)|y([12]|ChannelSelector)?|n(umOctaves|ame)|c(y|o(ntentS((?:cript|tyle)Type)|lor(-(interpolation(-filters)?|profile|rendering))?)|ursor|l(ip(-(path|rule)|PathUnits)?|ass)|a(p-height|lcMode)|x)|t(ype|o|ext(-(decoration|anchor|rendering)|Length)|a(rget([XY])?|b(index|leValues))|ransform)|i(n(tercept|2)?|d(eographic)?|mage-rendering)|z(oomAndPan)?|o(p(erator|acity)|ver(flow|line-(thickness|position))|ffset|r(i(ent(ation)?|gin)|der))|d(y|i(splay|visor|ffuseConstant|rection)|ominant-baseline|ur|e(scent|celerate)|x)?|u(1|n(i(code(-(range|bidi))?|ts-per-em)|derline-(thickness|position))|2)|p(ing|oint(s(At([XYZ]))?|er-events)|a(nose-1|t(h(Length)?|tern(ContentUnits|Transform|Units))|int-order)|r(imitiveUnits|eserveA(spectRatio|lpha)))|e(n(d|able-background)|dgeMode|levation|x(ternalResourcesRequired|ponent))|v(i(sibility|ew(Box|Target))|-(hanging|ideographic|alphabetic|mathematical)|e(ctor-effect|r(sion|t-(origin-([xy])|adv-y)))|alues)|k([123]|e(y(Splines|Times|Points)|rn(ing|el(Matrix|UnitLength)))|4)?|f(y|il(ter(Res|Units)?|l(-(opacity|rule))?)|o(nt-(s(t(yle|retch)|ize(-adjust)?)|variant|family|weight)|rmat)|lood-(color|opacity)|r(om)?|x)|w(idth(s)?|ord-spacing|riting-mode)|l(i(ghting-color|mitingConeAngle)|ocal|e(ngthAdjust|tter-spacing)|ang)|a(scent|cc(umulate|ent-height)|ttribute(Name|Type)|zimuth|dditive|utoReverse|l(ignment-baseline|phabetic|lowReorder)|rabic-form|mplitude)|r(y|otate|e(s(tart|ult)|ndering-intent|peat(Count|Dur)|quired(Extensions|Features)|f([XY]|errerPolicy)|l)|adius|x)?|g([12]|lyph(Ref|-(name|orientation-(horizontal|vertical)))|radient(Transform|Units))|x([12]|ChannelSelector|-height|link:(show|href|t(ype|itle)|a(ctuate|rcrole)|role)|ml:(space|lang|base))?|m(in|ode|e(thod|dia)|a(sk((?:Content|)Units)?|thematical|rker(Height|-(start|end|mid)|Units|Width)|x))|b(y|ias|egin|ase(Profile|line-shift|Frequency)|box))(?![\\-\\:\\p{L}\\p{M}\\p{N}\\p{Pc}])`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.$1.html`,patterns:[{include:`#attribute-interior`}]},{begin:RegExp(`([^\\x00- "'\\/\\<\\=\\>\\x7F-\\x9F﷐-﷯￾￿🿾🿿𯿾𯿿𿿾𿿿\\u{4FFFE}\\u{4FFFF}\\u{5FFFE}\\u{5FFFF}\\u{6FFFE}\\u{6FFFF}\\u{7FFFE}\\u{7FFFF}\\u{8FFFE}\\u{8FFFF}\\u{9FFFE}\\u{9FFFF}\\u{AFFFE}\\u{AFFFF}\\u{BFFFE}\\u{BFFFF}\\u{CFFFE}\\u{CFFFF}\\u{DFFFE}\\u{DFFFF}\\u{EFFFE}\\u{EFFFF}\\u{FFFFE}\\u{FFFFF}\\u{10FFFE}\\u{10FFFF}]+)`,`dgv`),beginCaptures:{0:{name:`entity.other.attribute-name.html`}},end:new X(`(?=(?:(?=(\\p{space}*))\\1)[^\\=\\p{space}])`,`dgv`,{hiddenCaptures:[1]}),name:`meta.attribute.unrecognized.$1.html`,patterns:[{include:`#attribute-interior`}]},{match:RegExp(`[^\\>\\p{space}]+`,`dgv`),name:`invalid.illegal.character-not-allowed-here.html`}]},tags:{patterns:[{include:`#comment`},{include:`#cdata`},{captures:{0:{name:`meta.tag.metadata.svg.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(color-profile|desc|metadata|script|style|title)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.metadata.svg.$2.html`},{begin:RegExp(`(<)(color-profile|desc|metadata|script|style|title)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.metadata.svg.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.metadata.svg.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.metadata.svg.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.structure.svg.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(animateMotion|clipPath|defs|feComponentTransfer|feDiffuseLighting|feMerge|feSpecularLighting|filter|g|hatch|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|pattern|radialGradient|switch|text|textPath)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.structure.svg.$2.html`},{begin:RegExp(`(<)(animateMotion|clipPath|defs|feComponentTransfer|feDiffuseLighting|feMerge|feSpecularLighting|filter|g|hatch|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|pattern|radialGradient|switch|text|textPath)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.structure.svg.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.structure.svg.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.structure.svg.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.inline.svg.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(a|animate|discard|feBlend|feColorMatrix|feComposite|feConvolveMatrix|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feMergeNode|feMorphology|feOffset|fePointLight|feSpotLight|feTile|feTurbulence|hatchPath|mpath|set|solidcolor|stop|tspan)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.inline.svg.$2.html`},{begin:RegExp(`(<)(a|animate|discard|feBlend|feColorMatrix|feComposite|feConvolveMatrix|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feMergeNode|feMorphology|feOffset|fePointLight|feSpotLight|feTile|feTurbulence|hatchPath|mpath|set|solidcolor|stop|tspan)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.inline.svg.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.inline.svg.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.inline.svg.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.object.svg.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(circle|ellipse|feImage|foreignObject|image|line|path|polygon|polyline|rect|symbol|use|view)(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.object.svg.$2.html`},{begin:RegExp(`(<)(a|circle|ellipse|feImage|foreignObject|image|line|path|polygon|polyline|rect|symbol|use|view)(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.object.svg.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{patterns:[{include:`#attribute`}]},5:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)((?!))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.object.svg.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.object.svg.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.other.svg.$2.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)((altGlyph|altGlyphDef|altGlyphItem|animateColor|animateTransform|cursor|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|glyph|glyphRef|hkern|missing-glyph|tref|vkern))(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgiv`),name:`meta.element.other.svg.$2.html`},{begin:RegExp(`(<)((altGlyph|altGlyphDef|altGlyphItem|animateColor|animateTransform|cursor|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|glyph|glyphRef|hkern|missing-glyph|tref|vkern))(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgiv`),beginCaptures:{0:{name:`meta.tag.other.svg.$2.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)(((?!)))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.other.svg.$2.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`},4:{name:`punctuation.definition.tag.end.html`},5:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.other.svg.$2.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{captures:{0:{name:`meta.tag.other.invalid.void.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},match:RegExp(`(<)(([\\:\\p{L}\\p{M}\\p{N}\\p{Pc}]+))(?=\\p{space}|\\/?>)(([^"'\\>]|"[^"]*"|'[^']*')*)(\\/>)`,`dgv`),name:`meta.element.other.invalid.html`},{begin:RegExp(`(<)(([\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\>\\p{space}]*))(?=\\p{space}|\\/?>)(?:(([^"'\\>]|"[^"]*"|'[^']*')*)(>))?`,`dgv`),beginCaptures:{0:{name:`meta.tag.other.invalid.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{patterns:[{include:`#attribute`}]},6:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(<\\/)(((?!)))\\p{space}*(>)|(\\/>)|(?=<\\/[\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),endCaptures:{0:{name:`meta.tag.other.invalid.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.unrecognized-tag.html`},4:{name:`punctuation.definition.tag.end.html`},5:{name:`punctuation.definition.tag.end.html`}},name:`meta.element.other.invalid.html`,patterns:[{begin:RegExp(`(?)`,`dgvy`),end:RegExp(`(?=\\/>)|>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.invalid.start.html`,patterns:[{include:`#attribute`}]},{include:`#tags`}]},{include:`#tags-invalid`}]}}},"tags-invalid":{patterns:[{begin:RegExp(`(<\\/?)(([\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\>\\p{space}]*))(?)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.$2.html`,patterns:[{include:`#attribute`}]}]},"tags-valid":{patterns:[{begin:RegExp(`(^[\\t ]+)?(?=)`,`dgiv`),beginCaptures:{0:{name:`meta.tag.metadata.style.start.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`((<)\\/)(style)\\p{space}*(>)`,`dgiv`),endCaptures:{0:{name:`meta.tag.metadata.style.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`source.css-ignored-vscode`},3:{name:`entity.name.tag.html`},4:{name:`punctuation.definition.tag.end.html`}},name:`meta.embedded.block.html`,patterns:[{begin:RegExp(`(?:)`,`dgv`),captures:{1:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`(>)`,`dgv`),name:`meta.tag.metadata.style.start.html`,patterns:[{include:`#attribute`}]},{begin:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),end:RegExp(`(?=<\\/style)`,`dgiv`),name:`source.css`,patterns:[{include:`source.css`}]}]}]},{begin:RegExp(`(^[\\t ]+)?(?=)`,`dgiv`),endCaptures:{0:{name:`meta.tag.metadata.script.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`punctuation.definition.tag.end.html`}},name:`meta.embedded.block.html`,patterns:[{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(?=\\/)`,`dgv`),patterns:[{begin:RegExp(`(>)`,`dgv`),beginCaptures:{0:{name:`meta.tag.metadata.script.start.html`},1:{name:`punctuation.definition.tag.end.html`}},end:RegExp(`((<))(?=\\/script)`,`dgiv`),endCaptures:{0:{name:`meta.tag.metadata.script.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`source.js-ignored-vscode`}},patterns:[{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(?=<\\/script)`,`dgiv`),name:`source.js`,patterns:[{begin:RegExp(`(^[\\t ]+)?(?=\\/\\/)`,`dgv`),beginCaptures:{1:{name:`punctuation.whitespace.comment.leading.js`}},end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`\\/\\/`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.js`}},end:RegExp(`(?=<\\/script)|\\n`,`dgv`),name:`comment.line.double-slash.js`}]},{begin:RegExp(`\\/\\*`,`dgv`),captures:{0:{name:`punctuation.definition.comment.js`}},end:RegExp(`\\*\\/|(?=<\\/script)`,`dgv`),name:`comment.block.js`},{include:`source.js`}]}]},{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(?=>|type(?=[\\=\\p{space}])(?!\\p{space}*=\\p{space}*(''|""|(["']?)(text\\/(javascript(1\\.[0-5])?|x-javascript|jscript|livescript|(x-)?ecmascript|babel)|application\\/((?:(x-)?jav|(x-)?ecm)ascript)|module)["'\\>\\p{space}])))`,`dgiv`),name:`meta.tag.metadata.script.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(?=type\\p{space}*=\\p{space}*(["']?)text\\/(x-handlebars|(x-(handlebars-)?|ng-)?template|html)["'\\>\\p{space}])`,`dgiv`),end:RegExp(`((<))(?=\\/script)`,`dgiv`),endCaptures:{0:{name:`meta.tag.metadata.script.end.html`},1:{name:`punctuation.definition.tag.begin.html`},2:{name:`text.html.basic`}},patterns:[{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(>)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.script.start.html`,patterns:[{include:`#attribute`}]},{begin:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),end:RegExp(`(?=<\\/script)`,`dgiv`),name:`text.html.basic`,patterns:[{include:`text.html.basic`}]}]},{begin:RegExp(`(?=type)`,`dgiv`),end:RegExp(`(<)(?=\\/script)`,`dgiv`),endCaptures:{0:{name:`meta.tag.metadata.script.end.html`},1:{name:`punctuation.definition.tag.begin.html`}},patterns:[{begin:RegExp(`(?:)`,`dgv`),end:RegExp(`(>)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.script.start.html`,patterns:[{include:`#attribute`}]},{begin:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),end:RegExp(`(?=<\\/script)`,`dgiv`),name:`source.unknown`}]}]}]}]},{begin:RegExp(`(<)(base|link|meta)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(noscript|title)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)(noscript|title)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(col|hr|input)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(address|article|aside|blockquote|body|button|caption|colgroup|datalist|dd|details|dialog|div|dl|dt|fieldset|figcaption|figure|footer|form|head|header|hgroup|html|h[1-6]|label|legend|li|main|map|menu|meter|nav|ol|optgroup|option|output|p|pre|progress|section|select|slot|summary|table|tbody|td|template|textarea|tfoot|th|thead|tr|ul)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)(address|article|aside|blockquote|body|button|caption|colgroup|datalist|dd|details|dialog|div|dl|dt|fieldset|figcaption|figure|footer|form|head|header|hgroup|html|h[1-6]|label|legend|li|main|map|menu|meter|nav|ol|optgroup|option|output|p|pre|progress|section|select|slot|summary|table|tbody|td|template|textarea|tfoot|th|thead|tr|ul)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(area|br|wbr)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(a|abbr|b|bdi|bdo|cite|code|data|del|dfn|em|i|ins|kbd|mark|q|rp|rt|ruby|s|samp|small|span|strong|sub|sup|time|u|var)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)(a|abbr|b|bdi|bdo|cite|code|data|del|dfn|em|i|ins|kbd|mark|q|rp|rt|ruby|s|samp|small|span|strong|sub|sup|time|u|var)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(embed|img|param|source|track)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)(audio|canvas|iframe|object|picture|video)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)(audio|canvas|iframe|object|picture|video)(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((basefont|isindex))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.metadata.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((center|frameset|noembed|noframes))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)((center|frameset|noembed|noframes))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.structure.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((acronym|big|blink|font|strike|tt|xmp))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)((acronym|big|blink|font|strike|tt|xmp))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.inline.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((frame))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.void.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((applet))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)((applet))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.deprecated.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.object.$2.end.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<)((dir|keygen|listing|menuitem|plaintext|spacer))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.no-longer-supported.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.$2.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)((dir|keygen|listing|menuitem|plaintext|spacer))(?=\\p{space}|\\/?>)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`},3:{name:`invalid.illegal.no-longer-supported.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.other.$2.end.html`,patterns:[{include:`#attribute`}]},{include:`#math`},{include:`#svg`},{begin:RegExp(`(<)([A-Za-z][\\.0-9A-Z_a-z·À-ÖØ-öø-ͽͿ-῿‌‍‿⁀⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-\\u{EFFFF}]*-[\\-\\.0-9A-Z_a-z·À-ÖØ-öø-ͽͿ-῿‌‍‿⁀⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-\\u{EFFFF}]*)(?=\\p{space}|\\/?>)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`\\/?>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.custom.start.html`,patterns:[{include:`#attribute`}]},{begin:RegExp(`(<\\/)([A-Za-z][\\.0-9A-Z_a-z·À-ÖØ-öø-ͽͿ-῿‌‍‿⁀⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-\\u{EFFFF}]*-[\\-\\.0-9A-Z_a-z·À-ÖØ-öø-ͽͿ-῿‌‍‿⁀⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-\\u{EFFFF}]*)(?=\\p{space}|\\/?>)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.begin.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.tag.end.html`}},name:`meta.tag.custom.end.html`,patterns:[{include:`#attribute`}]}]},"xml-processing":{begin:RegExp(`(<\\?)(xml)`,`dgv`),captures:{1:{name:`punctuation.definition.tag.html`},2:{name:`entity.name.tag.html`}},end:RegExp(`(\\?>)`,`dgv`),name:`meta.tag.metadata.processing.xml.html`,patterns:[{include:`#attribute`}]}},scopeName:`text.html.basic`,embeddedLangs:[`javascript`,`css`],aliases:void 0}),hN=[...lN,...fN,mN]})),_N,vN,yN=t((()=>{eN(),_N=Object.freeze({displayName:`Java`,name:`java`,patterns:[{begin:RegExp(`\\b(package)\\b\\p{space}*`,`dgv`),beginCaptures:{1:{name:`keyword.other.package.java`}},contentName:`storage.modifier.package.java`,end:RegExp(`\\p{space}*(;)`,`dgv`),endCaptures:{1:{name:`punctuation.terminator.java`}},name:`meta.package.java`,patterns:[{include:`#comments`},{match:RegExp(`(?<=\\.)\\p{space}*\\.|\\.(?=\\p{space}*;)`,`dgv`),name:`invalid.illegal.character_not_allowed_here.java`},{match:RegExp(`(?`,`dgv`),endCaptures:{0:{name:`punctuation.bracket.angle.java`}},patterns:[{match:RegExp(`\\b(extends|super)\\b`,`dgv`),name:`storage.modifier.$1.java`},{captures:{1:{name:`storage.type.java`}},match:RegExp(`(?>>?|[\\^\\~])`,`dgv`),name:`keyword.operator.bitwise.java`},{match:RegExp(`(([\\&\\^\\|]|<<|>>>?)=)`,`dgv`),name:`keyword.operator.assignment.bitwise.java`},{match:RegExp(`(===?|!=|<=|>=|<>|[\\<\\>])`,`dgv`),name:`keyword.operator.comparison.java`},{match:RegExp(`([\\-\\%\\*\\+\\/]=)`,`dgv`),name:`keyword.operator.assignment.arithmetic.java`},{match:RegExp(`(=)`,`dgv`),name:`keyword.operator.assignment.java`},{match:RegExp(`(--|\\+\\+)`,`dgv`),name:`keyword.operator.increment-decrement.java`},{match:RegExp(`([\\-\\%\\*\\+\\/])`,`dgv`),name:`keyword.operator.arithmetic.java`},{match:RegExp(`(!|&&|\\|\\|)`,`dgv`),name:`keyword.operator.logical.java`},{match:RegExp(`([\\&\\|])`,`dgv`),name:`keyword.operator.bitwise.java`},{match:RegExp(`\\b(const|goto)\\b`,`dgv`),name:`keyword.reserved.java`}]},"lambda-expression":{patterns:[{match:RegExp(`->`,`dgv`),name:`storage.type.function.arrow.java`}]},"member-variables":{begin:RegExp(`(?=private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)`,`dgv`),end:RegExp(`(?=[\\;\\=])`,`dgv`),patterns:[{include:`#storage-modifiers`},{include:`#variables`},{include:`#primitive-arrays`},{include:`#object-types`}]},"method-call":{begin:RegExp(`(\\.)\\p{space}*([\\$A-Z_a-z][\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]*)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`punctuation.separator.period.java`},2:{name:`entity.name.function.java`},3:{name:`punctuation.definition.parameters.begin.bracket.round.java`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.java`}},name:`meta.method-call.java`,patterns:[{include:`#code`}]},methods:{begin:RegExp(`(?!new)(?=[\\<\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\n]*\\p{space}+)(?=([^\\/\\=]|\\/(?!\\/))+\\()`,`dgv`),end:RegExp(`(\\})|(?=;)`,`dgv`),endCaptures:{1:{name:`punctuation.section.method.end.bracket.curly.java`}},name:`meta.method.java`,patterns:[{include:`#storage-modifiers`},{begin:RegExp(`([\\p{L}\\p{M}\\p{N}\\p{Pc}]+)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`entity.name.function.java`},2:{name:`punctuation.definition.parameters.begin.bracket.round.java`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.java`}},name:`meta.method.identifier.java`,patterns:[{include:`#parameters`},{include:`#parens`},{include:`#comments`}]},{include:`#generics`},{begin:RegExp(`(?=[\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\n]*\\p{space}+[\\p{L}\\p{M}\\p{N}\\p{Pc}]+\\p{space}*\\()`,`dgv`),end:RegExp(`(?=\\p{space}+[\\p{L}\\p{M}\\p{N}\\p{Pc}]+\\p{space}*\\()`,`dgv`),name:`meta.method.return-type.java`,patterns:[{include:`#all-types`},{include:`#parens`},{include:`#comments`}]},{include:`#throws`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.method.begin.bracket.curly.java`}},contentName:`meta.method.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#code`}]},{include:`#comments`}]},module:{begin:RegExp(`((open)\\p{space})?(module)\\p{space}+([\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),beginCaptures:{1:{name:`storage.modifier.java`},3:{name:`storage.modifier.java`},4:{name:`entity.name.type.module.java`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.module.end.bracket.curly.java`}},name:`meta.module.java`,patterns:[{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.module.begin.bracket.curly.java`}},contentName:`meta.module.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#comments`},{include:`#comments-javadoc`},{match:RegExp(`\\b(requires|transitive|exports|opens|to|uses|provides|with)\\b`,`dgv`),name:`keyword.module.java`}]}]},numbers:{patterns:[{match:RegExp(`\\b(?)?(\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.java`},2:{name:`entity.name.type.record.java`},3:{patterns:[{include:`#generics`}]},4:{name:`punctuation.definition.parameters.begin.bracket.round.java`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.java`}},name:`meta.record.identifier.java`,patterns:[{include:`#code`}]},{begin:RegExp(`(implements)\\p{space}`,`dgv`),beginCaptures:{1:{name:`storage.modifier.implements.java`}},end:RegExp(`(?=\\p{space}*\\{)`,`dgv`),name:`meta.definition.class.implemented.interfaces.java`,patterns:[{include:`#object-types-inherited`},{include:`#comments`}]},{include:`#record-body`}]},"record-body":{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.class.begin.bracket.curly.java`}},end:RegExp(`(?=\\})`,`dgv`),name:`meta.record.body.java`,patterns:[{include:`#record-constructor`},{include:`#class-body`}]},"record-constructor":{begin:RegExp(`(?!new)(?=[\\<\\p{L}\\p{M}\\p{N}\\p{Pc}][^\\n]*\\p{space}+)(?=([^\\(\\/\\=]|\\/(?!\\/))+(?=\\{))`,`dgv`),end:RegExp(`(\\})|(?=;)`,`dgv`),endCaptures:{1:{name:`punctuation.section.method.end.bracket.curly.java`}},name:`meta.method.java`,patterns:[{include:`#storage-modifiers`},{begin:RegExp(`([\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),beginCaptures:{1:{name:`entity.name.function.java`}},end:RegExp(`(?=\\p{space}*\\{)`,`dgv`),name:`meta.method.identifier.java`,patterns:[{include:`#comments`}]},{include:`#comments`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.method.begin.bracket.curly.java`}},contentName:`meta.method.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#code`}]}]},"static-initializer":{patterns:[{include:`#anonymous-block-and-instance-initializer`},{match:RegExp(`static`,`dgv`),name:`storage.modifier.java`}]},"storage-modifiers":{match:RegExp(`\\b(public|private|protected|static|final|native|synchronized|abstract|threadsafe|transient|volatile|default|strictfp|sealed|non-sealed)\\b`,`dgv`),name:`storage.modifier.java`},strings:{patterns:[{begin:RegExp(`"""`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.java`}},end:RegExp(`"""`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.java`}},name:`string.quoted.triple.java`,patterns:[{match:RegExp(`(\\\\""")(?!")|(\\\\[^\\n])`,`dgv`),name:`constant.character.escape.java`}]},{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.java`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.java`}},name:`string.quoted.double.java`,patterns:[{match:RegExp(`\\\\[^\\n]`,`dgv`),name:`constant.character.escape.java`}]},{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.java`}},end:RegExp(`'`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.java`}},name:`string.quoted.single.java`,patterns:[{match:RegExp(`\\\\[^\\n]`,`dgv`),name:`constant.character.escape.java`}]}]},throws:{begin:RegExp(`throws`,`dgv`),beginCaptures:{0:{name:`storage.modifier.java`}},end:RegExp(`(?=[\\;\\{])`,`dgv`),name:`meta.throwables.java`,patterns:[{match:RegExp(`,`,`dgv`),name:`punctuation.separator.delimiter.java`},{match:RegExp(`[\\$A-Z_a-z][\\$\\.0-9A-Z_a-z]*`,`dgv`),name:`storage.type.java`},{include:`#comments`}]},"try-catch-finally":{patterns:[{begin:RegExp(`\\btry\\b`,`dgv`),beginCaptures:{0:{name:`keyword.control.try.java`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.try.end.bracket.curly.java`}},name:`meta.try.java`,patterns:[{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.section.try.resources.begin.bracket.round.java`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.section.try.resources.end.bracket.round.java`}},name:`meta.try.resources.java`,patterns:[{include:`#code`}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.try.begin.bracket.curly.java`}},contentName:`meta.try.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#code`}]}]},{begin:RegExp(`\\b(catch)\\b`,`dgv`),beginCaptures:{1:{name:`keyword.control.catch.java`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.catch.end.bracket.curly.java`}},name:`meta.catch.java`,patterns:[{include:`#comments`},{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.parameters.begin.bracket.round.java`}},contentName:`meta.catch.parameters.java`,end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.java`}},patterns:[{include:`#comments`},{include:`#storage-modifiers`},{begin:RegExp(`[\\$A-Z_a-z][\\$\\.0-9A-Z_a-z]*`,`dgv`),beginCaptures:{0:{name:`storage.type.java`}},end:RegExp(`(\\|)|(?=\\))`,`dgv`),endCaptures:{1:{name:`punctuation.catch.separator.java`}},patterns:[{include:`#comments`},{captures:{0:{name:`variable.parameter.java`}},match:RegExp(`[\\p{L}\\p{M}\\p{N}\\p{Pc}]+`,`dgv`)}]}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.catch.begin.bracket.curly.java`}},contentName:`meta.catch.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#code`}]}]},{begin:RegExp(`\\bfinally\\b`,`dgv`),beginCaptures:{0:{name:`keyword.control.finally.java`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.section.finally.end.bracket.curly.java`}},name:`meta.finally.java`,patterns:[{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.section.finally.begin.bracket.curly.java`}},contentName:`meta.finally.body.java`,end:RegExp(`(?=\\})`,`dgv`),patterns:[{include:`#code`}]}]}]},variables:{begin:new X(`(?=\\b((void|boolean|byte|char|short|int|float|long|double)|(?:(?=(([\\p{L}\\p{M}\\p{N}\\p{Pc}]+\\.)*[A-Z_]+[\\p{L}\\p{M}\\p{N}\\p{Pc}]*))\\3))\\b\\p{space}*(<[\\]\\,\\.\\<\\>\\?\\[\\p{L}\\p{M}\\p{N}\\p{Pc}\\p{space}]*>)?\\p{space}*((\\[\\])*)?\\p{space}+[\\$A-Z_a-z][\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]*([\\]\\$\\,\\[\\p{L}\\p{M}\\p{N}\\p{Pc}][\\]\\,\\[\\p{L}\\p{M}\\p{N}\\p{Pc}\\p{space}]*)?\\p{space}*([\\:\\;\\=]))`,`dgv`,{hiddenCaptures:[3]}),end:RegExp(`(?=[\\:\\;\\=])`,`dgv`),name:`meta.definition.variable.java`,patterns:[{captures:{1:{name:`variable.other.definition.java`}},match:RegExp(`([\\$A-Z_a-z][\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]*)(?=\\p{space}*(\\[\\])*\\p{space}*([\\,\\:\\;\\=]))`,`dgv`)},{include:`#all-types`},{include:`#code`}]},"variables-local":{begin:RegExp(`(?=\\b(var)\\b\\p{space}+[\\$A-Z_a-z][\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]*\\p{space}*([\\:\\;\\=]))`,`dgv`),end:RegExp(`(?=[\\:\\;\\=])`,`dgv`),name:`meta.definition.variable.local.java`,patterns:[{match:RegExp(`\\bvar\\b`,`dgv`),name:`storage.type.local.java`},{captures:{1:{name:`variable.other.definition.java`}},match:RegExp(`([\\$A-Z_a-z][\\$\\p{L}\\p{M}\\p{N}\\p{Pc}]*)(?=\\p{space}*(\\[\\])*\\p{space}*([\\:\\;\\=]))`,`dgv`)},{include:`#code`}]}},scopeName:`source.java`,embeddedLangs:void 0,aliases:void 0}),vN=[_N]})),bN,xN,SN=t((()=>{yN(),bN=Object.freeze({displayName:`XML`,name:`xml`,patterns:[{begin:RegExp(`(<\\?)\\p{space}*([\\-0-9A-Z_a-z]+)`,`dgv`),captures:{1:{name:`punctuation.definition.tag.xml`},2:{name:`entity.name.tag.xml`}},end:RegExp(`(\\?>)`,`dgv`),name:`meta.tag.preprocessor.xml`,patterns:[{match:RegExp(` ([\\-A-Za-z]+)`,`dgv`),name:`entity.other.attribute-name.xml`},{include:`#doublequotedString`},{include:`#singlequotedString`}]},{begin:RegExp(`()`,`dgv`),name:`meta.tag.sgml.doctype.xml`,patterns:[{include:`#internalSubset`}]},{include:`#comments`},{begin:RegExp(`(<)((?:([\\-0-9A-Z_a-z]+)(:))?([\\-0-\\:A-Z_a-z]+))(?=(\\p{space}[^\\>]*)?><\\/\\2>)`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.tag.xml`},2:{name:`entity.name.tag.xml`},3:{name:`entity.name.tag.namespace.xml`},4:{name:`punctuation.separator.namespace.xml`},5:{name:`entity.name.tag.localname.xml`}},end:RegExp(`(>)(<\\/)((?:([\\-0-9A-Z_a-z]+)(:))?([\\-0-\\:A-Z_a-z]+))(>)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.tag.xml`},2:{name:`punctuation.definition.tag.xml`},3:{name:`entity.name.tag.xml`},4:{name:`entity.name.tag.namespace.xml`},5:{name:`punctuation.separator.namespace.xml`},6:{name:`entity.name.tag.localname.xml`},7:{name:`punctuation.definition.tag.xml`}},name:`meta.tag.no-content.xml`,patterns:[{include:`#tagStuff`}]},{begin:RegExp(`(<\\/?)(?:([\\-\\.\\p{L}\\p{M}\\p{N}\\p{Pc}]+)((:)))?([\\-\\.\\:\\p{L}\\p{M}\\p{N}\\p{Pc}]+)`,`dgv`),captures:{1:{name:`punctuation.definition.tag.xml`},2:{name:`entity.name.tag.namespace.xml`},3:{name:`entity.name.tag.xml`},4:{name:`punctuation.separator.namespace.xml`},5:{name:`entity.name.tag.localname.xml`}},end:RegExp(`(\\/?>)`,`dgv`),name:`meta.tag.xml`,patterns:[{include:`#tagStuff`}]},{include:`#entity`},{include:`#bare-ampersand`},{begin:RegExp(`<%@`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.xml`}},end:RegExp(`%>`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.xml`}},name:`source.java-props.embedded.xml`,patterns:[{match:RegExp(`page|include|taglib`,`dgv`),name:`keyword.other.page-props.xml`}]},{begin:RegExp(`<%[\\!\\=]?(?!--)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.xml`}},end:RegExp(`(?!--)%>`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.xml`}},name:`source.java.embedded.xml`,patterns:[{include:`source.java`}]},{begin:RegExp(``,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.xml`}},name:`string.unquoted.cdata.xml`}],repository:{EntityDecl:{begin:RegExp(`()`,`dgv`),patterns:[{include:`#doublequotedString`},{include:`#singlequotedString`}]},"bare-ampersand":{match:RegExp(`&`,`dgv`),name:`invalid.illegal.bad-ampersand.xml`},comments:{patterns:[{begin:RegExp(`<%--`,`dgv`),captures:{0:{name:`punctuation.definition.comment.xml`},end:`--%>`,name:`comment.block.xml`}},{begin:RegExp(``,`dgv`),name:`comment.block.xml`,patterns:[{begin:RegExp(`--(?!>)`,`dgv`),captures:{0:{name:`invalid.illegal.bad-comments-or-CDATA.xml`}}}]}]},doublequotedString:{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.xml`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.xml`}},name:`string.quoted.double.xml`,patterns:[{include:`#entity`},{include:`#bare-ampersand`}]},entity:{captures:{1:{name:`punctuation.definition.constant.xml`},3:{name:`punctuation.definition.constant.xml`}},match:RegExp(`(&)([\\:A-Z_a-z][\\-\\.0-\\:A-Z_a-z]*|#[0-9]+|#x\\p{AHex}+)(;)`,`dgv`),name:`constant.character.entity.xml`},internalSubset:{begin:RegExp(`(\\[)`,`dgv`),captures:{1:{name:`punctuation.definition.constant.xml`}},end:RegExp(`(\\])`,`dgv`),name:`meta.internalsubset.xml`,patterns:[{include:`#EntityDecl`},{include:`#parameterEntity`},{include:`#comments`}]},parameterEntity:{captures:{1:{name:`punctuation.definition.constant.xml`},3:{name:`punctuation.definition.constant.xml`}},match:RegExp(`(%)([\\:A-Z_a-z][\\-\\.0-\\:A-Z_a-z]*)(;)`,`dgv`),name:`constant.character.parameter-entity.xml`},singlequotedString:{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.xml`}},end:RegExp(`'`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.xml`}},name:`string.quoted.single.xml`,patterns:[{include:`#entity`},{include:`#bare-ampersand`}]},tagStuff:{patterns:[{captures:{1:{name:`entity.other.attribute-name.namespace.xml`},2:{name:`entity.other.attribute-name.xml`},3:{name:`punctuation.separator.namespace.xml`},4:{name:`entity.other.attribute-name.localname.xml`}},match:RegExp(`(?:^|\\p{space}+)(?:([\\-\\.\\p{L}\\p{M}\\p{N}\\p{Pc}]+)((:)))?([\\-\\.\\:\\p{L}\\p{M}\\p{N}\\p{Pc}]+)\\p{space}*=`,`dgv`)},{include:`#doublequotedString`},{include:`#singlequotedString`}]}},scopeName:`text.xml`,embeddedLangs:[`java`],aliases:void 0}),xN=[...vN,bN]})),CN=n({default:()=>TN},1),wN,TN,EN=t((()=>{eN(),wN=Object.freeze({displayName:`SQL`,name:`sql`,patterns:[{match:RegExp(`((?]?=|<>|[\\<\\>]`,`dgv`),name:`keyword.operator.comparison.sql`},{match:RegExp(`[\\-\\+\\/]`,`dgv`),name:`keyword.operator.math.sql`},{match:RegExp(`\\|\\|`,`dgv`),name:`keyword.operator.concatenator.sql`},{captures:{1:{name:`support.function.aggregate.sql`}},match:RegExp(`\\b(approx_count_distinct|approx_percentile_cont|approx_percentile_disc|avg|checksum_agg|count|count_big|group|grouping|grouping_id|max|min|sum|stdevp??|varp??)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.analytic.sql`}},match:RegExp(`\\b(cume_dist|first_value|lag|last_value|lead|percent_rank|percentile_cont|percentile_disc)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.bitmanipulation.sql`}},match:RegExp(`\\b((?:bit_coun|get_bi|left_shif|right_shif|set_bi)t)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.conversion.sql`}},match:RegExp(`\\b(cast|convert|parse|try_cast|try_convert|try_parse)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.collation.sql`}},match:RegExp(`\\b(collationproperty|tertiary_weights)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.cryptographic.sql`}},match:RegExp(`\\b(asymkey_id|asymkeyproperty|certproperty|cert_id|crypt_gen_random|decryptbyasymkey|decryptbycert|decryptbykey|decryptbykeyautoasymkey|decryptbykeyautocert|decryptbypassphrase|encryptbyasymkey|encryptbycert|encryptbykey|encryptbypassphrase|hashbytes|is_objectsigned|key_guid|key_id|key_name|signbyasymkey|signbycert|symkeyproperty|verifysignedbycert|verifysignedbyasymkey)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.cursor.sql`}},match:RegExp(`\\b(cursor_status)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.datetime.sql`}},match:RegExp(`\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|datetrunc|eomonth|switchoffset|todatetimeoffset|isdate|date_bucket)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.datatype.sql`}},match:RegExp(`\\b(datalength|ident_current|ident_incr|ident_seed|identity|sql_variant_property)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.expression.sql`}},match:RegExp(`\\b(coalesce|nullif)\\b\\p{space}*\\(`,`dgiv`)},{captures:{1:{name:`support.function.globalvar.sql`}},match:RegExp(`(?kN},1),ON,kN,AN=t((()=>{ON=Object.freeze({displayName:`JSON`,name:`json`,patterns:[{include:`#value`}],repository:{array:{begin:RegExp(`\\[`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.array.begin.json`}},end:RegExp(`\\]`,`dgv`),endCaptures:{0:{name:`punctuation.definition.array.end.json`}},name:`meta.structure.array.json`,patterns:[{include:`#value`},{match:RegExp(`,`,`dgv`),name:`punctuation.separator.array.json`},{match:RegExp(`[^\\]\\p{space}]`,`dgv`),name:`invalid.illegal.expected-array-separator.json`}]},comments:{patterns:[{begin:RegExp(`\\/\\*\\*(?!\\/)`,`dgv`),captures:{0:{name:`punctuation.definition.comment.json`}},end:RegExp(`\\*\\/`,`dgv`),name:`comment.block.documentation.json`},{begin:RegExp(`\\/\\*`,`dgv`),captures:{0:{name:`punctuation.definition.comment.json`}},end:RegExp(`\\*\\/`,`dgv`),name:`comment.block.json`},{captures:{1:{name:`punctuation.definition.comment.json`}},match:RegExp(`(\\/\\/)[^\\n]*(?=\\n?$)\\n?`,`dgv`),name:`comment.line.double-slash.js`}]},constant:{match:RegExp(`\\b(?:true|false|null)\\b`,`dgv`),name:`constant.language.json`},number:{match:RegExp(`-?(?:0|[1-9]\\p{Nd}*)(?:(?:\\.\\p{Nd}+)?(?:[Ee][\\-\\+]?\\p{Nd}+)?)?`,`dgv`),name:`constant.numeric.json`},object:{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.dictionary.begin.json`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.dictionary.end.json`}},name:`meta.structure.dictionary.json`,patterns:[{include:`#objectkey`},{include:`#comments`},{begin:RegExp(`:`,`dgv`),beginCaptures:{0:{name:`punctuation.separator.dictionary.key-value.json`}},end:RegExp(`(,)|(?=\\})`,`dgv`),endCaptures:{1:{name:`punctuation.separator.dictionary.pair.json`}},name:`meta.structure.dictionary.value.json`,patterns:[{include:`#value`},{match:RegExp(`[^\\,\\p{space}]`,`dgv`),name:`invalid.illegal.expected-dictionary-separator.json`}]},{match:RegExp(`[^\\}\\p{space}]`,`dgv`),name:`invalid.illegal.expected-dictionary-separator.json`}]},objectkey:{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.support.type.property-name.begin.json`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.support.type.property-name.end.json`}},name:`string.json support.type.property-name.json`,patterns:[{include:`#stringcontent`}]},string:{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.json`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.json`}},name:`string.quoted.double.json`,patterns:[{include:`#stringcontent`}]},stringcontent:{patterns:[{match:RegExp(`\\\\(?:["\\/\\\\bfnrt]|u\\p{AHex}{4})`,`dgv`),name:`constant.character.escape.json`},{match:RegExp(`\\\\[^\\n]`,`dgv`),name:`invalid.illegal.unrecognized-string-escape.json`}]},value:{patterns:[{include:`#constant`},{include:`#number`},{include:`#string`},{include:`#array`},{include:`#object`},{include:`#comments`}]}},scopeName:`source.json`,embeddedLangs:void 0,aliases:void 0}),kN=[ON]})),jN=n({default:()=>NN},1),MN,NN,PN=t((()=>{eN(),gN(),SN(),EN(),uN(),AN(),pN(),MN=Object.freeze({displayName:`PHP`,name:`php`,patterns:[{include:`#attribute`},{include:`#comments`},{captures:{1:{name:`keyword.other.namespace.php`},2:{name:`entity.name.type.namespace.php`,patterns:[{match:RegExp(`\\\\`,`dgv`),name:`punctuation.separator.inheritance.php`}]}},match:RegExp(`(?:^|(?<=<\\?php))\\p{space}*(namespace)\\p{space}+([0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)(?=\\p{space}*;)`,`dgiv`),name:`meta.namespace.php`},{begin:RegExp(`(?:^|(?<=<\\?php))\\p{space}*(namespace)\\p{space}+`,`dgiv`),beginCaptures:{1:{name:`keyword.other.namespace.php`}},end:RegExp(`(?<=\\})|(?=\\?>)`,`dgv`),name:`meta.namespace.php`,patterns:[{include:`#comments`},{captures:{0:{patterns:[{match:RegExp(`\\\\`,`dgv`),name:`punctuation.separator.inheritance.php`}]}},match:RegExp(`[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+`,`dgiv`),name:`entity.name.type.namespace.php`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.namespace.begin.bracket.curly.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.namespace.end.bracket.curly.php`}},patterns:[{include:`$self`}]},{match:RegExp(`\\P{space}+`,`dgv`),name:`invalid.illegal.identifier.php`}]},{match:RegExp(`\\p{space}+(?=use\\b)`,`dgv`)},{begin:RegExp(`\\buse\\b`,`dgiv`),beginCaptures:{0:{name:`keyword.other.use.php`}},end:RegExp(`(?<=\\})|(?=;)|(?=\\?>)`,`dgv`),name:`meta.use.php`,patterns:[{match:RegExp(`\\b(const|function)\\b`,`dgv`),name:"storage.type.${1:/downcase}.php"},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.use.begin.bracket.curly.php`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.use.end.bracket.curly.php`}},patterns:[{include:`#scope-resolution`},{captures:{1:{name:`keyword.other.use-as.php`},2:{name:`storage.modifier.php`},3:{name:`entity.other.alias.php`}},match:RegExp(`\\b(as)\\p{space}+(final|abstract|public|private|protected|static)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)},{captures:{1:{name:`keyword.other.use-as.php`},2:{patterns:[{match:RegExp(`^(?:final|abstract|public|private|protected|static)(?=\\n?$)`,`dgv`),name:`storage.modifier.php`},{match:RegExp(`[^\\n]+`,`dgv`),name:`entity.other.alias.php`}]}},match:RegExp(`\\b(as)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)},{captures:{1:{name:`keyword.other.use-insteadof.php`},2:{name:`support.class.php`}},match:RegExp(`\\b(insteadof)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)},{match:RegExp(`;`,`dgv`),name:`punctuation.terminator.expression.php`},{include:`#use-inner`}]},{include:`#use-inner`}]},{begin:RegExp(`\\b(trait)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`),beginCaptures:{1:{name:`storage.type.trait.php`},2:{name:`entity.name.type.trait.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.trait.end.bracket.curly.php`}},name:`meta.trait.php`,patterns:[{include:`#comments`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.trait.begin.bracket.curly.php`}},contentName:`meta.trait.body.php`,end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{include:`$self`}]}]},{begin:RegExp(`\\b(interface)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`),beginCaptures:{1:{name:`storage.type.interface.php`},2:{name:`entity.name.type.interface.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.interface.end.bracket.curly.php`}},name:`meta.interface.php`,patterns:[{include:`#comments`},{include:`#interface-extends`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.interface.begin.bracket.curly.php`}},contentName:`meta.interface.body.php`,end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{include:`#class-constant`},{include:`$self`}]}]},{begin:RegExp(`\\b(enum)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(?:\\p{space}*(:)\\p{space}*(int|string)\\b)?`,`dgiv`),beginCaptures:{1:{name:`storage.type.enum.php`},2:{name:`entity.name.type.enum.php`},3:{name:`keyword.operator.return-value.php`},4:{name:`keyword.other.type.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.enum.end.bracket.curly.php`}},name:`meta.enum.php`,patterns:[{include:`#comments`},{include:`#class-implements`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.enum.begin.bracket.curly.php`}},contentName:`meta.enum.body.php`,end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{captures:{1:{name:`storage.modifier.php`},2:{name:`constant.enum.php`}},match:RegExp(`\\b(case)\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)},{include:`#class-constant`},{include:`$self`}]}]},{begin:RegExp(`\\b(?:((?:(?:final|abstract|readonly)\\p{space}+)*)(class)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)|(new)\\b\\p{space}*(#\\[[^\\n]*\\])?\\p{space}*(?:(readonly)\\p{space}+)?\\b(class)\\b)`,`dgiv`),beginCaptures:{1:{patterns:[{match:RegExp(`final|abstract`,`dgv`),name:"storage.modifier.${0:/downcase}.php"},{match:RegExp(`readonly`,`dgv`),name:`storage.modifier.php`}]},2:{name:`storage.type.class.php`},3:{name:`entity.name.type.class.php`},4:{name:`keyword.other.new.php`},5:{patterns:[{include:`#attribute`}]},6:{name:`storage.modifier.php`},7:{name:`storage.type.class.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.class.end.bracket.curly.php`}},name:`meta.class.php`,patterns:[{begin:RegExp(`(?<=class)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`punctuation.definition.arguments.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},name:`meta.function-call.php`,patterns:[{include:`#named-arguments`},{include:`$self`}]},{include:`#comments`},{include:`#class-extends`},{include:`#class-implements`},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.class.begin.bracket.curly.php`}},contentName:`meta.class.body.php`,end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{include:`#class-constant`},{include:`$self`}]}]},{include:`#match_statement`},{include:`#switch_statement`},{captures:{1:{name:`keyword.control.yield-from.php`}},match:RegExp(`\\p{space}*\\b(yield\\p{space}+from)\\b`,`dgv`)},{captures:{1:{name:"keyword.control.${1:/downcase}.php"}},match:RegExp(`\\b(break|case|continue|declare|default|die|do|else(if)?|end(declare|for(each)?|if|switch|while)|exit|for(each)?|if|return|switch|use|while|yield)\\b`,`dgv`)},{begin:RegExp(`\\b((?:require|include)(?:_once)?)(\\p{space}+|(?=\\())`,`dgiv`),beginCaptures:{1:{name:`keyword.control.import.include.php`}},end:RegExp(`(?=[\\;\\p{space}]|(?=\\n?$)|\\?>)`,`dgv`),name:`meta.include.php`,patterns:[{include:`$self`}]},{begin:RegExp(`\\b(catch)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`keyword.control.exception.catch.php`},2:{name:`punctuation.definition.parameters.begin.bracket.round.php`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.php`}},name:`meta.catch.php`,patterns:[{captures:{1:{patterns:[{match:RegExp(`\\|`,`dgv`),name:`punctuation.separator.delimiter.php`},{begin:RegExp(`(?=[\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(?![0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`support.class.exception.php`}},patterns:[{include:`#namespace`}]}]},2:{name:`variable.other.php`},3:{name:`punctuation.definition.variable.php`}},match:RegExp(`([0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*\\|\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)*)\\p{space}*((\\$+)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?`,`dgiv`)}]},{match:RegExp(`\\b(catch|try|throw|exception|finally)\\b`,`dgv`),name:`keyword.control.exception.php`},{begin:RegExp(`\\b(function)\\p{space}*(?=&?\\p{space}*\\()`,`dgiv`),beginCaptures:{1:{name:`storage.type.function.php`}},end:RegExp(`(?=\\p{space}*\\{)`,`dgv`),name:`meta.function.closure.php`,patterns:[{include:`#comments`},{begin:RegExp(`(&)?\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.reference.php`},2:{name:`punctuation.definition.parameters.begin.bracket.round.php`}},contentName:`meta.function.parameters.php`,end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.php`}},patterns:[{include:`#function-parameters`}]},{begin:RegExp(`(use)\\p{space}*(\\()`,`dgiv`),beginCaptures:{1:{name:`keyword.other.function.use.php`},2:{name:`punctuation.definition.parameters.begin.bracket.round.php`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.php`}},name:`meta.function.closure.use.php`,patterns:[{match:RegExp(`,`,`dgv`),name:`punctuation.separator.delimiter.php`},{captures:{1:{name:`variable.other.php`},2:{name:`storage.modifier.reference.php`},3:{name:`punctuation.definition.variable.php`}},match:RegExp(`((?:(&)\\p{space}*)?(\\$+)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)\\p{space}*(?=[\\)\\,])`,`dgiv`)}]},{captures:{1:{name:`keyword.operator.return-value.php`},2:{patterns:[{include:`#php-types`}]}},match:RegExp(`(:)\\p{space}*((?:\\?\\p{space}*)?[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\))(?:\\p{space}*[\\&\\|]\\p{space}*(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\)))+)(?=\\p{space}*(?:\\{|\\/[\\*\\/]|#|(?=\\n?$)))`,`dgiv`)}]},{begin:RegExp(`\\b(fn)\\p{space}*(?=&?\\p{space}*\\()`,`dgiv`),beginCaptures:{1:{name:`storage.type.function.php`}},end:RegExp(`=>`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arrow.php`}},name:`meta.function.closure.php`,patterns:[{begin:RegExp(`(?:(&)\\p{space}*)?(\\()`,`dgv`),beginCaptures:{1:{name:`storage.modifier.reference.php`},2:{name:`punctuation.definition.parameters.begin.bracket.round.php`}},contentName:`meta.function.parameters.php`,end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.parameters.end.bracket.round.php`}},patterns:[{include:`#function-parameters`}]},{captures:{1:{name:`keyword.operator.return-value.php`},2:{patterns:[{include:`#php-types`}]}},match:RegExp(`(:)\\p{space}*((?:\\?\\p{space}*)?[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\))(?:\\p{space}*[\\&\\|]\\p{space}*(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\)))+)(?=\\p{space}*(?:=>|\\/[\\*\\/]|#|(?=\\n?$)))`,`dgiv`)}]},{begin:RegExp(`((?:(?:final|abstract|public|private|protected)\\p{space}+)*)(function)\\p{space}+(__construct)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{patterns:[{match:RegExp(`final|abstract|public|private|protected`,`dgv`),name:`storage.modifier.php`}]},2:{name:`storage.type.function.php`},3:{name:`support.function.constructor.php`},4:{name:`punctuation.definition.parameters.begin.bracket.round.php`}},contentName:`meta.function.parameters.php`,end:RegExp(`(\\))\\p{space}*(:\\p{space}*(?:\\?\\p{space}*)?(?!\\p{space})[\\&\\(\\)0-9\\\\_a-z\\|\\x7F-\\u{10FFFF}\\p{space}]+(?)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.array.end.bracket.round.php`}},name:`meta.array.php`,patterns:[{include:`$self`}]},{captures:{1:{name:`punctuation.definition.storage-type.begin.bracket.round.php`},2:{name:`storage.type.php`},3:{name:`punctuation.definition.storage-type.end.bracket.round.php`}},match:RegExp(`(\\()\\p{space}*(array|real|double|float|int(?:eger)?|bool(?:ean)?|string|object|binary|unset)\\p{space}*(\\))`,`dgiv`)},{match:RegExp(`\\b(array|real|double|float|int(eger)?|bool(ean)?|string|class|var|function|interface|trait|parent|self|object|mixed)\\b`,`dgiv`),name:`storage.type.php`},{match:RegExp(`\\bconst\\b`,`dgiv`),name:`storage.type.const.php`},{match:RegExp(`\\b(global|abstract|final|private|protected|public|static)\\b`,`dgiv`),name:`storage.modifier.php`},{include:`#object`},{match:RegExp(`;`,`dgv`),name:`punctuation.terminator.expression.php`},{match:RegExp(`:`,`dgv`),name:`punctuation.terminator.statement.php`},{include:`#heredoc`},{include:`#numbers`},{match:RegExp(`\\bclone\\b`,`dgiv`),name:`keyword.other.clone.php`},{match:RegExp(`\\.\\.\\.`,`dgv`),name:`keyword.operator.spread.php`},{match:RegExp(`\\.=?`,`dgv`),name:`keyword.operator.string.php`},{match:RegExp(`=>`,`dgv`),name:`keyword.operator.key.php`},{captures:{1:{name:`keyword.operator.assignment.php`},2:{name:`storage.modifier.reference.php`},3:{name:`storage.modifier.reference.php`}},match:RegExp(`(=)(&)|(&)(?=[\\$_a-z])`,`dgiv`)},{match:RegExp(`@`,`dgv`),name:`keyword.operator.error-control.php`},{match:RegExp(`===?|!==?|<>`,`dgv`),name:`keyword.operator.comparison.php`},{match:RegExp(`(?:|[\\-\\+]|\\*\\*?|[\\%\\&\\/\\^\\|]|<<|>>|\\?\\?)=`,`dgv`),name:`keyword.operator.assignment.php`},{match:RegExp(`<=>?|>=|[\\<\\>]`,`dgv`),name:`keyword.operator.comparison.php`},{match:RegExp(`--|\\+\\+`,`dgv`),name:`keyword.operator.increment-decrement.php`},{match:RegExp(`[\\-\\+]|\\*\\*?|[\\%\\/]`,`dgv`),name:`keyword.operator.arithmetic.php`},{match:RegExp(`(!|&&|\\|\\|)|\\b(and|or|xor|as)\\b`,`dgiv`),name:`keyword.operator.logical.php`},{include:`#function-call`},{match:RegExp(`<<|>>|[\\&\\^\\|\\~]`,`dgv`),name:`keyword.operator.bitwise.php`},{begin:RegExp(`\\b(instanceof)\\p{space}+(?=[\\$\\\\_a-z])`,`dgiv`),beginCaptures:{1:{name:`keyword.operator.type.php`}},end:RegExp(`(?=[^\\$0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),patterns:[{include:`#class-name`},{include:`#variable-name`}]},{include:`#instantiation`},{captures:{1:{name:`keyword.control.goto.php`},2:{name:`support.other.php`}},match:RegExp(`(goto)\\p{space}+([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)},{captures:{1:{name:`entity.name.goto-label.php`}},match:RegExp(`^\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*(?)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.end.bracket.curly.php`}},patterns:[{include:`$self`}]},{begin:RegExp(`\\[`,`dgv`),beginCaptures:{0:{name:`punctuation.section.array.begin.php`}},end:RegExp(`\\]|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.section.array.end.php`}},patterns:[{include:`$self`}]},{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.end.bracket.round.php`}},patterns:[{include:`$self`}]},{include:`#constants`},{match:RegExp(`,`,`dgv`),name:`punctuation.separator.delimiter.php`}],repository:{attribute:{begin:RegExp(`#\\[`,`dgv`),end:RegExp(`\\]`,`dgv`),name:`meta.attribute.php`,patterns:[{match:RegExp(`,`,`dgv`),name:`punctuation.separator.delimiter.php`},{begin:RegExp(`([0-9A-Z\\\\_a-z\\x7F-\\u{10FFFF}]+)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{patterns:[{include:`#attribute-name`}]},2:{name:`punctuation.definition.arguments.begin.bracket.round.php`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},patterns:[{include:`#named-arguments`},{include:`$self`}]},{include:`#attribute-name`}]},"attribute-name":{patterns:[{begin:RegExp(`(?=\\\\?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*\\\\)`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(?![0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`support.attribute.php`}},patterns:[{include:`#namespace`}]},{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:RegExp(`(\\\\)?\\b(Attribute|SensitiveParameter|AllowDynamicProperties|ReturnTypeWillChange|Override|Deprecated)\\b`,`dgiv`),name:`support.attribute.builtin.php`},{begin:RegExp(`(?=[\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(?![0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`support.attribute.php`}},patterns:[{include:`#namespace`}]}]},"class-builtin":{patterns:[{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:new X(`(\\\\)?\\b(Attribute|(A(?:PC|ppend))Iterator|Array(Access|Iterator|Object)|Bad(Function|Method)CallException|(Ca(?:ching|llbackFilter))Iterator|Collator|Collectable|Cond|Countable|CURLFile|Date(Interval|Period|Time(Interface|Immutable|Zone)?)?|Directory(Iterator)?|DomainException|DOM(Attr|CdataSection|CharacterData|Comment|Document(Fragment)?|Element|EntityReference|Implementation|NamedNodeMap|Node(list)?|ProcessingInstruction|Text|XPath)|(Error)?Exception|EmptyIterator|finfo|Ev(Check|Child|Embed|Fork|Idle|Io|Loop|Periodic|Prepare|Signal|Stat|Timer|Watcher)?|Event(Base|Buffer(Event)?|SslContext|Http(Request|Connection)?|Config|DnsBase|Util|Listener)?|FANNConnection|(Fil(?:ter|esystem))Iterator|Gender\\\\Gender|GlobIterator|Gmagick(Draw|Pixel)?|Haru(Annotation|Destination|Doc|Encoder|Font|Image|Outline|Page)|Http(((?:In|De)flate)?Stream|Message|Request(Pool)?|Response|QueryString)|HRTime\\\\(PerformanceCounter|StopWatch)|Intl(Calendar|((CodePoint|RuleBased)?Break|Parts)?Iterator|DateFormatter|TimeZone)|Imagick(Draw|Pixel(Iterator)?)?|InfiniteIterator|InvalidArgumentException|Iterator(Aggregate|Iterator)?|JsonSerializable|KTaglib_(MPEG_(File|AudioProperties)|Tag|ID3v2_(Tag|(AttachedPicture)?Frame))|Lapack|(L(?:ength|ocale|ogic))Exception|LimitIterator|Lua(Closure)?|Mongo(BinData|Client|Code|Collection|CommandCursor|Cursor(Exception)?|Date|DB(Ref)?|DeleteBatch|Grid(FS(Cursor|File)?)|Id|InsertBatch|Int(32|64)|Log|Pool|Regex|ResultException|Timestamp|UpdateBatch|Write(Batch|ConcernException))?|Memcache(d)?|MessageFormatter|MultipleIterator|Mutex|mysqli(_(driver|stmt|warning|result))?|MysqlndUh(Connection|PreparedStatement)|NoRewindIterator|Normalizer|NumberFormatter|OCI-(Collection|Lob)|OuterIterator|(O(?:utOf(Bounds|Range)|verflow))Exception|ParentIterator|PDO(Statement)?|Phar(Data|FileInfo)?|php_user_filter|Pool|QuickHash(Int(S(?:et|tringHash))|StringIntHash)|Recursive(Array|Caching|Directory|Fallback|Filter|Iterator|Regex|Tree)?Iterator|Reflection(Attribute|Class(Constant)?|Constant|Enum((?:Unit|Backed)Case)?|Fiber|Function(Abstract)?|Generator|(Named|Union|Intersection)?Type|Method|Object|Parameter|Property|Reference|(Zend)?Extension)?|RangeException|Reflector|RegexIterator|ResourceBundle|RuntimeException|RRD(Creator|Graph|Updater)|SAM(Connection|Message)|SCA(_((?:Soap|Local)Proxy))?|SDO_(DAS_(ChangeSummary|Data(Factory|Object)|Relational|Setting|XML(_Document)?)|Data(Factory|Object)|Exception|List|Model_(Property|ReflectionDataObject|Type)|Sequence)|SeekableIterator|Serializable|SessionHandler(Interface)?|SimpleXML(Iterator|Element)|SNMP|Soap(Client|Fault|Header|Param|Server|Var)|SphinxClient|Spoofchecker|Spl(DoublyLinkedList|Enum|File(Info|Object)|FixedArray|(M(?:ax|in))?Heap|Observer|ObjectStorage|(Priority)?Queue|Stack|Subject|Type|TempFileObject)|SQLite(3(Result|Stmt)?|Database|Result|Unbuffered)|stdClass|streamWrapper|SVM(Model)?|Swish(Result(s)?|Search)?|Sync(Event|Mutex|ReaderWriter|Semaphore)|Thread(ed)?|tidy(Node)?|TokyoTyrant(Table|Iterator|Query)?|Transliterator|Traversable|UConverter|(Un(?:derflow|expectedValue))Exception|V8Js(Exception)?|Varnish(Admin|Log|Stat)|Worker|Weak(Map|Ref)|XML(Diff\\\\(Base|DOM|File|Memory)|Reader|Writer)|XsltProcessor|Yaf_(Route_(Interface|Map|Regex|Rewrite|Simple|Supervar)|Action_Abstract|Application|Config_(Simple|Ini|Abstract)|Controller_Abstract|Dispatcher|Exception|Loader|Plugin_Abstract|Registry|Request_(Abstract|Simple|Http)|Response_Abstract|Router|Session|View_(Simple|Interface))|Yar_(Client(_Exception)?|Concurrent_Client|Server(_Exception)?)|ZipArchive|ZMQ(Context|Device|Poll|Socket)?)\\b`,`dgiv`,{lazyCompile:!0}),name:`support.class.builtin.php`}]},"class-constant":{patterns:[{captures:{1:{name:`storage.type.const.php`},2:{patterns:[{include:`#php-types`}]},3:{name:`constant.other.php`}},match:RegExp(`\\b(const)\\p{space}+(?:((?:\\?\\p{space}*)?[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\))(?:\\p{space}*[\\&\\|]\\p{space}*(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\)))+)\\p{space}+)?([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`)}]},"class-extends":{patterns:[{begin:RegExp(`(extends)\\p{space}+`,`dgiv`),beginCaptures:{1:{name:`storage.modifier.extends.php`}},end:RegExp(`(?=[^0-9A-Z\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),patterns:[{include:`#comments`},{include:`#inheritance-single`}]}]},"class-implements":{patterns:[{begin:RegExp(`(implements)\\p{space}+`,`dgiv`),beginCaptures:{1:{name:`storage.modifier.implements.php`}},end:RegExp(`(?=\\{)`,`dgv`),patterns:[{include:`#comments`},{match:RegExp(`,`,`dgv`),name:`punctuation.separator.classes.php`},{include:`#inheritance-single`}]}]},"class-name":{patterns:[{begin:RegExp(`(?=\\\\?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*\\\\)`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(?![0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`support.class.php`}},patterns:[{include:`#namespace`}]},{include:`#class-builtin`},{begin:RegExp(`(?=[\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(?![0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`support.class.php`}},patterns:[{include:`#namespace`}]}]},comments:{patterns:[{begin:RegExp(`\\/\\*\\*(?=\\p{space})`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.php`}},end:RegExp(`\\*\\/`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.php`}},name:`comment.block.documentation.phpdoc.php`,patterns:[{include:`#php_doc`}]},{begin:RegExp(`\\/\\*`,`dgv`),captures:{0:{name:`punctuation.definition.comment.php`}},end:RegExp(`\\*\\/`,`dgv`),name:`comment.block.php`},{begin:RegExp(`(^\\p{space}+)?(?=\\/\\/)`,`dgv`),beginCaptures:{1:{name:`punctuation.whitespace.comment.leading.php`}},end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`\\/\\/`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.php`}},end:RegExp(`\\n|(?=\\?>)`,`dgv`),name:`comment.line.double-slash.php`}]},{begin:RegExp(`(^\\p{space}+)?(?=#)(?!#\\[)`,`dgv`),beginCaptures:{1:{name:`punctuation.whitespace.comment.leading.php`}},end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),patterns:[{begin:RegExp(`#`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.comment.php`}},end:RegExp(`\\n|(?=\\?>)`,`dgv`),name:`comment.line.number-sign.php`}]}]},constants:{patterns:[{match:RegExp(`\\b(TRUE|FALSE|NULL|__(FILE|DIR|FUNCTION|CLASS|METHOD|LINE|NAMESPACE)__|ON|OFF|YES|NO|NL|BR|TAB)\\b`,`dgiv`),name:`constant.language.php`},{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:RegExp(`(\\\\)?\\b(DEFAULT_INCLUDE_PATH|EAR_(INSTALL|EXTENSION)_DIR|E_(ALL|COMPILE_(ERROR|WARNING)|CORE_(ERROR|WARNING)|DEPRECATED|ERROR|NOTICE|PARSE|RECOVERABLE_ERROR|STRICT|USER_(DEPRECATED|ERROR|NOTICE|WARNING)|WARNING)|PHP_(ROUND_HALF_(DOWN|EVEN|ODD|UP)|(MAJOR|MINOR|RELEASE)_VERSION|MAXPATHLEN|BINDIR|SHLIB_SUFFIX|SYSCONFDIR|SAPI|CONFIG_FILE_(PATH|SCAN_DIR)|INT_(MAX|SIZE)|ZTS|OS|OUTPUT_HANDLER_(START|CONT|END)|DEBUG|DATADIR|URL_(SCHEME|HOST|USER|PORT|PASS|PATH|QUERY|FRAGMENT)|PREFIX|EXTRA_VERSION|EXTENSION_DIR|EOL|VERSION(_ID)?|WINDOWS_(NT_(SERVER|DOMAIN_CONTROLLER|WORKSTATION)|VERSION_(M(?:AJOR|INOR))|BUILD|SUITEMASK|SP_(M(?:AJOR|INOR))|PRODUCTTYPE|PLATFORM)|LIBDIR|LOCALSTATEDIR)|STD(ERR|IN|OUT)|ZEND_(DEBUG_BUILD|THREAD_SAFE))\\b`,`dgv`),name:`support.constant.core.php`},{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:RegExp(`(\\\\)?\\b(__COMPILER_HALT_OFFSET__|AB(MON_([1-9]|10|11|12)|DAY[1-7])|AM_STR|ASSERT_(ACTIVE|BAIL|CALLBACK_QUIET_EVAL|WARNING)|ALT_DIGITS|CASE_(UPPER|LOWER)|CHAR_MAX|CONNECTION_(ABORTED|NORMAL|TIMEOUT)|CODESET|COUNT_(NORMAL|RECURSIVE)|CREDITS_(ALL|DOCS|FULLPAGE|GENERAL|GROUP|MODULES|QA|SAPI)|CRYPT_(BLOWFISH|EXT_DES|MD5|SHA(256|512)|SALT_LENGTH|STD_DES)|CURRENCY_SYMBOL|D_(T_)?FMT|DATE_(ATOM|COOKIE|ISO8601|RFC(822|850|1036|1123|2822|3339)|RSS|W3C)|DAY_[1-7]|DECIMAL_POINT|DIRECTORY_SEPARATOR|ENT_(COMPAT|IGNORE|(NO)?QUOTES)|EXTR_(IF_EXISTS|OVERWRITE|PREFIX_(ALL|IF_EXISTS|INVALID|SAME)|REFS|SKIP)|ERA(_(D_(T_)?FMT)|T_FMT|YEAR)?|FRAC_DIGITS|GROUPING|HASH_HMAC|HTML_(ENTITIES|SPECIALCHARS)|INF|INFO_(ALL|CREDITS|CONFIGURATION|ENVIRONMENT|GENERAL|LICENSEMODULES|VARIABLES)|INI_(ALL|CANNER_(NORMAL|RAW)|PERDIR|SYSTEM|USER)|INT_(CURR_SYMBOL|FRAC_DIGITS)|LC_(ALL|COLLATE|CTYPE|MESSAGES|MONETARY|NUMERIC|TIME)|LOCK_(EX|NB|SH|UN)|LOG_(ALERT|AUTH(PRIV)?|CRIT|CRON|CONS|DAEMON|DEBUG|EMERG|ERR|INFO|LOCAL[1-7]|LPR|KERN|MAIL|NEWS|NODELAY|NOTICE|NOWAIT|ODELAY|PID|PERROR|WARNING|SYSLOG|UCP|USER)|M_(1_PI|SQRT(1_2|[23]|PI)|2_(SQRT)?PI|PI(_([24]))?|E(ULER)?|LN(10|2|PI)|LOG(10|2)E)|MON_([1-9]|10|11|12|DECIMAL_POINT|GROUPING|THOUSANDS_SEP)|N_(CS_PRECEDES|SEP_BY_SPACE|SIGN_POSN)|NAN|NEGATIVE_SIGN|NO(EXPR|STR)|P_(CS_PRECEDES|SEP_BY_SPACE|SIGN_POSN)|PM_STR|POSITIVE_SIGN|PATH(_SEPARATOR|INFO_(EXTENSION|(BASE|DIR|FILE)NAME))|RADIXCHAR|SEEK_(CUR|END|SET)|SORT_(ASC|DESC|LOCALE_STRING|REGULAR|STRING)|STR_PAD_(BOTH|LEFT|RIGHT)|T_FMT(_AMPM)?|THOUSEP|THOUSANDS_SEP|UPLOAD_ERR_(CANT_WRITE|EXTENSION|(FORM|INI)_SIZE|NO_(FILE|TMP_DIR)|OK|PARTIAL)|YES(EXPR|STR))\\b`,`dgv`),name:`support.constant.std.php`},{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:new X(`(\\\\)?\\b(GLOB_(MARK|BRACE|NO(SORT|CHECK|ESCAPE)|ONLYDIR|ERR|AVAILABLE_FLAGS)|XML_(SAX_IMPL|(DTD|DOCUMENT(_(FRAG|TYPE))?|HTML_DOCUMENT|NOTATION|NAMESPACE_DECL|PI|COMMENT|DATA_SECTION|TEXT)_NODE|OPTION_(SKIP_(TAGSTART|WHITE)|CASE_FOLDING|TARGET_ENCODING)|ERROR_((BAD_CHAR|(ATTRIBUTE_EXTERNAL|BINARY|PARAM|RECURSIVE)_ENTITY)_REF|MISPLACED_XML_PI|SYNTAX|NONE|NO_(MEMORY|ELEMENTS)|TAG_MISMATCH|INCORRECT_ENCODING|INVALID_TOKEN|DUPLICATE_ATTRIBUTE|UNCLOSED_(CDATA_SECTION|TOKEN)|UNDEFINED_ENTITY|UNKNOWN_ENCODING|JUNK_AFTER_DOC_ELEMENT|PARTIAL_CHAR|EXTERNAL_ENTITY_HANDLING|ASYNC_ENTITY)|ENTITY_(((REF|DECL)_)?NODE)|ELEMENT(_DECL)?_NODE|LOCAL_NAMESPACE|ATTRIBUTE_(N(?:MTOKEN(S)?|OTATION|ODE))|CDATA|ID(REF(S)?)?|DECL_NODE|ENTITY|ENUMERATION)|MHASH_(RIPEMD(128|160|256|320)|GOST|MD([245])|SHA(1|224|256|384|512)|SNEFRU256|HAVAL(128|160|192|224|256)|CRC23(B)?|TIGER(1(?:28|60))?|WHIRLPOOL|ADLER32)|MYSQL_(BOTH|NUM|CLIENT_(SSL|COMPRESS|IGNORE_SPACE|INTERACTIVE|ASSOC))|MYSQLI_(REPORT_(STRICT|INDEX|OFF|ERROR|ALL)|REFRESH_(GRANT|MASTER|BACKUP_LOG|STATUS|SLAVE|HOSTS|THREADS|TABLES|LOG)|READ_DEFAULT_(FILE|GROUP)|(GROUP|MULTIPLE_KEY|BINARY|BLOB)_FLAG|BOTH|STMT_ATTR_(CURSOR_TYPE|UPDATE_MAX_LENGTH|PREFETCH_ROWS)|STORE_RESULT|SERVER_QUERY_(NO_((GOOD_)?INDEX_USED)|WAS_SLOW)|SET_(CHARSET_NAME|FLAG)|NO_(D(?:EFAULT_VALUE_FLAG|ATA))|NOT_NULL_FLAG|NUM(_FLAG)?|CURSOR_TYPE_(READ_ONLY|SCROLLABLE|NO_CURSOR|FOR_UPDATE)|CLIENT_(SSL|NO_SCHEMA|COMPRESS|IGNORE_SPACE|INTERACTIVE|FOUND_ROWS)|TYPE_(GEOMETRY|((MEDIUM|LONG|TINY)_)?BLOB|BIT|SHORT|STRING|SET|YEAR|NULL|NEWDECIMAL|NEWDATE|CHAR|TIME(STAMP)?|TINY|INT24|INTERVAL|DOUBLE|DECIMAL|DATE(TIME)?|ENUM|VAR_STRING|FLOAT|LONG(LONG)?)|TIME_STAMP_FLAG|INIT_COMMAND|ZEROFILL_FLAG|ON_UPDATE_NOW_FLAG|OPT_(NET_((CMD|READ)_BUFFER_SIZE)|CONNECT_TIMEOUT|INT_AND_FLOAT_NATIVE|LOCAL_INFILE)|DEBUG_TRACE_ENABLED|DATA_TRUNCATED|USE_RESULT|(ENUM|(PART|PRI|UNIQUE)_KEY|UNSIGNED)_FLAG|ASSOC|ASYNC|AUTO_INCREMENT_FLAG)|MCRYPT_(RC([26])|RIJNDAEL_(128|192|256)|RAND|GOST|XTEA|MODE_(STREAM|NOFB|CBC|CFB|OFB|ECB)|MARS|BLOWFISH(_COMPAT)?|SERPENT|SKIPJACK|SAFER(64|128|PLUS)|CRYPT|CAST_(128|256)|TRIPLEDES|THREEWAY|TWOFISH|IDEA|(3)?DES|DECRYPT|DEV_(U)?RANDOM|PANAMA|ENCRYPT|ENIGNA|WAKE|LOKI97|ARCFOUR(_IV)?)|STREAM_(REPORT_ERRORS|MUST_SEEK|MKDIR_RECURSIVE|BUFFER_(NONE|FULL|LINE)|SHUT_(RD)?WR|SOCK_(RDM|RAW|STREAM|SEQPACKET|DGRAM)|SERVER_(BIND|LISTEN)|NOTIFY_(REDIRECTED|RESOLVE|MIME_TYPE_IS|SEVERITY_(INFO|ERR|WARN)|COMPLETED|CONNECT|PROGRESS|FILE_SIZE_IS|FAILURE|AUTH_(RE(?:QUIRED|SULT)))|CRYPTO_METHOD_((SSLv2(3)?|SSLv3|TLS)_(CLIENT|SERVER))|CLIENT_((ASYNC_)?CONNECT|PERSISTENT)|CAST_(AS_STREAM|FOR_SELECT)|(I(?:GNORE|S))_URL|IPPROTO_(RAW|TCP|ICMP|IP|UDP)|OOB|OPTION_(READ_(BUFFER|TIMEOUT)|BLOCKING|WRITE_BUFFER)|URL_STAT_(LINK|QUIET)|USE_PATH|PEEK|PF_(INET(6)?|UNIX)|ENFORCE_SAFE_MODE|FILTER_(ALL|READ|WRITE))|SUNFUNCS_RET_(DOUBLE|STRING|TIMESTAMP)|SQLITE_(READONLY|ROW|MISMATCH|MISUSE|BOTH|BUSY|SCHEMA|NOMEM|NOTFOUND|NOTADB|NOLFS|NUM|CORRUPT|CONSTRAINT|CANTOPEN|TOOBIG|INTERRUPT|INTERNAL|IOERR|OK|DONE|PROTOCOL|PERM|ERROR|EMPTY|FORMAT|FULL|LOCKED|ABORT|ASSOC|AUTH)|SQLITE3_(BOTH|BLOB|NUM|NULL|TEXT|INTEGER|OPEN_(READ(ONLY|WRITE)|CREATE)|FLOAT_ASSOC)|CURL(M_(BAD_((EASY)?HANDLE)|CALL_MULTI_PERFORM|INTERNAL_ERROR|OUT_OF_MEMORY|OK)|MSG_DONE|SSH_AUTH_(HOST|NONE|DEFAULT|PUBLICKEY|PASSWORD|KEYBOARD)|CLOSEPOLICY_(SLOWEST|CALLBACK|OLDEST|LEAST_(RECENTLY_USED|TRAFFIC)|INFO_(REDIRECT_(COUNT|TIME)|REQUEST_SIZE|SSL_VERIFYRESULT|STARTTRANSFER_TIME|(S(?:IZE|PEED))_((?:DOWN|UP)LOAD)|HTTP_CODE|HEADER_(OUT|SIZE)|NAMELOOKUP_TIME|CONNECT_TIME|CONTENT_(TYPE|LENGTH_((?:DOWN|UP)LOAD))|CERTINFO|TOTAL_TIME|PRIVATE|PRETRANSFER_TIME|EFFECTIVE_URL|FILETIME)|OPT_(RESUME_FROM|RETURNTRANSFER|REDIR_PROTOCOLS|REFERER|READ(DATA|FUNCTION)|RANGE|RANDOM_FILE|MAX(CONNECTS|REDIRS)|BINARYTRANSFER|BUFFERSIZE|SSH_(HOST_PUBLIC_KEY_MD5|(P(?:RIVATE|UBLIC))_KEYFILE)|AUTH_TYPES)|SSL(CERT(TYPE|PASSWD)?|ENGINE(_DEFAULT)?|VERSION|KEY(TYPE|PASSWD)?)|SSL_(CIPHER_LIST|VERIFY(HOST|PEER))|STDERR|HTTP(GET|HEADER|200ALIASES|_VERSION|PROXYTUNNEL|AUTH)|HEADER(FUNCTION)?|NO(BODY|SIGNAL|PROGRESS)|NETRC|CRLF|CONNECTTIMEOUT(_MS)?|COOKIE(SESSION|JAR|FILE)?|CUSTOMREQUEST|CERTINFO|CLOSEPOLICY|CA(INFO|PATH)|TRANSFERTEXT|TCP_NODELAY|TIME(CONDITION|OUT(_MS)?|VALUE)|INTERFACE|INFILE(SIZE)?|IPRESOLVE|DNS_(CACHE_TIMEOUT|USE_GLOBAL_CACHE)|URL|USER(AGENT|PWD)|UNRESTRICTED_AUTH|UPLOAD|PRIVATE|PROGRESSFUNCTION|PROXY(TYPE|USERPWD|PORT|AUTH)?|PROTOCOLS|PORT|POST(REDIR|QUOTE|FIELDS)?|PUT|EGDSOCKET|ENCODING|VERBOSE|KRB4LEVEL|KEYPASSWD|QUOTE|FRESH_CONNECT|FTP(APPEND|LISTONLY|PORT|SSLAUTH)|FTP_(SSL|SKIP_PASV_IP|CREATE_MISSING_DIRS|USE_EP(RT|SV)|FILEMETHOD)|FILE(TIME)?|FORBID_REUSE|FOLLOWLOCATION|FAILONERROR|WRITE(FUNCTION|HEADER)|LOW_SPEED_(LIMIT|TIME)|AUTOREFERER)|PROXY_(HTTP|SOCKS([45]))|PROTO_(SCP|SFTP|HTTP(S)?|TELNET|TFTP|DICT|FTP(S)?|FILE|LDAP(S)?|ALL)|E_((RE(?:CV|AD))_ERROR|GOT_NOTHING|MALFORMAT_USER|BAD_(CONTENT_ENCODING|CALLING_ORDER|PASSWORD_ENTERED|FUNCTION_ARGUMENT)|SSH|SSL_(CIPHER|CONNECT_ERROR|CERTPROBLEM|CACERT|PEER_CERTIFICATE|ENGINE_(NOTFOUND|SETFAILED))|SHARE_IN_USE|SEND_ERROR|HTTP_(RANGE_ERROR|NOT_FOUND|PORT_FAILED|POST_ERROR)|COULDNT_(RESOLVE_(HOST|PROXY)|CONNECT)|TOO_MANY_REDIRECTS|TELNET_OPTION_SYNTAX|OBSOLETE|OUT_OF_MEMORY|OPERATION|TIMEOUTED|OK|URL_MALFORMAT(_USER)?|UNSUPPORTED_PROTOCOL|UNKNOWN_TELNET_OPTION|PARTIAL_FILE|FTP_(BAD_DOWNLOAD_RESUME|SSL_FAILED|COULDNT_(RETR_FILE|GET_SIZE|STOR_FILE|SET_(BINARY|ASCII)|USE_REST)|CANT_(GET_HOST|RECONNECT)|USER_PASSWORD_INCORRECT|PORT_FAILED|QUOTE_ERROR|WRITE_ERROR|WEIRD_((PASS|PASV|SERVER|USER)_REPLY|227_FORMAT)|ACCESS_DENIED)|FILESIZE_EXCEEDED|FILE_COULDNT_READ_FILE|FUNCTION_NOT_FOUND|FAILED_INIT|WRITE_ERROR|LIBRARY_NOT_FOUND|LDAP_(SEARCH_FAILED|CANNOT_BIND|INVALID_URL)|ABORTED_BY_CALLBACK)|VERSION_NOW|FTP(METHOD_(MULTI|SINGLE|NO)CWD|SSL_(ALL|NONE|CONTROL|TRY)|AUTH_(DEFAULT|SSL|TLS))|AUTH_(ANY(SAFE)?|BASIC|DIGEST|GSSNEGOTIATE|NTLM))|CURL_(HTTP_VERSION_(1_([01])|NONE)|NETRC_(REQUIRED|IGNORED|OPTIONAL)|TIMECOND_(IF(UN)?MODSINCE|LASTMOD)|IPRESOLVE_(V([46])|WHATEVER)|VERSION_(SSL|IPV6|KERBEROS4|LIBZ))|IMAGETYPE_(GIF|XBM|BMP|SWF|COUNT|TIFF_(MM|II)|ICO|IFF|UNKNOWN|JB2|JPX|JP2|JPC|JPEG(2000)?|PSD|PNG|WBMP)|INPUT_(REQUEST|GET|SERVER|SESSION|COOKIE|POST|ENV)|ICONV_(MIME_DECODE_(STRICT|CONTINUE_ON_ERROR)|IMPL|VERSION)|DNS_(MX|SRV|SOA|HINFO|NS|NAPTR|CNAME|TXT|PTR|ANY|ALL|AAAA|A(6)?)|DOM(STRING_SIZE_ERR)|DOM_((SYNTAX|HIERARCHY_REQUEST|NO_((?:MODIFICATION|DATA)_ALLOWED)|NOT_(FOUND|SUPPORTED)|NAMESPACE|INDEX_SIZE|USE_ATTRIBUTE|VALID_(MODIFICATION|STATE|CHARACTER|ACCESS)|PHP|VALIDATION|WRONG_DOCUMENT)_ERR)|JSON_(HEX_(TAG|QUOT|AMP|APOS)|NUMERIC_CHECK|ERROR_(SYNTAX|STATE_MISMATCH|NONE|CTRL_CHAR|DEPTH|UTF8)|FORCE_OBJECT)|PREG_((D_UTF8(_OFFSET)?|NO|INTERNAL|(BACKTRACK|RECURSION)_LIMIT)_ERROR|GREP_INVERT|SPLIT_(NO_EMPTY|(DELIM|OFFSET)_CAPTURE)|SET_ORDER|OFFSET_CAPTURE|PATTERN_ORDER)|PSFS_(PASS_ON|ERR_FATAL|FEED_ME|FLAG_(NORMAL|FLUSH_(CLOSE|INC)))|PCRE_VERSION|POSIX_(([FRWX])_OK|S_IF(REG|BLK|SOCK|CHR|IFO))|FNM_(NOESCAPE|CASEFOLD|PERIOD|PATHNAME)|FILTER_(REQUIRE_(SCALAR|ARRAY)|NULL_ON_FAILURE|CALLBACK|DEFAULT|UNSAFE_RAW|SANITIZE_(MAGIC_QUOTES|STRING|STRIPPED|SPECIAL_CHARS|NUMBER_(INT|FLOAT)|URL|EMAIL|ENCODED|FULL_SPCIAL_CHARS)|VALIDATE_(REGEXP|BOOLEAN|INT|IP|URL|EMAIL|FLOAT)|FORCE_ARRAY|FLAG_(SCHEME_REQUIRED|STRIP_(BACKTICK|HIGH|LOW)|HOST_REQUIRED|NONE|NO_(RES|PRIV)_RANGE|ENCODE_QUOTES|IPV([46])|PATH_REQUIRED|EMPTY_STRING_NULL|ENCODE_(HIGH|LOW|AMP)|QUERY_REQUIRED|ALLOW_(SCIENTIFIC|HEX|THOUSAND|OCTAL|FRACTION)))|FILE_(BINARY|SKIP_EMPTY_LINES|NO_DEFAULT_CONTEXT|TEXT|IGNORE_NEW_LINES|USE_INCLUDE_PATH|APPEND)|FILEINFO_(RAW|MIME(_(ENCODING|TYPE))?|SYMLINK|NONE|CONTINUE|DEVICES|PRESERVE_ATIME)|FORCE_(DEFLATE|GZIP)|LIBXML_(XINCLUDE|NSCLEAN|NO(XMLDECL|BLANKS|NET|CDATA|ERROR|EMPTYTAG|ENT|WARNING)|COMPACT|DTD(VALID|LOAD|ATTR)|((DOTTED|LOADED)_)?VERSION|PARSEHUGE|ERR_(NONE|ERROR|FATAL|WARNING)))\\b`,`dgv`,{lazyCompile:!0}),name:`support.constant.ext.php`},{captures:{1:{name:`punctuation.separator.inheritance.php`}},match:RegExp(`(\\\\)?\\b(T_(RETURN|REQUIRE(_ONCE)?|GOTO|GLOBAL|(MINUS|MOD|MUL|XOR)_EQUAL|METHOD_C|ML_COMMENT|BREAK|BOOL_CAST|BOOLEAN_(AND|OR)|BAD_CHARACTER|SR(_EQUAL)?|STRING(_CAST|VARNAME)?|START_HEREDOC|STATIC|SWITCH|SL(_EQUAL)?|HALT_COMPILER|NS_(C|SEPARATOR)|NUM_STRING|NEW|NAMESPACE|CHARACTER|COMMENT|CONSTANT(_ENCAPSED_STRING)?|CONCAT_EQUAL|CONTINUE|CURLY_OPEN|CLOSE_TAG|CLONE|CLASS(_C)?|CASE|CATCH|TRY|THROW|IMPLEMENTS|ISSET|IS_((GREATER|SMALLER)_OR_EQUAL|(NOT_)?(IDENTICAL|EQUAL))|INSTANCEOF|INCLUDE(_ONCE)?|INC|INT_CAST|INTERFACE|INLINE_HTML|IF|OR_EQUAL|OBJECT_(CAST|OPERATOR)|OPEN_TAG(_WITH_ECHO)?|OLD_FUNCTION|DNUMBER|DIR|DIV_EQUAL|DOC_COMMENT|DOUBLE_(ARROW|CAST|COLON)|DOLLAR_OPEN_CURLY_BRACES|DO|DEC|DECLARE|DEFAULT|USE|UNSET(_CAST)?|PRINT|PRIVATE|PROTECTED|PUBLIC|PLUS_EQUAL|PAAMAYIM_NEKUDOTAYIM|EXTENDS|EXIT|EMPTY|ENCAPSED_AND_WHITESPACE|END(SWITCH|IF|DECLARE|FOR(EACH)?|WHILE)|END_HEREDOC|ECHO|EVAL|ELSE(IF)?|VAR(IABLE)?|FINAL|FILE|FOR(EACH)?|FUNC_C|FUNCTION|WHITESPACE|WHILE|LNUMBER|LIST|LINE|LOGICAL_(AND|OR|XOR)|ARRAY_(CAST)?|ABSTRACT|AS|AND_EQUAL))\\b`,`dgv`),name:`support.constant.parser-token.php`},{match:RegExp(`[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*`,`dgiv`),name:`constant.other.php`}]},"function-call":{patterns:[{begin:RegExp(`(\\\\?(?)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},name:`meta.function-call.php`,patterns:[{include:`#named-arguments`},{include:`$self`}]},{begin:RegExp(`(\\\\)?(?)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},name:`meta.function-call.php`,patterns:[{include:`#named-arguments`},{include:`$self`}]},{match:RegExp(`\\b(print|echo)\\b`,`dgiv`),name:`support.function.construct.output.php`}]},"function-parameters":{patterns:[{include:`#attribute`},{include:`#comments`},{match:RegExp(`,`,`dgv`),name:`punctuation.separator.delimiter.php`},{captures:{1:{patterns:[{include:`#php-types`}]},2:{name:`variable.other.php`},3:{name:`storage.modifier.reference.php`},4:{name:`keyword.operator.variadic.php`},5:{name:`punctuation.definition.variable.php`}},match:RegExp(`(?:((?:\\?\\p{space}*)?[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\))(?:\\p{space}*[\\&\\|]\\p{space}*(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\)))+)\\p{space}+)?((?:(&)\\p{space}*)?(\\.\\.\\.)(\\$)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(?=\\p{space}*(?:[\\)\\,]|\\/[\\*\\/]|#|(?=\\n?$)))`,`dgiv`),name:`meta.function.parameter.variadic.php`},{begin:RegExp(`((?:\\?\\p{space}*)?[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\))(?:\\p{space}*[\\&\\|]\\p{space}*(?:[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+|\\(\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(?:\\p{space}*&\\p{space}*[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)+\\p{space}*\\)))+)\\p{space}+((?:(&)\\p{space}*)?(\\$)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)`,`dgiv`),beginCaptures:{1:{patterns:[{include:`#php-types`}]},2:{name:`variable.other.php`},3:{name:`storage.modifier.reference.php`},4:{name:`punctuation.definition.variable.php`}},end:RegExp(`(?=\\p{space}*(?:[\\)\\,]|\\/[\\*\\/]|#))`,`dgv`),name:`meta.function.parameter.typehinted.php`,patterns:[{begin:RegExp(`=`,`dgv`),beginCaptures:{0:{name:`keyword.operator.assignment.php`}},end:RegExp(`(?=\\p{space}*(?:[\\)\\,]|\\/[\\*\\/]|#))`,`dgv`),patterns:[{include:`#parameter-default-types`}]}]},{captures:{1:{name:`variable.other.php`},2:{name:`storage.modifier.reference.php`},3:{name:`punctuation.definition.variable.php`}},match:RegExp(`((?:(&)\\p{space}*)?(\\$)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(?=\\p{space}*(?:[\\)\\,]|\\/[\\*\\/]|#|(?=\\n?$)))`,`dgiv`),name:`meta.function.parameter.no-default.php`},{begin:RegExp(`((?:(&)\\p{space}*)?(\\$)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)\\p{space}*(=)\\p{space}*`,`dgiv`),beginCaptures:{1:{name:`variable.other.php`},2:{name:`storage.modifier.reference.php`},3:{name:`punctuation.definition.variable.php`},4:{name:`keyword.operator.assignment.php`}},end:RegExp(`(?=\\p{space}*(?:[\\)\\,]|\\/[\\*\\/]|#))`,`dgv`),name:`meta.function.parameter.default.php`,patterns:[{include:`#parameter-default-types`}]}]},heredoc:{patterns:[{begin:RegExp(`(?=<<<\\p{space}*("?)([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(\\1)\\p{space}*(?=\\n?$))`,`dgiv`),end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),name:`string.unquoted.heredoc.php`,patterns:[{include:`#heredoc_interior`}]},{begin:RegExp(`(?=<<<\\p{space}*'([A-Z_a-z]+[0-9A-Z_a-z]*)'\\p{space}*(?=\\n?$))`,`dgv`),end:new X(`(?!^)`,`dgv`,{strategy:`clip_search`}),name:`string.unquoted.nowdoc.php`,patterns:[{include:`#nowdoc_interior`}]}]},heredoc_interior:{patterns:[{begin:RegExp(`(<<<)\\p{space}*("?)(HTML)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.html`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.html`,patterns:[{include:`#interpolation`},{include:`text.html.basic`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(XML)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.xml`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.xml`,patterns:[{include:`#interpolation`},{include:`text.xml`}]},{begin:RegExp(`(<<<)\\p{space}*("?)([DS]QL)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.sql`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.sql`,patterns:[{include:`#interpolation`},{include:`source.sql`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(J(?:AVASCRIPT|S))(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.js`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.js`,patterns:[{include:`#interpolation`},{include:`source.js`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(JSON)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.json`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.json`,patterns:[{include:`#interpolation`},{include:`source.json`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(CSS)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.css`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.css`,patterns:[{include:`#interpolation`},{include:`source.css`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(REGEXP?)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`string.regexp.heredoc.php`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},patterns:[{include:`#interpolation`},{match:RegExp(`(\\\\){1,2}[\\]\\$\\.\\[\\^\\{\\}]`,`dgv`),name:`constant.character.escape.regex.php`},{captures:{1:{name:`punctuation.definition.arbitrary-repitition.php`},3:{name:`punctuation.definition.arbitrary-repitition.php`}},match:RegExp(`(\\{)\\p{Nd}+(,\\p{Nd}+)?(\\})`,`dgv`),name:`string.regexp.arbitrary-repitition.php`},{begin:RegExp(`\\[(?:\\^?\\])?`,`dgv`),captures:{0:{name:`punctuation.definition.character-class.php`}},end:RegExp(`\\]`,`dgv`),name:`string.regexp.character-class.php`,patterns:[{match:RegExp(`\\\\[\\]'\\[\\\\]`,`dgv`),name:`constant.character.escape.php`}]},{match:RegExp(`[\\$\\*\\+\\^]`,`dgv`),name:`keyword.operator.regexp.php`},{begin:RegExp(`(?<=^|\\p{space})(#)\\p{space}(?=[\\-\\t \\!\\,\\.0-9\\?_a-z\\x7F-\\u{10FFFF}[^\\x00-\\x7F]]*(?=\\n?$))`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.comment.php`}},end:RegExp(`(?=\\n?$)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.php`}},name:`comment.line.number-sign.php`}]},{begin:RegExp(`(<<<)\\p{space}*("?)(BLADE)(\\2)(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.html.php.blade`,end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.heredoc.php`}},name:`meta.embedded.php.blade`,patterns:[{include:`#interpolation`}]},{begin:RegExp(`(<<<)\\p{space}*("?)([_a-z\\x7F-\\u{10FFFF}]+[0-9_a-z\\x7F-\\u{10FFFF}]*)(\\2)(\\p{space}*)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.string.php`},3:{name:`keyword.operator.heredoc.php`},5:{name:`invalid.illegal.trailing-whitespace.php`}},end:RegExp(`^\\p{space}*(\\3)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()()`,`dgv`),endCaptures:{1:{name:`keyword.operator.heredoc.php`}},patterns:[{include:`#interpolation`}]}]},"inheritance-single":{patterns:[{begin:RegExp(`(?=\\\\?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*\\\\)`,`dgiv`),end:RegExp(`([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(?=[^0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),endCaptures:{1:{name:`entity.other.inherited-class.php`}},patterns:[{include:`#namespace`}]},{include:`#class-builtin`},{include:`#namespace`},{match:RegExp(`[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*`,`dgiv`),name:`entity.other.inherited-class.php`}]},instantiation:{patterns:[{captures:{1:{name:`keyword.other.new.php`},2:{patterns:[{match:RegExp(`(parent|static|self)(?![0-9_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),name:`storage.type.php`},{include:`#class-name`},{include:`#variable-name`}]}},match:RegExp(`(new)\\p{space}+(?!class\\b)([\\$0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)(?![\\(0-9\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgiv`)},{begin:RegExp(`(new)\\p{space}+(?!class\\b)([\\$0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)\\p{space}*(\\()`,`dgiv`),beginCaptures:{1:{name:`keyword.other.new.php`},2:{patterns:[{match:RegExp(`(parent|static|self)(?![0-9_a-z\\x7F-\\u{10FFFF}])`,`dgiv`),name:`storage.type.php`},{include:`#class-name`},{include:`#variable-name`}]},3:{name:`punctuation.definition.arguments.begin.bracket.round.php`}},contentName:`meta.function-call.php`,end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},patterns:[{include:`#named-arguments`},{include:`$self`}]}]},"interface-extends":{patterns:[{begin:RegExp(`(extends)\\p{space}+`,`dgiv`),beginCaptures:{1:{name:`storage.modifier.extends.php`}},end:RegExp(`(?=\\{)`,`dgv`),patterns:[{include:`#comments`},{match:RegExp(`,`,`dgv`),name:`punctuation.separator.classes.php`},{include:`#inheritance-single`}]}]},interpolation:{patterns:[{match:RegExp(`\\\\[0-7]{1,3}`,`dgv`),name:`constant.character.escape.octal.php`},{match:RegExp(`\\\\x\\p{AHex}{1,2}`,`dgv`),name:`constant.character.escape.hex.php`},{match:RegExp(`\\\\u\\{\\p{AHex}+\\}`,`dgv`),name:`constant.character.escape.unicode.php`},{match:RegExp(`\\\\[\\$\\\\efnrtv]`,`dgv`),name:`constant.character.escape.php`},{begin:RegExp(`\\{(?=\\$[^\\n]*?\\})`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.variable.php`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.variable.php`}},patterns:[{include:`$self`}]},{include:`#variable-name`}]},interpolation_double_quoted:{patterns:[{match:RegExp(`\\\\"`,`dgv`),name:`constant.character.escape.php`},{include:`#interpolation`}]},"invoke-call":{captures:{1:{name:`variable.other.php`},2:{name:`punctuation.definition.variable.php`}},match:RegExp(`((\\$+)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(?=\\p{space}*\\()`,`dgiv`),name:`meta.function-call.invoke.php`},match_statement:{patterns:[{match:RegExp(`\\p{space}+(?=match\\b)`,`dgv`)},{begin:RegExp(`\\bmatch\\b`,`dgv`),beginCaptures:{0:{name:`keyword.control.match.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.section.match-block.end.bracket.curly.php`}},name:`meta.match-statement.php`,patterns:[{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.match-expression.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.match-expression.end.bracket.round.php`}},patterns:[{include:`$self`}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.section.match-block.begin.bracket.curly.php`}},end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{match:RegExp(`=>`,`dgv`),name:`keyword.definition.arrow.php`},{include:`$self`}]}]}]},"named-arguments":{captures:{1:{name:`entity.name.variable.parameter.php`},2:{name:`punctuation.separator.colon.php`}},match:RegExp(`(?<=^|[\\(\\,])\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)\\p{space}*(:)(?!:)`,`dgiv`)},namespace:{begin:RegExp(`(?:(namespace)|[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?(\\\\)`,`dgiv`),beginCaptures:{1:{name:`variable.language.namespace.php`},2:{name:`punctuation.separator.inheritance.php`}},end:RegExp(`(?![0-9_a-z\\x7F-\\u{10FFFF}]*\\\\)`,`dgiv`),name:`support.other.namespace.php`,patterns:[{match:RegExp(`\\\\`,`dgv`),name:`punctuation.separator.inheritance.php`}]},nowdoc_interior:{patterns:[{begin:RegExp(`(<<<)\\p{space}*'(HTML)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.html`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.html`,patterns:[{include:`text.html.basic`}]},{begin:RegExp(`(<<<)\\p{space}*'(XML)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.xml`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.xml`,patterns:[{include:`text.xml`}]},{begin:RegExp(`(<<<)\\p{space}*'([DS]QL)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.sql`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.sql`,patterns:[{include:`source.sql`}]},{begin:RegExp(`(<<<)\\p{space}*'(J(?:AVASCRIPT|S))'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.js`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.js`,patterns:[{include:`source.js`}]},{begin:RegExp(`(<<<)\\p{space}*'(JSON)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.json`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.json`,patterns:[{include:`source.json`}]},{begin:RegExp(`(<<<)\\p{space}*'(CSS)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`source.css`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.css`,patterns:[{include:`source.css`}]},{begin:RegExp(`(<<<)\\p{space}*'(REGEXP?)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`string.regexp.nowdoc.php`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},patterns:[{match:RegExp(`(\\\\){1,2}[\\]\\$\\.\\[\\^\\{\\}]`,`dgv`),name:`constant.character.escape.regex.php`},{captures:{1:{name:`punctuation.definition.arbitrary-repitition.php`},3:{name:`punctuation.definition.arbitrary-repitition.php`}},match:RegExp(`(\\{)\\p{Nd}+(,\\p{Nd}+)?(\\})`,`dgv`),name:`string.regexp.arbitrary-repitition.php`},{begin:RegExp(`\\[(?:\\^?\\])?`,`dgv`),captures:{0:{name:`punctuation.definition.character-class.php`}},end:RegExp(`\\]`,`dgv`),name:`string.regexp.character-class.php`,patterns:[{match:RegExp(`\\\\[\\]'\\[\\\\]`,`dgv`),name:`constant.character.escape.php`}]},{match:RegExp(`[\\$\\*\\+\\^]`,`dgv`),name:`keyword.operator.regexp.php`},{begin:RegExp(`(?<=^|\\p{space})(#)\\p{space}(?=[\\-\\t \\!\\,\\.0-9\\?_a-z\\x7F-\\u{10FFFF}[^\\x00-\\x7F]]*(?=\\n?$))`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.comment.php`}},end:RegExp(`(?=\\n?$)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.comment.php`}},name:`comment.line.number-sign.php`}]},{begin:RegExp(`(<<<)\\p{space}*'(BLADE)'(\\p{space}*)(?=\\n?$)`,`dgv`),beginCaptures:{0:{name:`punctuation.section.embedded.begin.php`},1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},contentName:`text.html.php.blade`,end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{0:{name:`punctuation.section.embedded.end.php`},1:{name:`keyword.operator.nowdoc.php`}},name:`meta.embedded.php.blade`},{begin:RegExp(`(<<<)\\p{space}*'([_a-z\\x7F-\\u{10FFFF}]+[0-9_a-z\\x7F-\\u{10FFFF}]*)'(\\p{space}*)`,`dgiv`),beginCaptures:{1:{name:`punctuation.definition.string.php`},2:{name:`keyword.operator.nowdoc.php`},3:{name:`invalid.illegal.trailing-whitespace.php`}},end:RegExp(`^\\p{space}*(\\2)(?![0-9A-Z_a-z\\x7F-\\u{10FFFF}])()`,`dgv`),endCaptures:{1:{name:`keyword.operator.nowdoc.php`}}}]},null_coalescing:{match:RegExp(`\\?\\?`,`dgv`),name:`keyword.operator.null-coalescing.php`},numbers:{patterns:[{match:RegExp(`0[Xx]\\p{AHex}+(?:_\\p{AHex}+)*`,`dgv`),name:`constant.numeric.hex.php`},{match:RegExp(`0[Bb][01]+(?:_[01]+)*`,`dgv`),name:`constant.numeric.binary.php`},{match:RegExp(`0[Oo][0-7]+(?:_[0-7]+)*`,`dgv`),name:`constant.numeric.octal.php`},{match:RegExp(`0(?:_?[0-7]+)+`,`dgv`),name:`constant.numeric.octal.php`},{captures:{1:{name:`punctuation.separator.decimal.period.php`},2:{name:`punctuation.separator.decimal.period.php`}},match:RegExp(`(?:[0-9]+(?:_[0-9]+)*)?(\\.)[0-9]+(?:_[0-9]+)*(?:[Ee][\\-\\+]?[0-9]+(?:_[0-9]+)*)?|[0-9]+(?:_[0-9]+)*(\\.)(?:[0-9]+(?:_[0-9]+)*)?(?:[Ee][\\-\\+]?[0-9]+(?:_[0-9]+)*)?|[0-9]+(?:_[0-9]+)*[Ee][\\-\\+]?[0-9]+(?:_[0-9]+)*`,`dgv`),name:`constant.numeric.decimal.php`},{match:RegExp(`0|[1-9](?:_?[0-9]+)*`,`dgv`),name:`constant.numeric.decimal.php`}]},object:{patterns:[{begin:RegExp(`(\\??->)\\p{space}*(\\$?\\{)`,`dgv`),beginCaptures:{1:{name:`keyword.operator.class.php`},2:{name:`punctuation.definition.variable.php`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.variable.php`}},patterns:[{include:`$self`}]},{begin:RegExp(`(\\??->)\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)\\p{space}*(\\()`,`dgiv`),beginCaptures:{1:{name:`keyword.operator.class.php`},2:{name:`entity.name.function.php`},3:{name:`punctuation.definition.arguments.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},name:`meta.method-call.php`,patterns:[{include:`#named-arguments`},{include:`$self`}]},{captures:{1:{name:`keyword.operator.class.php`},2:{name:`variable.other.property.php`},3:{name:`punctuation.definition.variable.php`}},match:RegExp(`(\\??->)\\p{space}*((\\$+)?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?`,`dgiv`)}]},"parameter-default-types":{patterns:[{include:`#strings`},{include:`#numbers`},{include:`#string-backtick`},{include:`#variables`},{match:RegExp(`=>`,`dgv`),name:`keyword.operator.key.php`},{match:RegExp(`=`,`dgv`),name:`keyword.operator.assignment.php`},{match:RegExp(`&(?=\\p{space}*\\$)`,`dgv`),name:`storage.modifier.reference.php`},{begin:RegExp(`(array)\\p{space}*(\\()`,`dgv`),beginCaptures:{1:{name:`support.function.construct.php`},2:{name:`punctuation.definition.array.begin.bracket.round.php`}},end:RegExp(`\\)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.array.end.bracket.round.php`}},name:`meta.array.php`,patterns:[{include:`#parameter-default-types`}]},{begin:RegExp(`\\[`,`dgv`),beginCaptures:{0:{name:`punctuation.section.array.begin.php`}},end:RegExp(`\\]|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.section.array.end.php`}},patterns:[{include:`$self`}]},{include:`#instantiation`},{begin:RegExp(`(?=[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+(::)\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?)`,`dgiv`),end:RegExp(`(::)\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)?`,`dgiv`),endCaptures:{1:{name:`keyword.operator.class.php`},2:{name:`constant.other.class.php`}},patterns:[{include:`#class-name`}]},{include:`#constants`}]},"php-types":{patterns:[{match:RegExp(`\\?`,`dgv`),name:`keyword.operator.nullable-type.php`},{match:RegExp(`[\\&\\|]`,`dgv`),name:`punctuation.separator.delimiter.php`},{match:RegExp(`\\b(null|int|float|bool|string|array|object|callable|iterable|true|false|mixed|void)\\b`,`dgiv`),name:`keyword.other.type.php`},{match:RegExp(`\\b(parent|self)\\b`,`dgiv`),name:`storage.type.php`},{match:RegExp(`\\(`,`dgv`),name:`punctuation.definition.type.begin.bracket.round.php`},{match:RegExp(`\\)`,`dgv`),name:`punctuation.definition.type.end.bracket.round.php`},{include:`#class-name`}]},php_doc:{patterns:[{match:RegExp(`^(?!\\p{space}*\\*)[^\\n]*?(?:(?=\\*\\/)|(?=\\n?$)\\n?)`,`dgv`),name:`invalid.illegal.missing-asterisk.phpdoc.php`},{captures:{1:{name:`keyword.other.phpdoc.php`},3:{name:`storage.modifier.php`},4:{name:`invalid.illegal.wrong-access-type.phpdoc.php`}},match:RegExp(`^\\p{space}*\\*\\p{space}*(@access)\\p{space}+((p(?:ublic|rivate|rotected))|([^\\n]+))\\p{space}*(?=\\n?$)`,`dgv`)},{captures:{1:{name:`keyword.other.phpdoc.php`},2:{name:`markup.underline.link.php`}},match:RegExp(`(@xlink)\\p{space}+([^\\n]+)\\p{space}*(?=\\n?$)`,`dgv`)},{begin:RegExp(`(@(?:global|param|property(-(read|write))?|return|throws|var))\\p{space}+(?=[\\(\\?A-Z\\\\_a-z\\x7F-\\u{10FFFF}])`,`dgv`),beginCaptures:{1:{name:`keyword.other.phpdoc.php`}},contentName:`meta.other.type.phpdoc.php`,end:RegExp(`(?=\\p{space}|\\*\\/)`,`dgv`),patterns:[{include:`#php_doc_types_array_multiple`},{include:`#php_doc_types_array_single`},{include:`#php_doc_types`},{match:RegExp(`[\\&\\|]`,`dgv`),name:`punctuation.separator.delimiter.php`}]},{match:RegExp(`@(api|abstract|author|category|copyright|example|global|inherit[Dd]oc|internal|license|link|method|property(-(read|write))?|package|param|return|see|since|source|static|subpackage|throws|todo|var|version|uses|deprecated|final|ignore)\\b`,`dgv`),name:`keyword.other.phpdoc.php`},{captures:{1:{name:`keyword.other.phpdoc.php`}},match:RegExp(`\\{(@(link|inherit[Dd]oc))[^\\n]+?\\}`,`dgv`),name:`meta.tag.inline.phpdoc.php`}]},php_doc_types:{captures:{0:{patterns:[{match:RegExp(`\\?`,`dgv`),name:`keyword.operator.nullable-type.php`},{match:RegExp(`\\b(string|integer|int|boolean|bool|float|double|object|mixed|array|resource|void|null|callback|false|true|self|static)\\b`,`dgv`),name:`keyword.other.type.php`},{include:`#class-name`},{match:RegExp(`[\\&\\|]`,`dgv`),name:`punctuation.separator.delimiter.php`}]}},match:RegExp(`\\??[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+([\\&\\|]\\??[0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)*`,`dgiv`)},php_doc_types_array_multiple:{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.type.begin.bracket.round.phpdoc.php`}},end:RegExp(`(\\))(\\[\\])?|(?=\\*\\/)`,`dgv`),endCaptures:{1:{name:`punctuation.definition.type.end.bracket.round.phpdoc.php`},2:{name:`keyword.other.array.phpdoc.php`}},patterns:[{include:`#php_doc_types_array_multiple`},{include:`#php_doc_types_array_single`},{include:`#php_doc_types`},{match:RegExp(`[\\&\\|]`,`dgv`),name:`punctuation.separator.delimiter.php`}]},php_doc_types_array_single:{captures:{1:{patterns:[{include:`#php_doc_types`}]},2:{name:`keyword.other.array.phpdoc.php`}},match:RegExp(`([0-9\\\\_a-z\\x7F-\\u{10FFFF}]+)(\\[\\])`,`dgiv`)},"regex-double-quoted":{begin:new X(`"/(?=(?:(?=((\\\\[^\\n]|[^"\\/])+))\\1)/[ADSUXeimsux]*")`,`dgv`,{hiddenCaptures:[1]}),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},end:RegExp(`(\\/)([ADSUXeimsux]*)(")`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.regexp.double-quoted.php`,patterns:[{match:RegExp(`(\\\\){1,2}[\\]\\$\\.\\[\\^\\{\\}]`,`dgv`),name:`constant.character.escape.regex.php`},{include:`#interpolation_double_quoted`},{captures:{1:{name:`punctuation.definition.arbitrary-repetition.php`},3:{name:`punctuation.definition.arbitrary-repetition.php`}},match:RegExp(`(\\{)\\p{Nd}+(,\\p{Nd}+)?(\\})`,`dgv`),name:`string.regexp.arbitrary-repetition.php`},{begin:RegExp(`\\[(?:\\^?\\])?`,`dgv`),captures:{0:{name:`punctuation.definition.character-class.php`}},end:RegExp(`\\]`,`dgv`),name:`string.regexp.character-class.php`,patterns:[{include:`#interpolation_double_quoted`}]},{match:RegExp(`[\\$\\*\\+\\^]`,`dgv`),name:`keyword.operator.regexp.php`}]},"regex-single-quoted":{begin:new X(`'/(?=(?:(?=((\\\\(?:\\\\(?:\\\\['\\\\]?|[^'])|[^\\n])|[^'\\/])+))\\1)/[ADSUXeimsux]*')`,`dgv`,{hiddenCaptures:[1]}),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},end:RegExp(`(\\/)([ADSUXeimsux]*)(')`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.regexp.single-quoted.php`,patterns:[{include:`#single_quote_regex_escape`},{captures:{1:{name:`punctuation.definition.arbitrary-repetition.php`},3:{name:`punctuation.definition.arbitrary-repetition.php`}},match:RegExp(`(\\{)\\p{Nd}+(,\\p{Nd}+)?(\\})`,`dgv`),name:`string.regexp.arbitrary-repetition.php`},{begin:RegExp(`\\[(?:\\^?\\])?`,`dgv`),captures:{0:{name:`punctuation.definition.character-class.php`}},end:RegExp(`\\]`,`dgv`),name:`string.regexp.character-class.php`},{match:RegExp(`[\\$\\*\\+\\^]`,`dgv`),name:`keyword.operator.regexp.php`}]},"scope-resolution":{patterns:[{captures:{1:{patterns:[{match:RegExp(`\\b(self|static|parent)\\b`,`dgv`),name:`storage.type.php`},{include:`#class-name`},{include:`#variable-name`}]}},match:RegExp(`([A-Z\\\\_a-z\\x7F-\\u{10FFFF}][0-9A-Z\\\\_a-z\\x7F-\\u{10FFFF}]*)(?=\\p{space}*::)`,`dgv`)},{begin:RegExp(`(::)\\p{space}*([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)\\p{space}*(\\()`,`dgiv`),beginCaptures:{1:{name:`keyword.operator.class.php`},2:{name:`entity.name.function.php`},3:{name:`punctuation.definition.arguments.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.arguments.end.bracket.round.php`}},name:`meta.method-call.static.php`,patterns:[{include:`#named-arguments`},{include:`$self`}]},{captures:{1:{name:`keyword.operator.class.php`},2:{name:`keyword.other.class.php`}},match:RegExp(`(::)\\p{space}*(class)\\b`,`dgiv`)},{captures:{1:{name:`keyword.operator.class.php`},2:{name:`variable.other.class.php`},3:{name:`punctuation.definition.variable.php`},4:{name:`constant.other.class.php`}},match:RegExp(`(::)\\p{space}*(?:((\\$+)[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)|([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*))?`,`dgiv`)}]},single_quote_regex_escape:{match:RegExp(`\\\\(?:\\\\(?:\\\\['\\\\]?|[^'])|[^\\n])`,`dgv`),name:`constant.character.escape.php`},"sql-string-double-quoted":{begin:RegExp(`"\\p{space}*(?=(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|AND|WITH)\\b)`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},contentName:`source.sql.embedded.php`,end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.quoted.double.sql.php`,patterns:[{captures:{1:{name:`punctuation.definition.comment.sql`}},match:RegExp(`(#)(\\\\"|[^"])*(?="|(?=\\n?$))`,`dgv`),name:`comment.line.number-sign.sql`},{captures:{1:{name:`punctuation.definition.comment.sql`}},match:RegExp(`(--)(\\\\"|[^"])*(?="|(?=\\n?$))`,`dgv`),name:`comment.line.double-dash.sql`},{match:RegExp(`\\\\["'\\\\\\\`]`,`dgv`),name:`constant.character.escape.php`},{match:RegExp(`'(?=((\\\\')|[^"'])*("|(?=\\n?$)))`,`dgv`),name:`string.quoted.single.unclosed.sql`},{match:RegExp('`(?=((\\\\`)|[^"\\`])*("|(?=\\n?$)))',`dgv`),name:`string.quoted.other.backtick.unclosed.sql`},{begin:RegExp(`'`,`dgv`),end:RegExp(`'`,`dgv`),name:`string.quoted.single.sql`,patterns:[{include:`#interpolation_double_quoted`}]},{begin:RegExp("`",`dgv`),end:RegExp("`",`dgv`),name:`string.quoted.other.backtick.sql`,patterns:[{include:`#interpolation_double_quoted`}]},{include:`#interpolation_double_quoted`},{include:`source.sql`}]},"sql-string-single-quoted":{begin:RegExp(`'\\p{space}*(?=(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|AND|WITH)\\b)`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},contentName:`source.sql.embedded.php`,end:RegExp(`'`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.quoted.single.sql.php`,patterns:[{captures:{1:{name:`punctuation.definition.comment.sql`}},match:RegExp(`(#)(\\\\'|[^'])*(?='|(?=\\n?$))`,`dgv`),name:`comment.line.number-sign.sql`},{captures:{1:{name:`punctuation.definition.comment.sql`}},match:RegExp(`(--)(\\\\'|[^'])*(?='|(?=\\n?$))`,`dgv`),name:`comment.line.double-dash.sql`},{match:RegExp(`\\\\["'\\\\\\\`]`,`dgv`),name:`constant.character.escape.php`},{match:RegExp("`(?=((\\\\`)|[^'\\`])*('|(?=\\n?$)))",`dgv`),name:`string.quoted.other.backtick.unclosed.sql`},{match:RegExp(`"(?=((\\\\")|[^"'])*('|(?=\\n?$)))`,`dgv`),name:`string.quoted.double.unclosed.sql`},{include:`source.sql`}]},"string-backtick":{begin:RegExp("`",`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},end:RegExp("`",`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.interpolated.php`,patterns:[{match:RegExp("\\\\`",`dgv`),name:`constant.character.escape.php`},{include:`#interpolation`}]},"string-double-quoted":{begin:RegExp(`"`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},end:RegExp(`"`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.quoted.double.php`,patterns:[{include:`#interpolation_double_quoted`}]},"string-single-quoted":{begin:RegExp(`'`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.string.begin.php`}},end:RegExp(`'`,`dgv`),endCaptures:{0:{name:`punctuation.definition.string.end.php`}},name:`string.quoted.single.php`,patterns:[{match:RegExp(`\\\\['\\\\]`,`dgv`),name:`constant.character.escape.php`}]},strings:{patterns:[{include:`#regex-double-quoted`},{include:`#sql-string-double-quoted`},{include:`#string-double-quoted`},{include:`#regex-single-quoted`},{include:`#sql-string-single-quoted`},{include:`#string-single-quoted`}]},support:{patterns:[{match:RegExp(`\\bapc_(store|sma_info|compile_file|clear_cache|cas|cache_info|inc|dec|define_constants|delete(_file)?|exists|fetch|load_constants|add|bin_(dump|load)(file)?)\\b`,`dgiv`),name:`support.function.apc.php`},{match:RegExp(`\\b(compact|count|current|end|extract|in_array|key(_exists)?|list|nat(case)?sort|next|pos|prev|range|reset|shuffle|sizeof|[ak]?r?sort|u[ak]?sort|array_(all|any|change_key_case|chunk|column|combine|count_values|fill(_keys)?|filter|find(_key)?|flip|is_list|key_(exists|first|last)|keys|map|multisort|pad|pop|product|push|rand|reduce|reverse|search|shift|slice|splice|sum|unique|unshift|values|u?(diff|intersect)(_u?(key|assoc))?|(walk|replace|merge)(_recursive)?))\\b`,`dgiv`),name:`support.function.array.php`},{match:RegExp(`\\b(connection_(aborted|status)|constant|defined?|die|eval|exit|get_browser|__halt_compiler|highlight_(file|string)|hrtime|ignore_user_abort|pack|php_strip_whitespace|show_source|u?sleep|sys_getloadavg|time_(nanosleep|sleep_until)|uniqid|unpack)\\b`,`dgiv`),name:`support.function.basic_functions.php`},{match:RegExp(`\\bbc(add|ceil|comp|(div|pow)(mod)?|floor|mod|mul|round|scale|sqrt|sub)\\b`,`dgiv`),name:`support.function.bcmath.php`},{match:RegExp(`\\bblenc_encrypt\\b`,`dgiv`),name:`support.function.blenc.php`},{match:RegExp(`\\bbz(compress|close|open|decompress|errstr|errno|error|flush|write|read)\\b`,`dgiv`),name:`support.function.bz2.php`},{match:RegExp(`\\b((French|Gregorian|Jewish|Julian)ToJD|cal_(to_jd|info|days_in_month|from_jd)|unixtojd|jdto(unix|jewish)|easter_(da(?:te|ys))|JD(MonthName|To(Gregorian|Julian|French)|DayOfWeek))\\b`,`dgiv`),name:`support.function.calendar.php`},{match:RegExp(`\\b(__autoload|class_alias|(class|interface|method|property|trait|enum)_exists|is_(a|subclass_of)|get_(class(_(vars|methods))?|(called|parent)_class|(mangled_)?object_vars|declared_(classes|interfaces|traits)))\\b`,`dgiv`),name:`support.function.classobj.php`},{match:RegExp(`\\b(com_(create_guid|print_typeinfo|event_sink|load_typelib|get_active_object|message_pump)|variant_(sub|set(_type)?|not|neg|cast|cat|cmp|int|idiv|imp|or|div|date_(from|to)_timestamp|pow|eqv|fix|and|add|abs|round|get_type|xor|mod|mul))\\b`,`dgiv`),name:`support.function.com.php`},{match:RegExp(`\\b(isset|unset|eval|empty|list)\\b`,`dgiv`),name:`support.function.construct.php`},{match:RegExp(`\\b(print|echo)\\b`,`dgiv`),name:`support.function.construct.output.php`},{match:RegExp(`\\bctype_(space|cntrl|digit|upper|punct|print|lower|alnum|alpha|graph|xdigit)\\b`,`dgiv`),name:`support.function.ctype.php`},{match:RegExp(`\\bcurl_(close|copy_handle|errno|error|escape|exec|getinfo|init|pause|reset|setopt(_array)?|strerror|unescape|upkeep|version|multi_((add|remove)_handle|close|errno|exec|getcontent|info_read|init|select|setopt|strerror)|share_(close|errno|init(_persistent)?|setopt|strerror))\\b`,`dgiv`),name:`support.function.curl.php`},{match:RegExp(`\\b(strtotime|str[fp]time|checkdate|time|timezone_name_(from_abbr|get)|idate|timezone_((location|offset|transitions|version)_get|(abbreviations|identifiers)_list|open)|date(_(sun(rise|set)|sun_info|sub|create(_immutable)?(_from_format)?|timestamp_[gs]et|timezone_[gs]et|time_set|isodate_set|interval_(create_from_date_string|format)|offset_get|diff|default_timezone_[gs]et|date_set|parse(_from_format)?|format|add|get_last_errors|modify))?|localtime|get(date|timeofday)|gm(strftime|date|mktime)|microtime|mktime)\\b`,`dgiv`),name:`support.function.datetime.php`},{match:RegExp(`\\bdba_(sync|handlers|nextkey|close|insert|optimize|open|delete|popen|exists|key_split|firstkey|fetch|list|replace)\\b`,`dgiv`),name:`support.function.dba.php`},{match:RegExp(`\\bdbx_(sort|connect|compare|close|escape_string|error|query|fetch_row)\\b`,`dgiv`),name:`support.function.dbx.php`},{match:RegExp(`\\b(scandir|chdir|chroot|closedir|opendir|dir|rewinddir|readdir|getcwd)\\b`,`dgiv`),name:`support.function.dir.php`},{match:RegExp(`\\beio_(sync(fs)?|sync_file_range|symlink|stat(vfs)?|sendfile|set_min_parallel|set_max_(idle|poll_(reqs|time)|parallel)|seek|n(threads|op|pending|reqs|ready)|chown|chmod|custom|close|cancel|truncate|init|open|dup2|unlink|utime|poll|event_loop|f(sync|stat(vfs)?|chown|chmod|truncate|datasync|utime|allocate)|write|lstat|link|rename|realpath|read(ahead|dir|link)?|rmdir|get_(event_stream|last_error)|grp(_(add|cancel|limit))?|mknod|mkdir|busy)\\b`,`dgiv`),name:`support.function.eio.php`},{match:RegExp(`\\benchant_(dict_(store_replacement|suggest|check|is_in_session|describe|quick_check|add_to_(personal|session)|get_error)|broker_(set_ordering|init|dict_exists|describe|free(_dict)?|list_dicts|request_(pwl_)?dict|get_error))\\b`,`dgiv`),name:`support.function.enchant.php`},{match:RegExp(`\\b(split(i)?|sql_regcase|ereg(i)?(_replace)?)\\b`,`dgiv`),name:`support.function.ereg.php`},{match:RegExp(`\\b((restore|set)_(e(?:rror|xception))_handler|trigger_error|debug_(print_)?backtrace|user_error|error_(log|reporting|(clear|get)_last))\\b`,`dgiv`),name:`support.function.errorfunc.php`},{match:RegExp(`\\b(shell_exec|system|passthru|proc_(nice|close|terminate|open|get_status)|escapeshell(arg|cmd)|exec)\\b`,`dgiv`),name:`support.function.exec.php`},{match:RegExp(`\\b(exif_(thumbnail|tagname|imagetype|read_data)|read_exif_data)\\b`,`dgiv`),name:`support.function.exif.php`},{match:RegExp(`\\bfann_((duplicate|length|merge|shuffle|subset)_train_data|scale_(train(_data)?|((?:in|out)put)(_train_data)?)|set_(scaling_params|sarprop_(step_error_(shift|threshold_factor)|temperature|weight_decay_shift)|cascade_(num_candidate_groups|candidate_(change_fraction|limit|stagnation_epochs)|output_(change_fraction|stagnation_epochs)|weight_multiplier|activation_(functions|steepnesses)|(m(?:ax|in))_(cand|out)_epochs)|callback|training_algorithm|train_(error|stop)_function|((?:in|out)put)_scaling_params|error_log|quickprop_(decay|mu)|weight(_array)?|learning_(momentum|rate)|bit_fail_limit|activation_(function|steepness)(_(hidden|layer|output))?|rprop_(((?:de|in)crease)_factor|delta_(max|min|zero)))|save(_train)?|num_((?:in|out)put)_train_data|copy|clear_scaling_params|cascadetrain_on_(file|data)|create_((s(?:parse|hortcut|tandard))(_array)?|train(_from_callback)?|from_file)|test(_data)?|train(_(on_(file|data)|epoch))?|init_weights|descale_(input|output|train)|destroy(_train)?|print_error|run|reset_(MSE|err(no|str))|read_train_from_file|randomize_weights|get_(sarprop_(step_error_(shift|threshold_factor)|temperature|weight_decay_shift)|num_(input|output|layers)|network_type|MSE|connection_(array|rate)|bias_array|bit_fail(_limit)?|cascade_(num_(candidate(?:s|_groups))|(candidate|output)_(change_fraction|limit|stagnation_epochs)|weight_multiplier|activation_(functions|steepnesses)(_count)?|(m(?:ax|in))_(cand|out)_epochs)|total_((?:connecti|neur)ons)|training_algorithm|train_(error|stop)_function|err(no|str)|quickprop_(decay|mu)|learning_(momentum|rate)|layer_array|activation_(function|steepness)|rprop_(((?:de|in)crease)_factor|delta_(max|min|zero))))\\b`,`dgiv`),name:`support.function.fann.php`},{match:RegExp(`\\b(symlink|stat|set_file_buffer|chown|chgrp|chmod|copy|clearstatcache|touch|tempnam|tmpfile|is_(dir|(uploaded_)?file|executable|link|readable|writ(e)?able)|disk_(free|total)_space|diskfreespace|dirname|delete|unlink|umask|pclose|popen|pathinfo|parse_ini_(file|string)|fscanf|fstat|fseek|fnmatch|fclose|ftell|ftruncate|file(size|[acm]time|type|inode|owner|perms|group)?|file_(exists|(get|put)_contents)|f(open|puts|putcsv|passthru|eof|flush|write|lock|read|gets(s)?|getc(sv)?)|lstat|lchown|lchgrp|link(info)?|rename|rewind|read(file|link)|realpath(_cache_(get|size))?|rmdir|glob|move_uploaded_file|mkdir|basename|f(data)?sync)\\b`,`dgiv`),name:`support.function.file.php`},{match:RegExp(`\\b(finfo_(set_flags|close|open|file|buffer)|mime_content_type)\\b`,`dgiv`),name:`support.function.fileinfo.php`},{match:RegExp(`\\bfilter_(has_var|input(_array)?|id|var(_array)?|list)\\b`,`dgiv`),name:`support.function.filter.php`},{match:RegExp(`\\b(f(?:astcgi_finish_request|pm_get_status))\\b`,`dgiv`),name:`support.function.fpm.php`},{match:RegExp(`\\b(call_user_(func|method)(_array)?|create_function|unregister_tick_function|forward_static_call(_array)?|function_exists|func_(num_args|get_arg(s)?)|register_(shutdown|tick)_function|get_defined_functions)\\b`,`dgiv`),name:`support.function.funchand.php`},{match:RegExp(`\\b((n)?gettext|textdomain|d((?:(n)?|c(n)?)gettext)|bind(textdomain|_textdomain_codeset))\\b`,`dgiv`),name:`support.function.gettext.php`},{match:RegExp(`\\bgmp_(scan[01]|strval|sign|sub|setbit|sqrt(rem)?|hamdist|neg|nextprime|com|clrbit|cmp|testbit|intval|init|invert|import|or|div(exact)?|div_(qr??|r)|jacobi|popcount|pow(m)?|perfect_(square|power)|prob_prime|export|fact|legendre|and|add|abs|root(rem)?|random(_(bits|range|seed))?|gcd(ext)?|xor|mod|mul|binomial|kronecker|lcm)\\b`,`dgiv`),name:`support.function.gmp.php`},{match:RegExp(`\\bhash(_(algos|copy|equals|file|final|hkdf|hmac(_(file|algos)?)?|init|pbkdf2|update(_(file|stream))?))?\\b`,`dgiv`),name:`support.function.hash.php`},{match:RegExp(`\\b(http_(support|send_(status|stream|content_(disposition|type)|data|file|last_modified)|head|negotiate_(charset|content_type|language)|chunked_decode|cache_(etag|last_modified)|throttle|inflate|deflate|date|post_(data|fields)|put_(data|file|stream)|persistent_handles_(count|clean|ident)|parse_(cookie|headers|message|params)|redirect|request(_(method_(exists|name|(un)?register)|body_encode))?|get(_request_(headers|body(_stream)?))?|match_(etag|modified|request_header)|build_(cookie|str|url))|ob_(etag|deflate|inflate)handler)\\b`,`dgiv`),name:`support.function.http.php`},{match:RegExp(`\\b(iconv(_(str(pos|len|rpos)|substr|[gs]et_encoding|mime_(decode(_headers)?|encode)))?|ob_iconv_handler)\\b`,`dgiv`),name:`support.function.iconv.php`},{match:RegExp(`\\biis_((st(?:art|op))_(serv(?:ice|er))|set_(script_map|server_rights|dir_security|app_settings)|(add|remove)_server|get_(script_map|service_state|server_(rights|by_(comment|path))|dir_security))\\b`,`dgiv`),name:`support.function.iisfunc.php`},{match:RegExp(`\\b(iptc(embed|parse)|(jpeg|png)2wbmp|gd_info|getimagesize(fromstring)?|image(s[xy]|scale|(char|string)(up)?|set(clip|style|thickness|tile|interpolation|pixel|brush)|savealpha|convolution|copy(resampled|resized|merge(gray)?)?|colors(forindex|total)|color(set|closest(alpha|hwb)?|transparent|deallocate|(allocate|exact|resolve)(alpha)?|at|match)|crop(auto)?|create(truecolor|from(avif|bmp|string|jpeg|png|wbmp|webp|gif|gd(2(part)?)?|tga|xpm|xbm))?|types|ttf(bbox|text)|truecolortopalette|istruecolor|interlace|2wbmp|destroy|dashedline|jpeg|_type_to_(extension|mime_type)|ps(slantfont|text|(encode|extend|free|load)font|bbox)|png|polygon|palette(copy|totruecolor)|ellipse|ft(text|bbox)|filter|fill|filltoborder|filled(arc|ellipse|polygon|rectangle)|font(height|width)|flip|webp|wbmp|line|loadfont|layereffect|antialias|affine(matrix(concat|get))?|alphablending|arc|rotate|rectangle|gif|gd2?|gammacorrect|grab(screen|window)|xbm|resolution|openpolygon|get(clip|interpolation)|avif|bmp))\\b`,`dgiv`),name:`support.function.image.php`},{match:RegExp(`\\b(sys_get_temp_dir|set_(time_limit|include_path|magic_quotes_runtime)|cli_[gs]et_process_title|ini_(alter|get(_all)?|restore|set)|zend_(thread_id|version|logo_guid)|dl|php(credits|info|version)|php_(sapi_name|ini_(scanned_files|loaded_file)|uname|logo_guid)|putenv|extension_loaded|version_compare|assert(_options)?|restore_include_path|gc_(collect_cycles|disable|enable(d)?)|getopt|get_(cfg_var|current_user|defined_constants|extension_funcs|include_path|included_files|loaded_extensions|magic_quotes_(gpc|runtime)|required_files|resources)|get(env|lastmod|rusage|my(inode|[gpu]id))|memory_get_(peak_)?usage|main|magic_quotes_runtime)\\b`,`dgiv`),name:`support.function.info.php`},{match:RegExp(`\\bibase_(set_event_handler|service_((?:at|de)tach)|server_info|num_(fields|params)|name_result|connect|commit(_ret)?|close|trans|delete_user|drop_db|db_info|pconnect|param_info|prepare|err(code|msg)|execute|query|field_info|fetch_(assoc|object|row)|free_(event_handler|query|result)|wait_event|add_user|affected_rows|rollback(_ret)?|restore|gen_id|modify_user|maintain_db|backup|blob_(cancel|close|create|import|info|open|echo|add|get))\\b`,`dgiv`),name:`support.function.interbase.php`},{match:RegExp(`\\b(normalizer_(normalize|is_normalized)|idn_to_(unicode|utf8|ascii)|numfmt_(set_(symbol|(text_)?attribute|pattern)|create|(parse|format)(_currency)?|get_(symbol|(text_)?attribute|pattern|error_(code|message)|locale))|collator_(sort(_with_sort_keys)?|set_(attribute|strength)|compare|create|asort|get_(strength|sort_key|error_(code|message)|locale|attribute))|transliterator_(create(_(inverse|from_rules))?|transliterate|list_ids|get_error_(code|message))|intl(cal|tz)_get_error_(code|message)|intl_(is_failure|error_name|get_error_(code|message))|datefmt_(set_(calendar|lenient|pattern|timezone(_id)?)|create|is_lenient|parse|format(_object)?|localtime|get_(calendar(_object)?|time(type|zone(_id)?)|datetype|pattern|error_(code|message)|locale))|locale_(set_default|compose|canonicalize|parse|filter_matches|lookup|accept_from_http|get_(script|display_(script|name|variant|language|region)|default|primary_language|keywords|all_variants|region))|resourcebundle_(create|count|locales|get(_(error_(code|message)))?)|grapheme_(str(i?str|r?i?pos|len|_split)|substr|extract)|msgfmt_(set_pattern|create|(format|parse)(_message)?|get_(pattern|error_(code|message)|locale)))\\b`,`dgiv`),name:`support.function.intl.php`},{match:RegExp(`\\bjson_(decode|encode|last_error(_msg)?|validate)\\b`,`dgiv`),name:`support.function.json.php`},{match:RegExp(`\\bldap_(start|tls|sort|search|sasl_bind|set_(option|rebind_proc)|(first|next)_(attribute|entry|reference)|connect|control_paged_result(_response)?|count_entries|compare|close|t61_to_8859|8859_to_t61|dn2ufn|delete|unbind|parse_(re(?:ference|sult))|escape|errno|err2str|error|explode_dn|bind|free_result|list|add|rename|read|get_(option|dn|entries|values(_len)?|attributes)|modify(_batch)?|mod_(add|del|replace))\\b`,`dgiv`),name:`support.function.ldap.php`},{match:RegExp(`\\blibxml_(set_(streams_context|external_entity_loader)|clear_errors|disable_entity_loader|use_internal_errors|get_(errors|last_error))\\b`,`dgiv`),name:`support.function.libxml.php`},{match:RegExp(`\\b(ezmlm_hash|mail)\\b`,`dgiv`),name:`support.function.mail.php`},{match:RegExp(`\\b(a?(cos|sin|tan)h?|sqrt|srand|hypot|hexdec|ceil|is_(nan|(in)?finite)|octdec|dec(hex|oct|bin)|deg2rad|pi|pow|exp(m1)?|floor|f(div|mod|pow)|lcg_value|log(1[0p])?|atan2|abs|round|rand|rad2deg|getrandmax|mt_(srand|rand|getrandmax)|max|min|bindec|base_convert|intdiv)\\b`,`dgiv`),name:`support.function.math.php`},{match:RegExp(`\\bmb_(str(cut|str|to(lower|upper)|istr|ipos|imwidth|pos|width|len|rchr|richr|ripos|rpos|_pad|_split)|substitute_character|substr(_count)?|split|send_mail|http_((?:in|out)put)|check_encoding|convert_(case|encoding|kana|variables)|internal_encoding|output_handler|decode_(numericentity|mimeheader)|detect_(encoding|order)|parse_str|preferred_mime_name|encoding_aliases|encode_(numericentity|mimeheader)|ereg(i(_replace)?)?|ereg_(search(_(get(pos|regs)|init|regs|(set)?pos))?|replace(_callback)?|match)|list_encodings|language|regex_(set_options|encoding)|get_info|[lr]?trim|[lu]cfirst|ord|chr|scrub)\\b`,`dgiv`),name:`support.function.mbstring.php`},{match:RegExp(`\\b(m(?:crypt_(cfb|create_iv|cbc|ofb|decrypt|encrypt|ecb|list_(algorithms|modes)|generic(_((de)?init|end))?|enc_(self_test|is_block_(algorithm|algorithm_mode|mode)|get_(supported_key_sizes|(block|iv|key)_size|(algorithms|modes)_name))|get_(cipher_name|(block|iv|key)_size)|module_(close|self_test|is_block_(algorithm|algorithm_mode|mode)|open|get_(supported_key_sizes|algo_(block|key)_size)))|decrypt_generic))\\b`,`dgiv`),name:`support.function.mcrypt.php`},{match:RegExp(`\\bmemcache_debug\\b`,`dgiv`),name:`support.function.memcache.php`},{match:RegExp(`\\bmhash(_(count|keygen_s2k|get_(hash_name|block_size)))?\\b`,`dgiv`),name:`support.function.mhash.php`},{match:RegExp(`\\b(log_(cmd_(insert|delete|update)|killcursor|write_batch|reply|getmore)|bson_((?:de|en)code))\\b`,`dgiv`),name:`support.function.mongo.php`},{match:RegExp(`\\bmysql_(stat|set_charset|select_db|num_(fields|rows)|connect|client_encoding|close|create_db|escape_string|thread_id|tablename|insert_id|info|data_seek|drop_db|db_(name|query)|unbuffered_query|pconnect|ping|errno|error|query|field_(seek|name|type|table|flags|len)|fetch_(object|field|lengths|assoc|array|row)|free_result|list_(tables|dbs|processes|fields)|affected_rows|result|real_escape_string|get_(client|host|proto|server)_info)\\b`,`dgiv`),name:`support.function.mysql.php`},{match:RegExp(`\\bmysqli_(ssl_set|store_result|stat|send_(query|long_data)|set_(charset|opt|local_infile_(default|handler))|stmt_(store_result|send_long_data|next_result|close|init|data_seek|prepare|execute|fetch|free_result|attr_[gs]et|result_metadata|reset|get_(result|warnings)|more_results|bind_(param|result))|select_db|slave_query|savepoint|next_result|change_user|character_set_name|connect|commit|client_encoding|close|thread_safe|init|options|((?:en|dis)able)_(r(?:eads_from_master|pl_parse))|dump_debug_info|debug|data_seek|use_result|ping|poll|param_count|prepare|escape_string|execute|embedded_server_(start|end)|kill|query|field_seek|free_result|autocommit|rollback|report|refresh|fetch(_(object|fields|field(_direct)?|assoc|all|array|row))?|rpl_(parse_enabled|probe|query_type)|release_savepoint|reap_async_query|real_(connect|escape_string|query)|more_results|multi_query|get_(charset|connection_stats|client_(stats|info|version)|cache_stats|warnings|links_stats|metadata)|master_query|bind_(param|result)|begin_transaction)\\b`,`dgiv`),name:`support.function.mysqli.php`},{match:RegExp(`\\bmysqlnd_memcache_(set|get_config)\\b`,`dgiv`),name:`support.function.mysqlnd-memcache.php`},{match:RegExp(`\\bmysqlnd_ms_(set_(user_pick_server|qos)|dump_servers|query_is_select|fabric_select_(shard|global)|get_(stats|last_(used_connection|gtid))|xa_(commit|rollback|gc|begin)|match_wild)\\b`,`dgiv`),name:`support.function.mysqlnd-ms.php`},{match:RegExp(`\\bmysqlnd_qc_(set_(storage_handler|cache_condition|is_select|user_handlers)|clear_cache|get_(normalized_query_trace_log|core_stats|cache_info|query_trace_log|available_handlers))\\b`,`dgiv`),name:`support.function.mysqlnd-qc.php`},{match:RegExp(`\\bmysqlnd_uh_(set_(statement|connection)_proxy|convert_to_mysqlnd)\\b`,`dgiv`),name:`support.function.mysqlnd-uh.php`},{match:RegExp(`\\b(syslog|socket_(set_(blocking|timeout)|get_status)|set(raw)?cookie|http_response_code|openlog|headers_(list|sent)|header(_(re(?:gister_callback|move)))?|checkdnsrr|closelog|inet_(ntop|pton)|ip2long|openlog|dns_(check_record|get_(record|mx))|define_syslog_variables|(p)?fsockopen|long2ip|get(servby(name|port)|host(name|by(name(l)?|addr))|protoby(n(?:ame|umber))|mxrr)|http_(clear|get)_last_response_headers|net_get_interfaces|request_parse_body)\\b`,`dgiv`),name:`support.function.network.php`},{match:RegExp(`\\bnsapi_(virtual|response_headers|request_headers)\\b`,`dgiv`),name:`support.function.nsapi.php`},{match:RegExp(`\\b(oci(?:(statementtype|setprefetch|serverversion|savelob(file)?|numcols|new(collection|cursor|descriptor)|nlogon|column(scale|size|name|type(raw)?|isnull|precision)|coll(size|trim|assign(elem)?|append|getelem|max)|commit|closelob|cancel|internaldebug|definebyname|plogon|parse|error|execute|fetch(statement|into)?|free(statement|collection|cursor|desc)|write(temporarylob|lobtofile)|loadlob|log(o(?:n|ff))|rowcount|rollback|result|bindbyname)|_(statement_type|set_(client_(i(?:nfo|dentifier))|prefetch|edition|action|module_name)|server_version|num_(fields|rows)|new_(connect|collection|cursor|descriptor)|connect|commit|client_version|close|cancel|internal_debug|define_by_name|pconnect|password_change|parse|error|execute|bind_(array_)?by_name|field_(scale|size|name|type(_raw)?|is_null|precision)|fetch(_(object|assoc|all|array|row))?|free_(statement|descriptor)|lob_(copy|is_equal)|rollback|result|get_implicit_resultset)))\\b`,`dgiv`),name:`support.function.oci8.php`},{match:RegExp(`\\bopcache_(compile_file|invalidate|is_script_cached|reset|get_(status|configuration))\\b`,`dgiv`),name:`support.function.opcache.php`},{match:RegExp(`\\bopenssl_(sign|spki_(new|export(_challenge)?|verify)|seal|csr_(sign|new|export(_to_file)?|get_(subject|public_key))|cipher_(iv|key)_length|open|dh_compute_key|digest|decrypt|public_((?:de|en)crypt)|encrypt|error_string|pkcs12_(export(_to_file)?|read)|(cms|pkcs7)_(sign|decrypt|encrypt|verify|read)|verify|free_key|random_pseudo_bytes|pkey_(derive|new|export(_to_file)?|free|get_(details|public|private))|private_((?:de|en)crypt)|pbkdf2|get_((cipher|md)_methods|cert_locations|curve_names|(p(?:ublic|rivate))key)|x509_(check_private_key|checkpurpose|parse|export(_to_file)?|fingerprint|free|read|verify))\\b`,`dgiv`),name:`support.function.openssl.php`},{match:RegExp(`\\b(output_(add_rewrite_var|reset_rewrite_vars)|flush|ob_(start|clean|implicit_flush|end_(clean|flush)|flush|list_handlers|gzhandler|get_(status|contents|clean|flush|length|level)))\\b`,`dgiv`),name:`support.function.output.php`},{match:RegExp(`\\bpassword_(algos|hash|needs_rehash|verify|get_info)\\b`,`dgiv`),name:`support.function.password.php`},{match:RegExp(`\\bpcntl_(alarm|async_signals|errno|exec|r?fork|get_last_error|[gs]et((?:cpuaffin|prior)ity)|signal(_(dispatch|get_handler))?|sig(procmask|timedwait|waitinfo)|strerror|unshare|wait(p?id)?|wexitstatus|wif((?:exit|signal|stopp)ed)|w(stop|term)sig)\\b`,`dgiv`),name:`support.function.pcntl.php`},{match:RegExp(`\\bpg_(socket|send_(prepare|execute|query(_params)?)|set_(client_encoding|error_verbosity)|select|host|num_(fields|rows)|consume_input|connection_(status|reset|busy)|connect(_poll)?|convert|copy_(from|to)|client_encoding|close|cancel_query|tty|transaction_status|trace|insert|options|delete|dbname|untrace|unescape_bytea|update|pconnect|ping|port|put_line|parameter_status|prepare|version|query(_params)?|escape_(string|identifier|literal|bytea)|end_copy|execute|flush|free_result|last_(notice|error|oid)|field_(size|num|name|type(_oid)?|table|is_null|prtlen)|affected_rows|result_(status|seek|error(_field)?)|fetch_(object|assoc|all(_columns)?|array|row|result)|get_(notify|pid|result)|meta_data|lo_(seek|close|create|tell|truncate|import|open|unlink|export|write|read(_all)?)|)\\b`,`dgiv`),name:`support.function.pgsql.php`},{match:RegExp(`\\b(virtual|getallheaders|apache_([gs]etenv|note|child_terminate|lookup_uri|response_headers|reset_timeout|request_headers|get_(version|modules)))\\b`,`dgiv`),name:`support.function.php_apache.php`},{match:RegExp(`\\bdom_import_simplexml\\b`,`dgiv`),name:`support.function.php_dom.php`},{match:RegExp(`\\bftp_(ssl_connect|systype|site|size|set_option|nlist|nb_(continue|f?(put|get))|ch(dir|mod)|connect|cdup|close|delete|put|pwd|pasv|exec|quit|f(put|get)|login|alloc|rename|raw(list)?|rmdir|get(_option)?|mdtm|mkdir)\\b`,`dgiv`),name:`support.function.php_ftp.php`},{match:RegExp(`\\bimap_((create|delete|list|rename|scan)(mailbox)?|status|sort|subscribe|set_quota|set(flag_full|acl)|search|savebody|num_(recent|msg)|check|close|clearflag_full|thread|timeout|open|header(info)?|headers|append|alerts|reopen|8bit|unsubscribe|undelete|utf7_((?:de|en)code)|utf8|uid|ping|errors|expunge|qprint|gc|fetch(structure|header|text|mime|body)|fetch_overview|lsub|list(s(?:can|ubscribed))|last_error|rfc822_(parse_(headers|adrlist)|write_address)|get(subscribed|acl|mailboxes)|get_quota(root)?|msgno|mime_header_decode|mail_(copy|compose|move)|mail|mailboxmsginfo|binary|body(struct)?|base64)\\b`,`dgiv`),name:`support.function.php_imap.php`},{match:RegExp(`\\bmssql_(select_db|num_(fields|rows)|next_result|connect|close|init|data_seek|pconnect|execute|query|field_(seek|name|type|length)|fetch_(object|field|assoc|array|row|batch)|free_(statement|result)|rows_affected|result|guid_string|get_last_message|min_(error|message)_severity|bind)\\b`,`dgiv`),name:`support.function.php_mssql.php`},{match:RegExp(`\\bodbc_(statistics|specialcolumns|setoption|num_(fields|rows)|next_result|connect|columns|columnprivileges|commit|cursor|close(_all)?|tables|tableprivileges|do|data_source|pconnect|primarykeys|procedures|procedurecolumns|prepare|error(msg)?|exec(ute)?|field_(scale|num|name|type|precision|len)|foreignkeys|free_result|fetch_(into|object|array|row)|longreadlen|autocommit|rollback|result(_all)?|gettypeinfo|binmode)\\b`,`dgiv`),name:`support.function.php_odbc.php`},{match:RegExp(`\\bpreg_(split|quote|filter|last_error(_msg)?|replace(_callback(_array)?)?|grep|match(_all)?)\\b`,`dgiv`),name:`support.function.php_pcre.php`},{match:RegExp(`\\b(spl_(classes|object_hash|autoload(_(call|unregister|extensions|functions|register))?)|class_(implements|uses|parents)|iterator_(count|to_array|apply))\\b`,`dgiv`),name:`support.function.php_spl.php`},{match:RegExp(`\\bzip_(close|open|entry_(name|compressionmethod|compressedsize|close|open|filesize|read)|read)\\b`,`dgiv`),name:`support.function.php_zip.php`},{match:RegExp(`\\bposix_(strerror|set(s|e?u|[ep]?g)id|ctermid|ttyname|times|isatty|initgroups|uname|errno|kill|e?access|get(sid|cwd|uid|pid|ppid|pwnam|pwuid|pgid|pgrp|euid|egid|login|rlimit|gid|grnam|groups|grgid)|get_last_error|mknod|mkfifo|(sys|f?path)conf|setrlimit)\\b`,`dgiv`),name:`support.function.posix.php`},{match:RegExp(`\\bset(thread|proc)title\\b`,`dgiv`),name:`support.function.proctitle.php`},{match:RegExp(`\\bpspell_(store_replacement|suggest|save_wordlist|new(_(config|personal))?|check|clear_session|config_(save_repl|create|ignore|(d(?:ata|ict))_dir|personal|runtogether|repl|mode)|add_to_(session|personal))\\b`,`dgiv`),name:`support.function.pspell.php`},{match:RegExp(`\\breadline(_(completion_function|clear_history|callback_(handler_(install|remove)|read_char)|info|on_new_line|write_history|list_history|add_history|redisplay|read_history))?\\b`,`dgiv`),name:`support.function.readline.php`},{match:RegExp(`\\brecode(_(string|file))?\\b`,`dgiv`),name:`support.function.recode.php`},{match:RegExp(`\\brrd(c_disconnect|_(create|tune|info|update|error|version|first|fetch|last(update)?|restore|graph|xport))\\b`,`dgiv`),name:`support.function.rrd.php`},{match:RegExp(`\\b(shm_((get|has|remove|put)_var|detach|attach|remove)|sem_(acquire|release|remove|get)|ftok|msg_((get|remove|set|stat)_queue|send|queue_exists|receive))\\b`,`dgiv`),name:`support.function.sem.php`},{match:RegExp(`\\bsession_(status|start|set_(save_handler|cookie_params)|save_path|name|commit|cache_(expire|limiter)|is_registered|id|destroy|decode|unset|unregister|encode|write_close|abort|reset|register(_shutdown)?|((?:regener|cre)ate)_id|get_cookie_params|module_name|gc)\\b`,`dgiv`),name:`support.function.session.php`},{match:RegExp(`\\bshmop_(size|close|open|delete|write|read)\\b`,`dgiv`),name:`support.function.shmop.php`},{match:RegExp(`\\bsimplexml_(import_dom|load_(string|file))\\b`,`dgiv`),name:`support.function.simplexml.php`},{match:RegExp(`\\b(snmp(?:(walk(oid)?|realwalk|get(next)?|set)|_(set_(valueretrieval|quick_print|enum_print|oid_(numeric_print|output_format))|read_mib|get_(valueretrieval|quick_print))|[23]_(set|walk|real_walk|get(next)?)))\\b`,`dgiv`),name:`support.function.snmp.php`},{match:RegExp(`\\b(is_soap_fault|use_soap_error_handler)\\b`,`dgiv`),name:`support.function.soap.php`},{match:RegExp(`\\bsocket_(accept|addrinfo_(bind|connect|explain|lookup)|atmark|bind|(clear|last)_error|close|cmsg_space|connect|create(_(listen|pair))?|(ex|im)port_stream|[gs]et_option|[gs]etopt|get(peer|sock)name|listen|read|recv(from|msg)?|select|send(msg|to)?|set_(non)?block|shutdown|strerror|write|wsaprotocol_info_(export|import|release))\\b`,`dgiv`),name:`support.function.sockets.php`},{match:RegExp(`\\bsqlite_(single_query|seek|has_(more|prev)|num_(fields|rows)|next|changes|column|current|close|create_(aggregate|function)|open|unbuffered_query|udf_((?:de|en)code)_binary|popen|prev|escape_string|error_string|exec|valid|key|query|field_name|factory|fetch_(string|single|column_types|object|all|array)|lib(encoding|version)|last_(insert_rowid|error)|array_query|rewind|busy_timeout)\\b`,`dgiv`),name:`support.function.sqlite.php`},{match:RegExp(`\\bsqlsrv_(send_stream_data|server_info|has_rows|num_(fields|rows)|next_result|connect|configure|commit|client_info|close|cancel|prepare|errors|execute|query|field_metadata|fetch(_(array|object))?|free_stmt|rows_affected|rollback|get_(config|field)|begin_transaction)\\b`,`dgiv`),name:`support.function.sqlsrv.php`},{match:RegExp(`\\bstats_(harmonic_mean|covariance|standard_deviation|skew|cdf_(noncentral_(chisquare|f)|negative_binomial|chisquare|cauchy|t|uniform|poisson|exponential|f|weibull|logistic|laplace|gamma|binomial|beta)|stat_(noncentral_t|correlation|innerproduct|independent_t|powersum|percentile|paired_t|gennch|binomial_coef)|dens_(normal|negative_binomial|chisquare|cauchy|t|pmf_(hypergeometric|poisson|binomial)|exponential|f|weibull|logistic|laplace|gamma|beta)|den_uniform|variance|kurtosis|absolute_deviation|rand_(setall|phrase_to_seeds|ranf|get_seeds|gen_(noncentral_[ft]|noncenral_chisquare|normal|chisquare|t|int|i(uniform|poisson|binomial(_negative)?)|exponential|f(uniform)?|gamma|beta)))\\b`,`dgiv`),name:`support.function.stats.php`},{match:RegExp(`\\bstream_(bucket_(new|prepend|append|make_writeable)|context_(create|[gs]et_(options?|default|params))|copy_to_stream|filter_((ap|pre)pend|register|remove)|get_(contents|filters|line|meta_data|transports|wrappers)|is(atty|_local)|notification_callback|register_wrapper|resolve_include_path|select|set_(blocking|chunk_size|(read|write)_buffer|timeout)|socket_(accept|client|enable_crypto|get_name|pair|recvfrom|sendto|server|shutdown)|supports_lock|wrapper_((un)?register|restore))\\b`,`dgiv`),name:`support.function.streamsfuncs.php`},{match:RegExp(`\\b(money_format|md5(_file)?|metaphone|bin2hex|sscanf|sha1(_file)?|str(str|c?spn|n(at)?(case)?cmp|chr|coll|(case)?cmp|to(upper|lower)|tok|tr|istr|pos|pbrk|len|rchr|ri?pos|rev)|str_(getcsv|i?replace|pad|repeat|rot13|shuffle|split|word_count|contains|(starts|ends)_with|(in|de)crement)|strip(c?slashes|os)|strip_tags|similar_text|soundex|substr(_(count|compare|replace))?|setlocale|html(specialchars(_decode)?|entities)|html_entity_decode|hex2bin|hebrev(c)?|number_format|nl2br|nl_langinfo|chop|chunk_split|chr|convert_(cyr_string|uu((?:de|en)code))|count_chars|crypt|crc32|trim|implode|ord|uc(first|words)|join|parse_str|print(f)?|echo|explode|v?[fs]?printf|quoted_printable_((?:de|en)code)|quotemeta|wordwrap|lcfirst|[lr]trim|localeconv|levenshtein|addc?slashes|get_html_translation_table)\\b`,`dgiv`),name:`support.function.string.php`},{match:RegExp(`\\bsybase_(set_message_handler|select_db|num_(fields|rows)|connect|close|deadlock_retry_count|data_seek|unbuffered_query|pconnect|query|field_seek|fetch_(object|field|assoc|array|row)|free_result|affected_rows|result|get_last_message|min_(client|error|message|server)_severity)\\b`,`dgiv`),name:`support.function.sybase.php`},{match:RegExp(`\\b(taint|is_tainted|untaint)\\b`,`dgiv`),name:`support.function.taint.php`},{match:RegExp(`\\b(tidy_([gs]etopt|set_encoding|save_config|config_count|clean_repair|is_(x(?:html|ml))|diagnose|(access|error|warning)_count|load_config|reset_config|(parse|repair)_(string|file)|get_(status|html(_ver)?|head|config|output|opt_doc|root|release|body))|ob_tidyhandler)\\b`,`dgiv`),name:`support.function.tidy.php`},{match:RegExp(`\\btoken_(name|get_all)\\b`,`dgiv`),name:`support.function.tokenizer.php`},{match:RegExp(`\\btrader_(stoch([fr]|rsi)?|stddev|sin(h)?|sum|sub|set_(compat|unstable_period)|sqrt|sar(ext)?|sma|ht_(sine|trend(line|mode)|dc(p(?:eriod|hase))|phasor)|natr|cci|cos(h)?|correl|cdl(shootingstar|shortline|sticksandwich|stalledpattern|spinningtop|separatinglines|hikkake(mod)?|highwave|homingpigeon|hangingman|harami(cross)?|hammer|concealbabyswall|counterattack|closingmarubozu|thrusting|tasukigap|takuri|tristar|inneck|invertedhammer|identical3crows|2crows|onneck|doji(star)?|darkcloudcover|dragonflydoji|unique3river|upsidegap2crows|3(starsinsouth|inside|outside|whitesoldiers|linestrike|blackcrows)|piercing|engulfing|evening(doji)?star|kicking(bylength)?|longline|longleggeddoji|ladderbottom|advanceblock|abandonedbaby|risefall3methods|rickshawman|gapsidesidewhite|gravestonedoji|xsidegap3methods|morning(doji)?star|mathold|matchinglow|marubozu|belthold|breakaway)|ceil|cmo|tsf|typprice|t3|tema|tan(h)?|trix|trima|trange|obv|div|dema|dx|ultosc|ppo|plus_d[im]|errno|exp|ema|var|kama|floor|wclprice|willr|wma|ln|log10|bop|beta|bbands|linearreg(_(slope|intercept|angle))?|asin|acos|atan|atr|adosc|add??|adx(r)?|apo|avgprice|aroon(osc)?|rsi|rocp??|rocr(100)?|get_(compat|unstable_period)|min(index)?|minus_d[im]|minmax(index)?|mid(p(?:oint|rice))|mom|mult|medprice|mfi|macd(ext|fix)?|mavp|max(index)?|ma(ma)?)\\b`,`dgiv`),name:`support.function.trader.php`},{match:RegExp(`\\buopz_(copy|compose|implement|overload|delete|undefine|extend|function|flags|restore|rename|redefine|backup)\\b`,`dgiv`),name:`support.function.uopz.php`},{match:RegExp(`\\b(http_build_query|(raw)?url((?:de|en)code)|parse_url|get_(headers|meta_tags)|base64_((?:de|en)code))\\b`,`dgiv`),name:`support.function.url.php`},{match:RegExp(`\\b((bool|double|float|int|str)val|debug_zval_dump|empty|get_(debug_type|defined_vars|resource_(id|type))|[gs]ettype|is_(array|bool|callable|countable|double|float|int(eger)?|iterable|long|null|numeric|object|real|resource|scalar|string)|isset|print_r|(un)?serialize|unset|var_(dump|export))\\b`,`dgiv`),name:`support.function.var.php`},{match:RegExp(`\\bwddx_(serialize_(va(?:lue|rs))|deserialize|packet_(start|end)|add_vars)\\b`,`dgiv`),name:`support.function.wddx.php`},{match:RegExp(`\\bxhprof_(sample_)?((?:dis|en)able)\\b`,`dgiv`),name:`support.function.xhprof.php`},{match:RegExp(`\\b(utf8_((?:de|en)code)|xml_(set_((notation|(end|start)_namespace|unparsed_entity)_decl_handler|(character_data|default|element|external_entity_ref|processing_instruction)_handler|object)|parse(_into_struct)?|parser_([gs]et_option|create(_ns)?|free)|error_string|get_(current_((column|line)_number|byte_index)|error_code)))\\b`,`dgiv`),name:`support.function.xml.php`},{match:RegExp(`\\bxmlrpc_(server_(call_method|create|destroy|add_introspection_data|register_(introspection_callback|method))|is_fault|decode(_request)?|parse_method_descriptions|encode(_request)?|[gs]et_type)\\b`,`dgiv`),name:`support.function.xmlrpc.php`},{match:RegExp(`\\bxmlwriter_((end|start|write)_(comment|cdata|dtd(_(attlist|entity|element))?|document|pi|attribute|element)|(start|write)_(attribute|element)_ns|write_raw|set_indent(_string)?|text|output_memory|open_(memory|uri)|full_end_element|flush|)\\b`,`dgiv`),name:`support.function.xmlwriter.php`},{match:RegExp(`\\b(zlib_(decode|encode|get_coding_type)|readgzfile|gz(seek|compress|close|tell|inflate|open|decode|deflate|uncompress|puts|passthru|encode|eof|file|write|rewind|read|getc|getss?)|deflate_(add|init)|inflate_(add|get_(read_len|status)|init))\\b`,`dgiv`),name:`support.function.zlib.php`}]},switch_statement:{patterns:[{match:RegExp(`\\p{space}+(?=switch\\b)`,`dgv`)},{begin:RegExp(`\\bswitch\\b(?!\\p{space}*\\([^\\n]*\\)\\p{space}*:)`,`dgv`),beginCaptures:{0:{name:`keyword.control.switch.php`}},end:RegExp(`\\}|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.section.switch-block.end.bracket.curly.php`}},name:`meta.switch-statement.php`,patterns:[{begin:RegExp(`\\(`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.switch-expression.begin.bracket.round.php`}},end:RegExp(`\\)|(?=\\?>)`,`dgv`),endCaptures:{0:{name:`punctuation.definition.switch-expression.end.bracket.round.php`}},patterns:[{include:`$self`}]},{begin:RegExp(`\\{`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.section.switch-block.begin.bracket.curly.php`}},end:RegExp(`(?=\\}|\\?>)`,`dgv`),patterns:[{include:`$self`}]}]}]},ternary_expression:{begin:RegExp(`\\?`,`dgv`),beginCaptures:{0:{name:`keyword.operator.ternary.php`}},end:RegExp(`(?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*))\\p{space}*(?:(\\??->)\\p{space}*(([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*))|(\\[)(?:(\\p{Nd}+)|((\\$)([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*))|([_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*))(\\]))?`,`dgiv`,{hiddenCaptures:[6,11],transfers:[[3,[6,11]]]})},{captures:{1:{name:`variable.other.php`},2:{name:`punctuation.definition.variable.php`},4:{name:`punctuation.definition.variable.php`}},match:RegExp(`((\\$\\{)(?[_a-z\\x7F-\\u{10FFFF}][0-9_a-z\\x7F-\\u{10FFFF}]*)(\\}))`,`dgiv`)}]},variables:{patterns:[{include:`#var_language`},{include:`#var_global`},{include:`#var_global_safer`},{include:`#var_basic`},{begin:RegExp(`\\$\\{(?=[^\\n]*?\\})`,`dgv`),beginCaptures:{0:{name:`punctuation.definition.variable.php`}},end:RegExp(`\\}`,`dgv`),endCaptures:{0:{name:`punctuation.definition.variable.php`}},patterns:[{include:`$self`}]}]}},scopeName:`source.php`,embeddedLangs:[`html`,`xml`,`sql`,`javascript`,`json`,`css`],aliases:void 0}),NN=[...hN,...xN,...TN,...lN,...kN,...fN,MN]}));const FN=await jM({themes:[{name:`tempest`,type:`dark`,colors:{"editor.background":`var(--code-background)`,"editor.foreground":`var(--code-foreground)`,"editorLineNumber.foreground":`var(--code-gutter)`,"editorGutter.background":`var(--code-background)`,"editor.selectionBackground":`var(--code-highlight)`,"editor.lineHighlightBackground":`var(--code-highlight)`},tokenColors:[{scope:[`comment`,`punctuation.definition.comment`,`comment.block`,`comment.line`],settings:{foreground:`var(--code-comment)`,fontStyle:`italic`}},{scope:[`keyword`,`storage.type`,`storage.modifier`,`keyword.control`,`keyword.operator`,`keyword.other`],settings:{foreground:`var(--code-keyword)`}},{scope:[`variable.parameter`,`variable.other`,`variable.language`,`entity.name.variable`],settings:{foreground:`var(--code-variable)`}},{scope:[`entity.name.type`,`entity.name.class`,`support.type`,`support.class`],settings:{foreground:`var(--code-type)`}},{scope:[`entity.name.function`,`support.function`,`meta.function-call`],settings:{foreground:`var(--code-property)`}},{scope:[`string`,`string.quoted`,`string.template`,`constant.character`,`constant.numeric`,`constant.language`,`constant.other`],settings:{foreground:`var(--code-value)`}},{scope:[`entity.other.attribute-name`,`support.other.variable`,`variable.other.property`],settings:{foreground:`var(--code-property)`}},{scope:[`punctuation`,`meta.brace`,`punctuation.definition.string`,`punctuation.definition.variable`,`punctuation.definition.parameters`,`punctuation.definition.array`],settings:{foreground:`var(--code-foreground)`}},{scope:[`markup.bold`],settings:{fontStyle:`bold`}},{scope:[`markup.italic`],settings:{fontStyle:`italic`}},{scope:[`markup.inserted`],settings:{foreground:`var(--code-gutter-addition)`}},{scope:[`markup.deleted`],settings:{foreground:`var(--code-gutter-deletion)`}}]}],langs:[sN(()=>Promise.resolve().then(()=>(PN(),jN)),void 0,import.meta.url),sN(()=>Promise.resolve().then(()=>(AN(),DN)),void 0,import.meta.url),sN(()=>Promise.resolve().then(()=>(EN(),CN)),void 0,import.meta.url)],engine:rN()});function IN(e,t){return FN.codeToHtml(e,{lang:t,theme:`tempest`})}var LN=[`innerHTML`],RN={key:1,class:`flex justify-center items-center p-8 pt-4 font-mono uppercase`},zN=P({__name:`context`,props:{context:{}},setup(e){let t=e,n=W(()=>JSON.stringify(t.context??``,null,2));return(t,r)=>{let i=_D;return L(),z(i,{title:`Exception context`,icon:`tabler:info-circle`},{default:N(()=>[Object.values(e.context??{}).length>0?(L(),R(`div`,{key:0,class:`p-2 overflow-auto`,innerHTML:M(IN)(n.value,`json`)},null,8,LN)):(L(),R(`div`,RN,[...r[0]||=[B(`span`,{class:`text-muted`},`No context`,-1)]]))]),_:1})}}}),BN={base:`inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans uppercase`,variants:{color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},variant:{solid:``,outline:``,soft:``,subtle:``},size:{sm:`h-4 min-w-[16px] text-[10px]`,md:`h-5 min-w-[20px] text-[11px]`,lg:`h-6 min-w-[24px] text-[12px]`}},compoundVariants:[{color:`primary`,variant:`solid`,class:`text-inverted bg-primary`},{color:`secondary`,variant:`solid`,class:`text-inverted bg-secondary`},{color:`success`,variant:`solid`,class:`text-inverted bg-success`},{color:`info`,variant:`solid`,class:`text-inverted bg-info`},{color:`warning`,variant:`solid`,class:`text-inverted bg-warning`},{color:`error`,variant:`solid`,class:`text-inverted bg-error`},{color:`primary`,variant:`outline`,class:`ring ring-inset ring-primary/50 text-primary`},{color:`secondary`,variant:`outline`,class:`ring ring-inset ring-secondary/50 text-secondary`},{color:`success`,variant:`outline`,class:`ring ring-inset ring-success/50 text-success`},{color:`info`,variant:`outline`,class:`ring ring-inset ring-info/50 text-info`},{color:`warning`,variant:`outline`,class:`ring ring-inset ring-warning/50 text-warning`},{color:`error`,variant:`outline`,class:`ring ring-inset ring-error/50 text-error`},{color:`primary`,variant:`soft`,class:`text-primary bg-primary/10`},{color:`secondary`,variant:`soft`,class:`text-secondary bg-secondary/10`},{color:`success`,variant:`soft`,class:`text-success bg-success/10`},{color:`info`,variant:`soft`,class:`text-info bg-info/10`},{color:`warning`,variant:`soft`,class:`text-warning bg-warning/10`},{color:`error`,variant:`soft`,class:`text-error bg-error/10`},{color:`primary`,variant:`subtle`,class:`text-primary ring ring-inset ring-primary/25 bg-primary/10`},{color:`secondary`,variant:`subtle`,class:`text-secondary ring ring-inset ring-secondary/25 bg-secondary/10`},{color:`success`,variant:`subtle`,class:`text-success ring ring-inset ring-success/25 bg-success/10`},{color:`info`,variant:`subtle`,class:`text-info ring ring-inset ring-info/25 bg-info/10`},{color:`warning`,variant:`subtle`,class:`text-warning ring ring-inset ring-warning/25 bg-warning/10`},{color:`error`,variant:`subtle`,class:`text-error ring ring-inset ring-error/25 bg-error/10`},{color:`neutral`,variant:`solid`,class:`text-inverted bg-inverted`},{color:`neutral`,variant:`outline`,class:`ring ring-inset ring-accented text-default bg-default`},{color:`neutral`,variant:`soft`,class:`text-default bg-elevated`},{color:`neutral`,variant:`subtle`,class:`ring ring-inset ring-accented text-default bg-elevated`}],defaultVariants:{variant:`outline`,color:`neutral`,size:`md`}},VN={__name:`Kbd`,props:{as:{type:null,required:!1,default:`kbd`},value:{type:null,required:!1},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},class:{type:null,required:!1}},setup(e){let t=e,{getKbdKey:n}=Ef(),r=wf(),i=W(()=>Mw({extend:Mw(BN),...r.ui?.kbd||{}}));return(r,a)=>(L(),z(M(K),{as:e.as,class:A(i.value({class:t.class,color:t.color,variant:t.variant,size:t.size}))},{default:N(()=>[F(r.$slots,`default`,{},()=>[nc(ft(M(n)(e.value)),1)])]),_:3},8,[`as`,`class`]))}},HN={slots:{content:`flex items-center gap-1 bg-default text-highlighted shadow-sm rounded-sm ring ring-default h-6 px-2.5 py-1 text-xs select-none data-[state=delayed-open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-tooltip-content-transform-origin) pointer-events-auto`,arrow:`fill-default`,text:`truncate`,kbds:`hidden lg:inline-flex items-center shrink-0 gap-0.5 not-first-of-type:before:content-['·'] not-first-of-type:before:me-0.5`,kbdsSize:`sm`}},UN={__name:`Tooltip`,props:{text:{type:String,required:!1},kbds:{type:Array,required:!1},content:{type:Object,required:!1},arrow:{type:[Boolean,Object],required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:!0},reference:{type:null,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},defaultOpen:{type:Boolean,required:!1},open:{type:Boolean,required:!1},delayDuration:{type:Number,required:!1},disableHoverableContent:{type:Boolean,required:!1},disableClosingTrigger:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},ignoreNonKeyboardFocus:{type:Boolean,required:!1}},emits:[`update:open`],setup(e,{emit:t}){let n=e,r=t,i=lo(),a=wf(),o=bg(jd(n,`defaultOpen`,`open`,`delayDuration`,`disableHoverableContent`,`disableClosingTrigger`,`ignoreNonKeyboardFocus`),r),s=RS(lr(()=>n.portal)),c=lr(()=>Af(n.content,{side:`bottom`,sideOffset:8,collisionPadding:8})),l=lr(()=>n.arrow),u=W(()=>Mw({extend:Mw(HN),...a.ui?.tooltip||{}})({side:c.value.side}));return(t,r)=>(L(),z(M(jS),U(M(o),{disabled:!(e.text||e.kbds?.length||i.content)||n.disabled}),{default:N(({open:r})=>[i.default||e.reference?(L(),z(M(IS),U({key:0},t.$attrs,{"as-child":``,reference:e.reference,class:n.class}),{default:N(()=>[F(t.$slots,`default`,{open:r})]),_:2},1040,[`reference`,`class`])):H(``,!0),V(M(FS),it(ec(M(s))),{default:N(()=>[V(M(PS),U(c.value,{"data-slot":`content`,class:u.value.content({class:[!i.default&&n.class,n.ui?.content]})}),{default:N(()=>[F(t.$slots,`content`,{ui:u.value},()=>[e.text?(L(),R(`span`,{key:0,"data-slot":`text`,class:A(u.value.text({class:n.ui?.text}))},ft(e.text),3)):H(``,!0),e.kbds?.length?(L(),R(`span`,{key:1,"data-slot":`kbds`,class:A(u.value.kbds({class:n.ui?.kbds}))},[(L(!0),R(I,null,qa(e.kbds,(e,t)=>(L(),z(VN,U({key:t,size:n.ui?.kbdsSize||u.value.kbdsSize()},{ref_for:!0},typeof e==`string`?{value:e}:e),null,16,[`size`]))),128))],2)):H(``,!0)]),e.arrow?(L(),z(M(wS),U({key:0},l.value,{"data-slot":`arrow`,class:u.value.arrow({class:n.ui?.arrow})}),null,16,[`class`])):H(``,!0)]),_:3},16,[`class`])]),_:3},16)]),_:3},16,[`disabled`]))}},WN=P((e,{slots:t})=>{let n=Ln($d(e));return()=>{if(t.default)return t.default(n)}},{name:`UseClipboard`,props:[`source`,`read`,`navigator`,`copiedDuring`,`legacy`]}),GN={class:`flex flex-col gap-y-2.5 font-mono`},KN=[`textContent`,`onClick`],qN=[`textContent`,`onClick`],JN=P({__name:`headers`,props:{headers:{}},setup(e){return(t,n)=>{let r=UN,i=_D;return L(),z(i,{title:`Request headers`,icon:`tabler:http-head`},{default:N(()=>[B(`ul`,GN,[(L(!0),R(I,null,qa(e.headers,(e,t)=>(L(),R(`li`,{key:t,class:`flex items-baseline gap-x-2`},[V(M(WN),{source:t},{default:N(({copy:e,copied:n})=>[V(r,{text:t},{default:N(()=>[B(`span`,{class:A([`truncate transition-colors cursor-pointer`,[`text-muted uppercase`,n&&`text-success`]]),textContent:ft(t),onClick:t=>e()},null,10,KN)]),_:2},1032,[`text`])]),_:2},1032,[`source`]),n[0]||=B(`div`,{class:`border-muted border-b-2 border-dotted min-w-[15%] grow`},null,-1),V(M(WN),{source:e},{default:N(({copy:t,copied:n})=>[V(r,{text:e},{default:N(()=>[B(`span`,{class:A([`truncate transition-colors cursor-pointer`,n?`text-success`:void 0]),textContent:ft(e),onClick:e=>t()},null,10,qN)]),_:2},1032,[`text`])]),_:2},1032,[`source`])]))),128))])]),_:1})}}}),YN=[`innerHTML`],XN={key:1,class:`flex justify-center items-center p-8 pt-4 font-mono uppercase`},ZN=P({__name:`request-body`,props:{body:{}},setup(e){return(t,n)=>{let r=_D;return L(),z(r,{title:`Request body`,icon:`tabler:http-post`},{default:N(()=>[e.body?(L(),R(`div`,{key:0,class:`p-2`,innerHTML:M(IN)(e.body,`json`)},null,8,YN)):(L(),R(`div`,XN,[...n[0]||=[B(`span`,{class:`text-muted`},`No request body`,-1)]]))]),_:1})}}}),QN={slots:{root:``,content:`data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden`}},$N={__name:`Collapsible`,props:{as:{type:null,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},defaultOpen:{type:Boolean,required:!1},open:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0}},emits:[`update:open`],setup(e,{emit:t}){let n=e,r=t,i=lo(),a=wf(),o=bg(jd(n,`as`,`defaultOpen`,`open`,`disabled`,`unmountOnHide`),r),s=W(()=>Mw({extend:Mw(QN),...a.ui?.collapsible||{}})());return(e,t)=>(L(),z(M(e_),U(M(o),{"data-slot":`root`,class:s.value.root({class:[n.ui?.root,n.class]})}),{default:N(({open:t})=>[i.default?(L(),z(M(n_),{key:0,"as-child":``},{default:N(()=>[F(e.$slots,`default`,{open:t})]),_:2},1024)):H(``,!0),V(M(t_),{"data-slot":`content`,class:A(s.value.content({class:n.ui?.content}))},{default:N(()=>[F(e.$slots,`content`)]),_:3},8,[`class`])]),_:3},16,[`class`]))}};const eP=e=>Array.isArray(e)?e:[e],tP=(e,t)=>{let n=[[],[]];for(let r of e)t(r)?n[0].push(r):n[1].push(r);return n},nP=Array,rP=(e,t)=>e.includes(t),iP=(e,t=0)=>[...Array(e)].map((e,n)=>n+t),aP=(e,t,n)=>e===void 0?t===void 0?[]:Array.isArray(t)?t:[t]:(n?.prepend?Array.isArray(t)?e.unshift(...t):e.unshift(t):Array.isArray(t)?e.push(...t):e.push(t),e),oP=(e,t)=>t==null?e??[]:e==null?eP(t):e.concat(t),sP=(...e)=>e.reduce(oP,[]),cP=(e,t,n)=>{if(e===void 0)return Array.isArray(t)?t:[t];let r=n?.isEqual??((e,t)=>e===t);for(let n of eP(t))e.some(e=>r(e,n))||e.push(n);return e},lP=(e,t)=>e.reduce((e,n)=>{let r=n[t];return e[r]=aP(e[r],n),e},{}),uP=(e,t,n)=>e.length===t.length&&e.every(n?.isEqual?(e,r)=>n.isEqual(e,t[r]):(e,n)=>e===t[n]),dP=(e,t)=>fP(e)===t,fP=e=>{let t=typeof e;return t===`object`?e===null?`null`:`object`:t===`function`?`object`:t},pP={boolean:`boolean`,null:`null`,undefined:`undefined`,bigint:`a bigint`,number:`a number`,object:`an object`,string:`a string`,symbol:`a symbol`},mP={...pP,function:`a function`};var hP=class extends Error{};const gP=e=>_P(e,hP),_P=(e,t=Error)=>{throw new t(e)};var vP=class extends Error{name=`ParseError`};const Z=e=>_P(e,vP),yP=e=>` ${e}`,bP=(e,t)=>{let n={},r=Array.isArray(e),i=!1;for(let[a,o]of Object.entries(e).entries()){let e=r?t(a,o[1]):t(...o,a);i||=typeof e[0]==`number`;let s=Array.isArray(e[0])||e.length===0?e:[e];for(let[e,t]of s)typeof e==`object`?n[e.group]=aP(n[e.group],t):n[e]=t}return i?Object.values(n):n},xP=Object.entries,SP=(e,t)=>e in t,CP=(e,t)=>t in e;var wP=class{constructor(e){Object.assign(this,e)}};const TP=class{};var EP=class extends TP{};const DP=(e,t)=>{let n={},r={},i;for(i in e)i in t?n[i]=e[i]:r[i]=e[i];return[n,r]},OP=(e,t)=>DP(e,t)[1],kP=e=>Object.keys(e).length===0,AP=e=>[...Object.entries(e),...Object.getOwnPropertySymbols(e).map(t=>[t,e[t]])],jP=(e,t)=>Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)),MP=e=>{let t=Object.keys(e).sort(),n={};for(let r=0;rObject.values(e).filter(t=>typeof t==`number`?!0:typeof e[t]!=`number`),FP={Array,Boolean,Date,Error,Function,Map,Number,Promise,RegExp,Set,String,WeakMap,WeakSet},IP=globalThis.File??Blob,LP={ArrayBuffer,Blob,File:IP,FormData,Headers,Request,Response,URL},RP={Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array},zP={...FP,...LP,...RP,String,Number,Boolean},BP=e=>{let t=Object.getPrototypeOf(e);for(;t?.constructor&&(!SP(t.constructor.name,zP)||!(e instanceof zP[t.constructor.name]));)t=Object.getPrototypeOf(t);let n=t?.constructor?.name;if(!(n===void 0||n===`Object`))return n},VP=e=>typeof e==`object`&&e?BP(e)??`object`:fP(e),HP=Array.isArray,UP={Array:`an array`,Function:`a function`,Date:`a Date`,RegExp:`a RegExp`,Error:`an Error`,Map:`a Map`,Set:`a Set`,String:`a String object`,Number:`a Number object`,Boolean:`a Boolean object`,Promise:`a Promise`,WeakMap:`a WeakMap`,WeakSet:`a WeakSet`},WP={ArrayBuffer:`an ArrayBuffer instance`,Blob:`a Blob instance`,File:`a File instance`,FormData:`a FormData instance`,Headers:`a Headers instance`,Request:`a Request instance`,Response:`a Response instance`,URL:`a URL instance`},GP={Int8Array:`an Int8Array`,Uint8Array:`a Uint8Array`,Uint8ClampedArray:`a Uint8ClampedArray`,Int16Array:`an Int16Array`,Uint16Array:`a Uint16Array`,Int32Array:`an Int32Array`,Uint32Array:`a Uint32Array`,Float32Array:`a Float32Array`,Float64Array:`a Float64Array`,BigInt64Array:`a BigInt64Array`,BigUint64Array:`a BigUint64Array`},KP={...UP,...WP,...GP},qP=e=>{let t=Object(e).name??null;return t&&SP(t,zP)&&zP[t]===e?t:null},JP=(e,t)=>{let n=e.prototype;for(;n!==null;){if(n===t.prototype)return!0;n=Object.getPrototypeOf(n)}return!1},YP=e=>XP(e,new Map);var XP=(e,t)=>{if(typeof e!=`object`||!e)return e;if(t?.has(e))return t.get(e);let n=qP(e.constructor);if(n===`Date`)return new Date(e.getTime());if(n&&n!==`Array`)return e;let r=Array.isArray(e)?e.slice():Object.create(Object.getPrototypeOf(e)),i=Object.getOwnPropertyDescriptors(e);if(t)for(let n in t.set(e,r),i){let e=i[n];`get`in e||`set`in e||(e.value=XP(e.value,t))}return Object.defineProperties(r,i),r};const ZP=e=>{let t=NP;return()=>t===NP?t=e():t},QP=e=>typeof e==`function`&&e.length===0,$P=class extends Function{constructor(...e){let t=e.slice(0,-1),n=e[e.length-1];try{super(...t,n)}catch(t){return gP(`Encountered an unexpected error while compiling your definition: + Message: ${t} + Source: (${e.slice(0,-1)}) => { + ${e[e.length-1]} + }`)}}};var eF=class{constructor(e,...[t]){return Object.assign(Object.setPrototypeOf(e.bind(t?.bind??this),this.constructor.prototype),t?.attach)}};const tF=ZP(()=>{try{return Function(`return false`)()}catch{return!0}});yP(`brand`),yP(`arkInferred`),yP(`args`);var nF=class{constructor(){}};const rF={fileName:()=>{try{return((Error().stack?.split(` +`)[2]?.trim()||``).match(/\(?(.+?)(?::\d+:\d+)?\)?$/)?.[1]||`unknown`).replace(/^file:\/\//,``)}catch{return`unknown`}},env:{}},iF=e=>e[0].toUpperCase()+e.slice(1),aF=e=>e[0].toLowerCase()+e.slice(1),oF=e=>new RegExp(sF(e),typeof e==`string`?``:e.flags),sF=e=>`^(?:${typeof e==`string`?e:e.source})$`,cF={negativeLookahead:e=>`(?!${e})`,nonCapturingGroup:e=>`(?:${e})`},lF={" ":1,"\n":1," ":1};var uF=`^-0\\.?0*$`,dF=`[1-9]\\d*`,fF=`\\.\\d+`,pF=`\\.\\d*[1-9]`,mF=e=>oF(cF.negativeLookahead(uF)+cF.nonCapturingGroup(`-?`+cF.nonCapturingGroup(cF.nonCapturingGroup(`0|`+dF)+cF.nonCapturingGroup(e.decimalPattern)+`?`)+(e.allowDecimalOnly?`|`+e.decimalPattern:``)+`?`));const hF=mF({decimalPattern:pF,allowDecimalOnly:!1}),gF=hF.test.bind(hF),_F=mF({decimalPattern:fF,allowDecimalOnly:!0});_F.test.bind(_F);const vF=/^-?\d*\.?\d*$/;var yF=e=>e.length!==0&&vF.test(e);const bF=oF(cF.negativeLookahead(`^-0$`)+`-?`+cF.nonCapturingGroup(cF.nonCapturingGroup(`0|`+dF))),xF=bF.test.bind(bF),SF=/^-?\d+$/;var CF=SF.test.bind(SF),wF={number:`a number`,bigint:`a bigint`,integer:`an integer`};const TF=(e,t)=>`'${e}' was parsed as ${wF[t]} but could not be narrowed to a literal value. Avoid unnecessary leading or trailing zeros and other abnormal notation`;var EF=(e,t)=>t===`number`?gF(e):xF(e),DF=(e,t)=>t===`number`?Number(e):Number.parseInt(e),OF=(e,t)=>t===`number`?yF(e):CF(e);const kF=(e,t)=>MF(e,`number`,t),AF=(e,t)=>MF(e,`number`,{...t,strict:!0}),jF=(e,t)=>MF(e,`integer`,t);var MF=(e,t,n)=>{let r=DF(e,t);return!Number.isNaN(r)&&OF(e,t)?n?.strict?EF(e,t)?r:Z(TF(e,t)):r:n?.errorOnFail?Z(n?.errorOnFail===!0?`Failed to parse ${wF[t]} from '${e}'`:n?.errorOnFail):void 0};const NF=e=>{if(e[e.length-1]!==`n`)return;let t=e.slice(0,-1),n;try{n=BigInt(t)}catch{return}if(bF.test(t))return n;if(SF.test(t))return Z(TF(e,`bigint`))},PF={version:`0.56.0`,filename:rF.fileName(),FileConstructor:IP};var FF=new Map,IF=Object.create(null);const LF=e=>{let t=FF.get(e);if(t)return t;let n=zF(e);return IF[n]?n=`${n}${IF[n]++}`:IF[n]=1,PF[n]=e,FF.set(e,n),n},RF=e=>/^[$A-Z_a-z][\w$]*$/.test(e);var zF=e=>{switch(typeof e){case`object`:{if(e===null)break;let t=BP(e)??`object`;return t[0].toLowerCase()+t.slice(1)}case`function`:return RF(e.name)?e.name:`fn`;case`symbol`:return e.description&&RF(e.description)?e.description:`symbol`}return gP(`Unexpected attempt to register serializable value of type ${fP(e)}`)};const BF=e=>typeof e==`string`?JSON.stringify(e):typeof e==`bigint`?`${e}n`:`${e}`,VF=(e,t={})=>GF(e,{onUndefined:`$ark.undefined`,onBigInt:e=>`$ark.bigint-${e}`,...t},[]),HF=(e,t)=>{switch(fP(e)){case`object`:let n=e,r=n.constructor?.name??`Object`;return r===`Object`||r===`Array`?t?.quoteKeys===!1?UF(n,t?.indent??0,``):JSON.stringify(GF(n,WF,[]),null,t?.indent):UF(n,t?.indent??0,``);case`symbol`:return WF.onSymbol(e);default:return BF(e)}};var UF=(e,t,n)=>{if(typeof e==`function`)return WF.onFunction(e);if(typeof e!=`object`||!e)return BF(e);let r=n+` `.repeat(t);if(Array.isArray(e)){if(e.length===0)return`[]`;let i=e.map(e=>UF(e,t,r)).join(`, +`+r);return t?`[\n${r}${i}\n${n}]`:`[${i}]`}let i=e.constructor?.name??`Object`;if(i===`Object`){let i=AP(e).map(([e,n])=>`${r}${typeof e==`symbol`?WF.onSymbol(e):RF(e)?e:JSON.stringify(e)}: ${UF(n,t,r)}`);return i.length===0?`{}`:t?`{\n${i.join(`, +`)}\n${n}}`:`{${i.join(`, `)}}`}return e instanceof Date?KF(e):`expression`in e&&typeof e.expression==`string`?e.expression:i},WF={onCycle:()=>`(cycle)`,onSymbol:e=>`Symbol(${LF(e)})`,onFunction:e=>`Function(${LF(e)})`},GF=(e,t,n)=>{switch(fP(e)){case`object`:{let r=e;if(`toJSON`in r&&typeof r.toJSON==`function`)return r.toJSON();if(typeof r==`function`)return WF.onFunction(r);if(n.includes(r))return`(cycle)`;let i=[...n,r];if(Array.isArray(r))return r.map(e=>GF(e,t,i));if(r instanceof Date)return r.toDateString();let a={};for(let e in r)a[e]=GF(r[e],t,i);for(let e of Object.getOwnPropertySymbols(r))a[t.onSymbol?.(e)??e.toString()]=GF(r[e],t,i);return a}case`symbol`:return WF.onSymbol(e);case`bigint`:return t.onBigInt?.(e)??`${e}n`;case`undefined`:return t.onUndefined??`undefined`;case`string`:return e.replace(/\\/g,`\\\\`);default:return e}};const KF=e=>{let t=e.getFullYear(),n=e.getMonth(),r=e.getDate(),i=e.getHours(),a=e.getMinutes(),o=e.getSeconds(),s=e.getMilliseconds();if(n===0&&r===1&&i===0&&a===0&&o===0&&s===0)return`${t}`;let c=`${qF[n]} ${r}, ${t}`;if(i===0&&a===0&&o===0&&s===0)return c;let l=e.toLocaleTimeString(),u=l.endsWith(` AM`)||l.endsWith(` PM`)?l.slice(-3):``;return u&&(l=l.slice(0,-u.length)),s?l+=`.${YF(s,3)}`:JF.test(l)&&(l=l.slice(0,-3)),`${l+u}, ${c}`};var qF=[`January`,`February`,`March`,`April`,`May`,`June`,`July`,`August`,`September`,`October`,`November`,`December`],JF=/:\d\d:00$/,YF=(e,t)=>String(e).padStart(t,`0`);const XF=(e,t,...[n])=>{let r=n?.stringifySymbol??HF,i=e;switch(typeof t){case`string`:i=RF(t)?e===``?t:`${e}.${t}`:`${e}[${JSON.stringify(t)}]`;break;case`number`:i=`${e}[${t}]`;break;case`symbol`:i=`${e}[${r(t)}]`;break;default:n?.stringifyNonKey?i=`${e}[${n.stringifyNonKey(t)}]`:Z(`${HF(t)} must be a PropertyKey or stringifyNonKey must be passed to options`)}return i},ZF=(e,...t)=>e.reduce((e,n)=>XF(e,n,...t),``);var QF=class extends nP{cache={};constructor(...e){super(),this.push(...e)}toJSON(){if(this.cache.json)return this.cache.json;this.cache.json=[];for(let e=0;et.lookahead===e):this.shiftUntil(t=>t.lookahead in e)}shiftUntilNonWhitespace(){return this.shiftUntil(()=>!(this.lookahead in lF))}jumpToIndex(e){this.i=e<0?this.length+e:e}jumpForward(e){this.i+=e}get location(){return this.i}get unscanned(){return this.chars.slice(this.i,this.length).join(``)}get scanned(){return this.chars.slice(0,this.i).join(``)}sliceChars(e,t){return this.chars.slice(e,t).join(``)}lookaheadIs(e){return this.lookahead===e}lookaheadIsIn(e){return this.lookahead in e}};const eI=(e,t)=>`Unmatched ${e}${t===``?``:` before ${t}`}`,tI=e=>`Missing ${e}`;yP(`implementedTraits`),Symbol.hasInstance;for(var nI=`$ark`,rI=2;nI in globalThis;)nI=`$ark${rI++}`;const iI=nI;globalThis[iI]=PF;const Q=PF,aI=e=>`${iI}.${e}`,oI=e=>aI(LF(e));var sI=class extends EP{argNames;body=``;constructor(...e){super(),this.argNames=e;for(let t of e){if(t in this)throw Error(`Arg name '${t}' would overwrite an existing property on FunctionBody`);this[t]=t}}indentation=0;indent(){return this.indentation+=4,this}dedent(){return this.indentation-=4,this}prop(e,t=!1){return lI(e,t)}index(e,t=!1){return dI(`${e}`,t)}line(e){return this.body+=`${` `.repeat(this.indentation)}${e}\n`,this}const(e,t){return this.line(`const ${e} = ${t}`),this}let(e,t){return this.line(`let ${e} = ${t}`)}set(e,t){return this.line(`${e} = ${t}`)}if(e,t){return this.block(`if (${e})`,t)}elseIf(e,t){return this.block(`else if (${e})`,t)}else(e){return this.block(`else`,e)}for(e,t,n=0){return this.block(`for (let i = ${n}; ${e}; i++)`,t)}forIn(e,t){return this.block(`for (const k in ${e})`,t)}block(e,t,n=``){return this.line(`${e} {`),this.indent(),t(this),this.dedent(),this.line(`}${n}`)}return(e=``){return this.line(`return ${e}`)}write(e=`anonymous`,t=0){return`${e}(${this.argNames.join(`, `)}) { ${t?this.body.split(` +`).map(e=>` `.repeat(t)+`${e}`).join(` +`):this.body} }`}compile(){return new $P(...this.argNames,this.body)}};const cI=e=>dP(e,`object`)||typeof e==`symbol`?oI(e):BF(e),lI=(e,t=!1)=>typeof e==`string`&&RF(e)?`${t?`?`:``}.${e}`:dI(uI(e),t),uI=e=>typeof e==`symbol`?oI(e):JSON.stringify(e),dI=(e,t=!1)=>`${t?`?.`:``}[${e}]`;var fI=class extends sI{traversalKind;optimistic;constructor(e){super(`data`,`ctx`),this.traversalKind=e.kind,this.optimistic=e.optimistic===!0}invoke(e,t){let n=t?.arg??this.data,r=typeof e==`string`?!0:this.requiresContextFor(e),i=typeof e==`string`?e:e.id;return r?`${this.referenceToId(i,t)}(${n}, ${this.ctx})`:`${this.referenceToId(i,t)}(${n})`}referenceToId(e,t){let n=`this.${e}${t?.kind??this.traversalKind}`;return t?.bind?`${n}.bind(${t?.bind})`:n}requiresContextFor(e){return this.traversalKind===`Apply`||e.allowsRequiresContext}initializeErrorCount(){return this.const(`errorCount`,`ctx.currentErrorCount`)}returnIfFail(){return this.if(`ctx.currentErrorCount > errorCount`,()=>this.return())}returnIfFailFast(){return this.if(`ctx.failFast && ctx.currentErrorCount > errorCount`,()=>this.return())}traverseKey(e,t,n){let r=this.requiresContextFor(n);return r&&this.line(`${this.ctx}.path.push(${e})`),this.check(n,{arg:t}),r&&this.line(`${this.ctx}.path.pop()`),this}check(e,t){return this.traversalKind===`Allows`?this.if(`!${this.invoke(e,t)}`,()=>this.return(!1)):this.line(this.invoke(e,t))}};const pI=e=>bP(e,(e,t)=>[e,HP(t)?[...t]:t]),mI=yP(`arkKind`),hI=(e,t)=>e?.[mI]===t,gI=e=>hI(e,`root`)||hI(e,`constraint`),_I=[`unit`,`proto`,`domain`],vI=[`required`,`optional`,`index`,`sequence`],yI=[`pattern`,`divisor`,`exactLength`,`max`,`min`,`maxLength`,`minLength`,`before`,`after`],bI=[...yI,`structure`,`predicate`],xI=[...bI,...vI],SI=[`alias`,`union`,`morph`,`unit`,`intersection`,`proto`,`domain`],CI=[...SI,...xI],wI=bP(xI,(e,t)=>[t,1]),TI=bP([...vI,`undeclared`],(e,t)=>[t,1]),EI=bP(CI,(e,t)=>[t,e]),DI=e=>typeof e==`string`&&e in EI,OI=e=>EI[e],kI=e=>SI.slice(OI(e)+1);[...kI(`union`)],[...kI(`morph`)];const AI=e=>typeof e==`string`||typeof e==`boolean`||e===null?e:typeof e==`number`?Number.isNaN(e)?`NaN`:e===1/0?`Infinity`:e===-1/0?`-Infinity`:e:cI(e),jI=e=>{let t=`{ `;for(let[n,r]of Object.entries(e))t+=`${n}: ${cI(r)}, `;return t+` }`},MI=e=>{let t=e;return t.hasAssociatedError&&(t.defaults.expected??=e=>`description`in e?e.description:t.defaults.description(e),t.defaults.actual??=e=>HF(e),t.defaults.problem??=e=>`must be ${e.expected}${e.actual?` (was ${e.actual})`:``}`,t.defaults.message??=e=>{if(e.path.length===0)return e.problem;let t=`${e.propString} ${e.problem}`;return t[0]===`[`?`value at ${t}`:t}),t},NI={Error:class extends Error{name=`ToJsonSchemaError`;code;context;constructor(e,t){super(HF(t,{quoteKeys:!1,indent:4})),this.code=e,this.context=t}hasCode(e){return this.code===e}},throw:(...e)=>{throw new NI.Error(...e)},throwInternalOperandError:(e,t)=>gP(`Unexpected JSON Schema input for ${e}: ${HF(t)}`),defaultConfig:{target:`draft-2020-12`,dialect:`https://json-schema.org/draft/2020-12/schema`,useRefs:!1,fallback:{arrayObject:e=>NI.throw(`arrayObject`,e),arrayPostfix:e=>NI.throw(`arrayPostfix`,e),defaultValue:e=>NI.throw(`defaultValue`,e),domain:e=>NI.throw(`domain`,e),morph:e=>NI.throw(`morph`,e),patternIntersection:e=>NI.throw(`patternIntersection`,e),predicate:e=>NI.throw(`predicate`,e),proto:e=>NI.throw(`proto`,e),symbolKey:e=>NI.throw(`symbolKey`,e),unit:e=>NI.throw(`unit`,e),date:e=>NI.throw(`date`,e)}}};Q.config??={};const PI=(e,t)=>{if(!t)return e;let n={...e},r;for(r in t){let i={...e.keywords};if(r===`keywords`){for(let e in t[r]){let n=t.keywords[e];n!==void 0&&(i[e]=typeof n==`string`?{description:n}:n)}n.keywords=i}else r===`toJsonSchema`?n[r]=II(e.toJsonSchema,t.toJsonSchema):DI(r)?n[r]={...e[r],...t[r]}:n[r]=t[r]}return n};var FI={"draft-2020-12":`https://json-schema.org/draft/2020-12/schema`,"draft-07":`http://json-schema.org/draft-07/schema#`};const II=((e,t)=>{if(!e)return LI(t??{},void 0);if(!t)return e;let n={...e},r;for(r in t)r===`fallback`?n.fallback=RI(e.fallback,t.fallback):n[r]=t[r];return LI(n,t)});var LI=(e,t)=>t?.dialect===void 0?t?.target===void 0?e:{...e,dialect:FI[t.target]}:e,RI=(e,t)=>{e=zI(e),t=zI(t);let n={},r;for(r in NI.defaultConfig.fallback)n[r]=t[r]??t.default??e[r]??e.default??NI.defaultConfig.fallback[r];return n},zI=e=>typeof e==`function`?{default:e}:e??{},BI=class e extends EP{[mI]=`error`;path;data;nodeConfig;input;ctx;constructor({prefixPath:e,relativePath:t,...n},r){super(),this.input=n,this.ctx=r,jP(this,n);let i=r.data;n.code===`union`&&(n.errors=n.errors.flatMap(n=>{let r=n.hasCode(`union`)?n.errors:[n];return!e&&!t?r:r.map(n=>n.transform(n=>({...n,path:sP(e,n.path,t)})))})),this.nodeConfig=r.config[this.code];let a=[...n.path??r.path];t&&a.push(...t),e&&a.unshift(...e),this.path=new QF(...a),this.data=`data`in n?n.data:i}transform(t){return new e(t({data:this.data,path:this.path,...this.input}),this.ctx)}hasCode(e){return this.code===e}get propString(){return ZF(this.path)}get expected(){if(this.input.expected)return this.input.expected;let e=this.meta?.expected??this.nodeConfig.expected;return typeof e==`function`?e(this.input):e}get actual(){if(this.input.actual)return this.input.actual;let e=this.meta?.actual??this.nodeConfig.actual;return typeof e==`function`?e(this.data):e}get problem(){if(this.input.problem)return this.input.problem;let e=this.meta?.problem??this.nodeConfig.problem;return typeof e==`function`?e(this):e}get message(){if(this.input.message)return this.input.message;let e=this.meta?.message??this.nodeConfig.message;return typeof e==`function`?e(this):e}get flat(){return this.hasCode(`intersection`)?[...this.errors]:[this]}toJSON(){return{data:this.data,path:this.path,...this.input,expected:this.expected,actual:this.actual,problem:this.problem,message:this.message}}toString(){return this.message}throw(){throw this}},VI=class e extends nP{[mI]=`errors`;ctx;constructor(e){super(),this.ctx=e}byPath=Object.create(null);get flatByPath(){return bP(this.byPath,(e,t)=>[e,t.flat])}get flatProblemsByPath(){return bP(this.byPath,(e,t)=>[e,t.flat.map(e=>e.problem)])}byAncestorPath=Object.create(null);count=0;mutable=this;throw(){throw this.toTraversalError()}toTraversalError(){return new HI(this)}add(e){let t=this.byPath[e.propString];if(t){if(e===t||t.hasCode(`union`)&&t.errors.length===0)return;let n=e.hasCode(`union`)&&e.errors.length===0?e:new BI({code:`intersection`,errors:t.hasCode(`intersection`)?[...t.errors,e]:[t,e]},this.ctx),r=this.indexOf(t);this.mutable[r===-1?this.length:r]=n,this.byPath[e.propString]=n,this.addAncestorPaths(e)}else this.byPath[e.propString]=e,this.addAncestorPaths(e),this.mutable.push(e);this.count++}transform(t){let n=new e(this.ctx);for(let e of this)n.add(t(e));return n}merge(e){for(let t of e)this.add(new BI({...t,path:[...this.ctx.path,...t.path]},this.ctx))}affectsPath(e){return this.length===0?!1:e.stringifyAncestors().some(e=>e in this.byPath)||e.stringify()in this.byAncestorPath}get summary(){return this.toString()}get issues(){return this}toJSON(){return[...this.map(e=>e.toJSON())]}toString(){return this.join(` +`)}addAncestorPaths(e){for(let t of e.path.stringifyAncestors())this.byAncestorPath[t]=aP(this.byAncestorPath[t],e)}},HI=class extends Error{name=`TraversalError`;constructor(e){e.length===1?super(e.summary):super(` +`+e.map(e=>` • ${UI(e)}`).join(` +`)),Object.defineProperty(this,`arkErrors`,{value:e,enumerable:!1})}},UI=e=>e.toString().split(` +`).join(` + `),WI=class{path=[];errors=new VI(this);root;config;queuedMorphs=[];branches=[];seen={};constructor(e,t){this.root=e,this.config=t}get data(){let e=this.root;for(let t of this.path)e=e?.[t];return e}get propString(){return ZF(this.path)}reject(e){return this.error(e),!1}mustBe(e){return this.error(e),!1}error(e){let t=typeof e==`object`?e.code?e:{...e,code:`predicate`}:{code:`predicate`,expected:e};return this.errorFromContext(t)}hasError(){return this.currentErrorCount!==0}get currentBranch(){return this.branches[this.branches.length-1]}queueMorphs(e){let t={path:new QF(...this.path),morphs:e};this.currentBranch?this.currentBranch.queuedMorphs.push(t):this.queuedMorphs.push(t)}finalize(e){return this.queuedMorphs.length&&(typeof this.root==`object`&&this.root!==null&&this.config.clone&&(this.root=this.config.clone(this.root)),this.applyQueuedMorphs()),this.hasError()?e?e(this.errors):this.errors:this.root}get currentErrorCount(){return this.currentBranch?this.currentBranch.error?1:0:this.errors.count}get failFast(){return this.branches.length!==0}pushBranch(){this.branches.push({error:void 0,queuedMorphs:[]})}popBranch(){return this.branches.pop()}get external(){return this}errorFromNodeContext(e){return this.errorFromContext(e)}errorFromContext(e){let t=new BI(e,this);return this.currentBranch?this.currentBranch.error=t:this.errors.add(t),t}applyQueuedMorphs(){for(;this.queuedMorphs.length;){let e=this.queuedMorphs;this.queuedMorphs=[];for(let{path:t,morphs:n}of e)this.errors.affectsPath(t)||this.applyMorphsAtPath(t,n)}}applyMorphsAtPath(e,t){let n=e[e.length-1],r;if(n!==void 0){r=this.root;for(let t=0;t{if(!n)return t();n.path.push(e);let r=t();return n.path.pop(),r};var KI=class extends eF{attachments;$;onFail;includesTransform;includesContextualPredicate;isCyclic;allowsRequiresContext;rootApplyStrategy;contextFreeMorph;rootApply;referencesById;shallowReferences;flatRefs;flatMorphs;allows;get shallowMorphs(){return[]}constructor(e,t){super((e,t,n=this.onFail)=>t?(this.traverseApply(e,t),t.hasError()?t.errors:t.data):this.rootApply(e,n),{attach:e}),this.attachments=e,this.$=t,this.onFail=this.meta.onFail??this.$.resolvedConfig.onFail,this.includesTransform=this.hasKind(`morph`)||this.hasKind(`structure`)&&this.structuralMorph!==void 0||this.hasKind(`sequence`)&&this.inner.defaultables!==void 0,this.includesContextualPredicate=this.hasKind(`predicate`)&&this.inner.predicate.length!==1,this.isCyclic=this.kind===`alias`,this.referencesById={[this.id]:this},this.shallowReferences=this.hasKind(`structure`)?[this,...this.children]:this.children.reduce((e,t)=>tL(e,t.shallowReferences),[this]);let n=this.isStructural();this.flatRefs=[],this.flatMorphs=[];for(let e=0;e$I(e,n))){this.flatRefs.push(n);for(let e of n.node.branches)(e.hasKind(`morph`)||e.hasKind(`intersection`)&&e.structure?.structuralMorph!==void 0)&&this.flatMorphs.push({path:n.path,propString:n.propString,node:e})}}}Object.assign(this.referencesById,this.children[e].referencesById)}this.flatRefs.sort((e,t)=>e.path.length>t.path.length?1:e.path.lengtht.propString?1:e.propStringe.length===1||e.name===`$arkStructuralMorph`)?this.hasKind(`union`)?this.branches.some(e=>e.shallowMorphs.length>1)?`contextual`:`branchedOptimistic`:this.shallowMorphs.length>1?`contextual`:`optimistic`:`contextual`:`contextual`,this.rootApply=this.createRootApply(),this.allows=this.allowsRequiresContext?e=>this.traverseAllows(e,new WI(e,this.$.resolvedConfig)):e=>this.traverseAllows(e)}createRootApply(){switch(this.rootApplyStrategy){case`allows`:return(e,t)=>{if(this.allows(e))return e;let n=new WI(e,this.$.resolvedConfig);return this.traverseApply(e,n),n.finalize(t)};case`contextual`:return(e,t)=>{let n=new WI(e,this.$.resolvedConfig);return this.traverseApply(e,n),n.finalize(t)};case`optimistic`:this.contextFreeMorph=this.shallowMorphs[0];let e=this.$.resolvedConfig.clone;return(t,n)=>{if(this.allows(t))return this.contextFreeMorph(e&&(typeof t==`object`&&t||typeof t==`function`)?e(t):t);let r=new WI(t,this.$.resolvedConfig);return this.traverseApply(t,r),r.finalize(n)};case`branchedOptimistic`:return this.createBranchedOptimisticRootApply();default:return this.rootApplyStrategy,gP(`Unexpected rootApplyStrategy ${this.rootApplyStrategy}`)}}compiledMeta=ZI(this.metaJson);cacheGetter(e,t){return Object.defineProperty(this,e,{value:t}),t}get description(){return this.cacheGetter(`description`,this.meta?.description??this.$.resolvedConfig[this.kind].description(this))}get references(){return Object.values(this.referencesById)}precedence=OI(this.kind);precompilation;assert=(e,t)=>this(e,t,e=>e.throw());traverse(e,t){return this(e,t,null)}get in(){return this.cacheGetter(`in`,this.rawIn.isRoot()?this.$.finalize(this.rawIn):this.rawIn)}get rawIn(){return this.cacheGetter(`rawIn`,this.getIo(`in`))}get out(){return this.cacheGetter(`out`,this.rawOut.isRoot()?this.$.finalize(this.rawOut):this.rawOut)}get rawOut(){return this.cacheGetter(`rawOut`,this.getIo(`out`))}getIo(e){if(!this.includesTransform)return this;let t={};for(let[n,r]of this.innerEntries){let i=this.impl.keys[n];if(i.reduceIo)i.reduceIo(e,t,r);else if(i.child){let i=r;t[n]=HP(i)?i.map(t=>e===`in`?t.rawIn:t.rawOut):e===`in`?i.rawIn:i.rawOut}else t[n]=r}return this.$.node(this.kind,t)}toJSON(){return this.json}toString(){return`Type<${this.expression}>`}equals(e){let t=gI(e)?e:this.$.parseDefinition(e);return this.innerHash===t.innerHash}ifEquals(e){return this.equals(e)?this:void 0}hasKind(e){return this.kind===e}assertHasKind(e){return this.kind!==e&&_P(`${this.kind} node was not of asserted kind ${e}`),this}hasKindIn(...e){return e.includes(this.kind)}assertHasKindIn(...e){return rP(e,this.kind)||_P(`${this.kind} node was not one of asserted kinds ${e}`),this}isBasis(){return rP(_I,this.kind)}isConstraint(){return rP(xI,this.kind)}isStructural(){return rP(vI,this.kind)}isRefinement(){return rP(bI,this.kind)}isRoot(){return rP(SI,this.kind)}isUnknown(){return this.hasKind(`intersection`)&&this.children.length===0}isNever(){return this.hasKind(`union`)&&this.children.length===0}hasUnit(e){return this.hasKind(`unit`)&&this.allows(e)}hasOpenIntersection(){return this.impl.intersectionIsOpen}get nestableExpression(){return this.expression}select(e){let t=qI.normalize(e);return this._select(t)}_select(e){let t=qI.applyBoundary[e.boundary??`references`](this);return e.kind&&(t=t.filter(t=>t.kind===e.kind)),e.where&&(t=t.filter(e.where)),qI.applyMethod[e.method??`filter`](t,this,e)}transform(e,t){return this._transform(e,this._createTransformContext(t))}_createTransformContext(e){return{root:this,selected:void 0,seen:{},path:[],parseOptions:{prereduced:e?.prereduced??!1},undeclaredKeyHandling:void 0,...e}}_transform(e,t){let n=t.bindScope??this.$;if(t.seen[this.id])return this.$.lazilyResolve(t.seen[this.id]);if(t.shouldTransform?.(this,t)===!1)return this;let r;t.seen[this.id]=()=>r,this.hasKind(`structure`)&&this.undeclared!==t.undeclaredKeyHandling&&(t={...t,undeclaredKeyHandling:this.undeclared});let i=bP(this.inner,(n,r)=>{if(!this.impl.keys[n].child)return[n,r];let i=r;if(!HP(i)){let r=i._transform(e,t);return r?[n,r]:[]}if(i.length===0)return[n,r];let a=i.flatMap(n=>n._transform(e,t)??[]);return a.length?[n,a]:[]});delete t.seen[this.id];let a=Object.assign(i,{meta:this.meta}),o=t.selected&&!t.selected.includes(this)?a:e(this.kind,a,t);if(o===null)return null;if(gI(o))return r=o;let s=Object.keys(o);return(s.length===0||s.length===1&&s[0]===`meta`)&&!kP(this.inner)?null:(this.kind===`required`||this.kind===`optional`||this.kind===`index`)&&!(`value`in o)?t.undeclaredKeyHandling?{...o,value:Q.intrinsic.unknown}:null:(this.kind===`morph`&&(o.in??=Q.intrinsic.unknown),r=n.node(this.kind,o,t.parseOptions))}configureReferences(e,t=`references`){let n=qI.normalize(t),r=typeof e==`string`?(t,n)=>({...n,meta:{...n.meta,description:e}}):typeof e==`function`?(t,n)=>({...n,meta:e(n.meta)}):(t,n)=>({...n,meta:{...n.meta,...e}});if(n.boundary===`self`)return this.$.node(this.kind,r(this.kind,{...this.inner,meta:this.meta}));let i=this._select(n),a=i&&eP(i),o=n.boundary===`child`?(e,t)=>t.root.children.includes(e):n.boundary===`shallow`?e=>e.kind!==`structure`:()=>!0;return this.$.finalize(this.transform(r,{shouldTransform:o,selected:a}))}},qI={applyBoundary:{self:e=>[e],child:e=>[...e.children],shallow:e=>[...e.shallowReferences],references:e=>[...e.references]},applyMethod:{filter:e=>e,assertFilter:(e,t,n)=>(e.length===0&&_P(JI(t,n)),e),find:e=>e[0],assertFind:(e,t,n)=>(e.length===0&&_P(JI(t,n)),e[0])},normalize:e=>typeof e==`function`?{boundary:`references`,method:`filter`,where:e}:typeof e==`string`?SP(e,qI.applyBoundary)?{method:`filter`,boundary:e}:{boundary:`references`,method:`filter`,kind:e}:{boundary:`references`,method:`filter`,...e}},JI=(e,t)=>`${e} had no references matching ${HF(t)}.`;const YI=e=>ZF(e,{stringifyNonKey:e=>e.expression});var XI=/"(\$ark\.[^"]+)"/g,ZI=e=>JSON.stringify(e).replace(XI,`$1`);const QI=(e,t)=>({path:e,node:t,propString:YI(e)}),$I=(e,t)=>e.propString===t.propString&&e.node.equals(t.node),eL=(e,t)=>cP(e,t,{isEqual:$I}),tL=(e,t)=>cP(e,t,{isEqual:(e,t)=>e.equals(t)});var $=class e extends Array{static init(t,n,r,i){return new e({kind:t,l:n,r,path:i?.path??[],optional:i?.optional??!1})}add(e,t,n,r){return this.push({kind:e,l:t,r:n,path:r?.path??[],optional:r?.optional??!1}),this}get summary(){return this.describeReasons()}describeReasons(){if(this.length===1){let{path:e,l:t,r:n}=this[0],r=ZF(e);return iL(`Intersection${r&&` at ${r}`} of ${nL(t,n)}`)}return`The following intersections result in unsatisfiable types:\n• ${this.map(({path:e,l:t,r:n})=>`${e}: ${nL(t,n)}`).join(` +• `)}`}throw(){return Z(this.describeReasons())}invert(){let t=this.map(e=>({...e,l:e.r,r:e.l}));return t instanceof e?t:new e(...t)}withPrefixKey(e,t){return this.map(n=>({...n,path:[e,...n.path],optional:n.optional||t===`optional`}))}toNeverIfDisjoint(){return Q.intrinsic.never}},nL=(e,t)=>`${rL(e)} and ${rL(t)}`,rL=e=>gI(e)?e.expression:HP(e)?e.map(rL).join(` | `)||`never`:String(e);const iL=e=>`${e} results in an unsatisfiable type`;var aL={};const oL=(e,t,n)=>cL(e,t,{$:n,invert:!1,pipe:!1}),sL=(e,t,n)=>cL(e,t,{$:n,invert:!1,pipe:!0}),cL=((e,t,n)=>{let r=n.pipe?`|>`:`&`,i=`${e.hash}${r}${t.hash}`;if(aL[i]!==void 0)return aL[i];if(!n.pipe){let n=`${t.hash}${r}${e.hash}`;if(aL[n]!==void 0){let e=aL[n],t=e instanceof $?e.invert():e;return aL[i]=t,t}}let a=!n.pipe||!e.includesTransform&&!t.includesTransform;if(a&&e.equals(t))return e;let o=a?lL(e,t,n):e.hasKindIn(...SI)?uL(e,t,n):lL(e,t,n);return gI(o)&&(e.equals(o)?o=e:t.equals(o)&&(o=t)),aL[i]=o,o});var lL=(e,t,n)=>{let r=e.precedencee.includesTransform||t.includesTransform?n.invert?dL(t,e,n):dL(e,t,n):lL(e,t,n),dL=(e,t,n)=>e.distribute(e=>fL(e,t,n),r=>{let i=r.filter(gI);if(i.length===0)return $.init(`union`,e.branches,t.branches);if(i.lengthe.rawIn.equals(i[t].rawIn)))return n.$.parseSchema(i);if(i.length===1)return i[0];let a={branches:i};return n.$.parseSchema(a)}),fL=(e,t,n)=>{if(e.hasKind(`morph`)){let r=[...e.morphs];if(e.lastMorphIfNode){let i=cL(e.lastMorphIfNode,t,n);if(i instanceof $)return i;r[r.length-1]=i}else r.push(t);return n.$.node(`morph`,{morphs:r,in:e.inner.in})}if(t.hasKind(`morph`)){let r=cL(e,t.rawIn,n);return r instanceof $?r:n.$.node(`morph`,{morphs:[t],in:r})}return n.$.node(`morph`,{morphs:[t],in:e})},pL=class extends KI{constructor(e,t){super(e,t),Object.defineProperty(this,mI,{value:`constraint`,enumerable:!1})}impliedSiblings;intersect(e){return oL(this,e,this.$)}},mL=class extends pL{traverseApply=(e,t)=>{this.traverseAllows(e,t)||t.errorFromNodeContext(this.errorContext)};compile(e){e.traversalKind===`Allows`?e.return(this.compiledCondition):e.if(this.compiledNegation,()=>e.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`))}get errorContext(){return{code:this.kind,description:this.description,meta:this.meta,...this.inner}}get compiledErrorContext(){return jI(this.errorContext)}};const hL=e=>(t,n)=>{if(HP(t)){if(t.length===0)return;let r=t.map(t=>n.$.node(e,t));return e===`predicate`?r:r.sort((e,t)=>e.hash{let t=e.r.shift();if(!t){let t=e.l.length===0&&e.kind===`structure`?Q.intrinsic.unknown.internal:e.ctx.$.node(e.kind,Object.assign(e.baseInner,vL(e.l)),{prereduced:!0});for(let n of e.roots){if(t instanceof $)return t;t=cL(n,t,e.ctx)}return t}let n=!1;for(let r=0;rObject.entries(e).flatMap(([e,t])=>e in wI?t:[]).sort((e,t)=>e.precedencet.precedence?1:e.kind===`predicate`&&t.kind===`predicate`?0:e.hash{let t={};for(let n of e)if(n.hasOpenIntersection())t[n.kind]=aP(t[n.kind],n);else{if(t[n.kind])return gP(`Unexpected intersection of closed refinements of kind ${n.kind}`);t[n.kind]=n}return t},yL=(...e)=>Z(bL(...e)),bL=(e,t,n)=>{let r=n.hasKind(`morph`)?`a morph`:n.isUnknown()?`unknown`:n.exclude(t).defaultShortDescription;return`${iF(e)} operand must be ${t.description} (was ${r})`},xL=(e,t,n)=>new CL(e,t,n,n,null);var SL=class extends eF{},CL=class extends eF{[mI]=`generic`;paramDefs;bodyDef;$;arg$;baseInstantiation;hkt;description;constructor(e,t,n,r,i){super((...e)=>{let n=bP(this.names,(t,n)=>{let r=this.arg$.parse(e[t]);return r.extends(this.constraints[t])||Z(wL(n,this.constraints[t].expression,r.expression)),[n,r]});if(this.defIsLazy()){let e=this.bodyDef(n);return this.$.parse(e)}return this.$.parse(t,{args:n})}),this.paramDefs=e,this.bodyDef=t,this.$=n,this.arg$=r,this.hkt=i,this.description=i?new i().description??`a generic type for ${i.constructor.name}`:`a generic type`,this.baseInstantiation=this(...this.constraints)}defIsLazy(){return this.bodyDef instanceof SL}cacheGetter(e,t){return Object.defineProperty(this,e,{value:t}),t}get json(){return this.cacheGetter(`json`,{params:this.params.map(e=>e[1].isUnknown()?e[0]:[e[0],e[1].json]),body:VF(this.bodyDef)})}get params(){return this.cacheGetter(`params`,this.paramDefs.map(e=>typeof e==`string`?[e,Q.intrinsic.unknown]:[e[0],this.$.parse(e[1])]))}get names(){return this.cacheGetter(`names`,this.params.map(e=>e[0]))}get constraints(){return this.cacheGetter(`constraints`,this.params.map(e=>e[1]))}get internal(){return this}get referencesById(){return this.baseInstantiation.internal.referencesById}get references(){return this.baseInstantiation.internal.references}};const wL=(e,t,n)=>`${e} must be assignable to ${t} (was ${n})`,TL={implementation:MI({kind:`predicate`,hasAssociatedError:!0,collapsibleKey:`predicate`,keys:{predicate:{}},normalize:e=>typeof e==`function`?{predicate:e}:e,defaults:{description:e=>`valid according to ${e.predicate.name||`an anonymous predicate`}`},intersectionIsOpen:!0,intersections:{predicate:()=>null}}),Node:class extends pL{serializedPredicate=oI(this.predicate);compiledCondition=`${this.serializedPredicate}(data, ctx)`;compiledNegation=`!${this.compiledCondition}`;impliedBasis=null;expression=this.serializedPredicate;traverseAllows=this.predicate;errorContext={code:`predicate`,description:this.description,meta:this.meta};compiledErrorContext=jI(this.errorContext);traverseApply=(e,t)=>{let n=t.currentErrorCount;!this.predicate(e,t.external)&&t.currentErrorCount===n&&t.errorFromNodeContext(this.errorContext)};compile(e){if(e.traversalKind===`Allows`){e.return(this.compiledCondition);return}e.initializeErrorCount(),e.if(`${this.compiledNegation} && ctx.currentErrorCount === errorCount`,()=>e.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`))}reduceJsonSchema(e,t){return t.fallback.predicate({code:`predicate`,base:e,predicate:this.predicate})}}},EL={implementation:MI({kind:`divisor`,collapsibleKey:`rule`,keys:{rule:{parse:e=>Number.isInteger(e)?e:Z(DL(e))}},normalize:e=>typeof e==`number`?{rule:e}:e,hasAssociatedError:!0,defaults:{description:e=>e.rule===1?`an integer`:e.rule===2?`even`:`a multiple of ${e.rule}`},intersections:{divisor:(e,t,n)=>n.$.node(`divisor`,{rule:Math.abs(e.rule*t.rule/OL(e.rule,t.rule))})},obviatesBasisDescription:!0}),Node:class extends mL{traverseAllows=e=>e%this.rule===0;compiledCondition=`data % ${this.rule} === 0`;compiledNegation=`data % ${this.rule} !== 0`;impliedBasis=Q.intrinsic.number.internal;expression=`% ${this.rule}`;reduceJsonSchema(e){return e.type=`integer`,this.rule===1||(e.multipleOf=this.rule),e}}},DL=e=>`divisor must be an integer (was ${e})`;var OL=(e,t)=>{let n,r=e,i=t;for(;i!==0;)n=i,i=r%i,r=n;return r},kL=class extends mL{boundOperandKind=RL[this.kind];compiledActual=this.boundOperandKind===`value`?`data`:this.boundOperandKind===`length`?`data.length`:`data.valueOf()`;comparator=zL(this.kind,this.exclusive);numericLimit=this.rule.valueOf();expression=`${this.comparator} ${this.rule}`;compiledCondition=`${this.compiledActual} ${this.comparator} ${this.numericLimit}`;compiledNegation=`${this.compiledActual} ${AL[this.comparator]} ${this.numericLimit}`;stringLimit=this.boundOperandKind===`date`?BL(this.numericLimit):`${this.numericLimit}`;limitKind=this.comparator[0]===`<`?`upper`:`lower`;isStricterThan(e){return(this.limitKind===`upper`?this.numericLimite.numericLimit)||this.numericLimit===e.numericLimit&&this.exclusive===!0&&!e.exclusive}overlapsRange(e){return!(this.isStricterThan(e)||this.numericLimit===e.numericLimit&&(this.exclusive||e.exclusive))}overlapIsUnit(e){return this.numericLimit===e.numericLimit&&!this.exclusive&&!e.exclusive}},AL={"<":`>=`,"<=":`>`,">":`<=`,">=":`<`};const jL={min:`max`,minLength:`maxLength`,after:`before`},ML={parse:e=>e||void 0},NL=e=>t=>{if(typeof t==`number`)return{rule:t};let{exclusive:n,...r}=t;return n?{...r,rule:e===`minLength`?r.rule+1:r.rule-1}:r},PL=e=>t=>{if(typeof t==`number`||typeof t==`string`||t instanceof Date)return{rule:t};let{exclusive:n,...r}=t;if(!n)return r;let i=typeof r.rule==`number`?r.rule:typeof r.rule==`string`?new Date(r.rule).valueOf():r.rule.valueOf();return n?{...r,rule:e===`after`?i+1:i-1}:r},FL=e=>typeof e==`string`||typeof e==`number`?new Date(e):e,IL=(e,t)=>`${e} bound must be a positive integer (was ${t})`,LL=e=>t=>((!Number.isInteger(t)||t<0)&&Z(IL(e,t)),t);var RL={min:`value`,max:`value`,minLength:`length`,maxLength:`length`,after:`date`,before:`date`};const zL=(e,t)=>`${SP(e,jL)?`>`:`<`}${t?``:`=`}`,BL=e=>typeof e==`string`?e:new Date(e).toLocaleString(),VL=e=>`Bounded expression ${e} must be exactly one of number, string, Array, or Date`,HL={implementation:MI({kind:`after`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{parse:FL,serialize:e=>e.toISOString()}},normalize:PL(`after`),defaults:{description:e=>`${e.collapsibleLimitString} or later`,actual:KF},intersections:{after:(e,t)=>e.isStricterThan(t)?e:t}}),Node:class extends kL{impliedBasis=Q.intrinsic.Date.internal;collapsibleLimitString=KF(this.rule);traverseAllows=e=>e>=this.rule;reduceJsonSchema(e,t){return t.fallback.date({code:`date`,base:e,after:this.rule})}}},UL={implementation:MI({kind:`before`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{parse:FL,serialize:e=>e.toISOString()}},normalize:PL(`before`),defaults:{description:e=>`${e.collapsibleLimitString} or earlier`,actual:KF},intersections:{before:(e,t)=>e.isStricterThan(t)?e:t,after:(e,t,n)=>e.overlapsRange(t)?e.overlapIsUnit(t)?n.$.node(`unit`,{unit:e.rule}):null:$.init(`range`,e,t)}}),Node:class extends kL{collapsibleLimitString=KF(this.rule);traverseAllows=e=>e<=this.rule;impliedBasis=Q.intrinsic.Date.internal;reduceJsonSchema(e,t){return t.fallback.date({code:`date`,base:e,before:this.rule})}}},WL={implementation:MI({kind:`exactLength`,collapsibleKey:`rule`,keys:{rule:{parse:LL(`exactLength`)}},normalize:e=>typeof e==`number`?{rule:e}:e,hasAssociatedError:!0,defaults:{description:e=>`exactly length ${e.rule}`,actual:e=>`${e.length}`},intersections:{exactLength:(e,t,n)=>$.init(`unit`,n.$.node(`unit`,{unit:e.rule}),n.$.node(`unit`,{unit:t.rule}),{path:[`length`]}),minLength:(e,t)=>e.rule>=t.rule?e:$.init(`range`,e,t),maxLength:(e,t)=>e.rule<=t.rule?e:$.init(`range`,e,t)}}),Node:class extends mL{traverseAllows=e=>e.length===this.rule;compiledCondition=`data.length === ${this.rule}`;compiledNegation=`data.length !== ${this.rule}`;impliedBasis=Q.intrinsic.lengthBoundable.internal;expression=`== ${this.rule}`;reduceJsonSchema(e){switch(e.type){case`string`:return e.minLength=this.rule,e.maxLength=this.rule,e;case`array`:return e.minItems=this.rule,e.maxItems=this.rule,e;default:return NI.throwInternalOperandError(`exactLength`,e)}}}},GL={implementation:MI({kind:`max`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{},exclusive:ML},normalize:e=>typeof e==`number`?{rule:e}:e,defaults:{description:e=>e.rule===0?e.exclusive?`negative`:`non-positive`:`${e.exclusive?`less than`:`at most`} ${e.rule}`},intersections:{max:(e,t)=>e.isStricterThan(t)?e:t,min:(e,t,n)=>e.overlapsRange(t)?e.overlapIsUnit(t)?n.$.node(`unit`,{unit:e.rule}):null:$.init(`range`,e,t)},obviatesBasisDescription:!0}),Node:class extends kL{impliedBasis=Q.intrinsic.number.internal;traverseAllows=this.exclusive?e=>ee<=this.rule;reduceJsonSchema(e){return this.exclusive?e.exclusiveMaximum=this.rule:e.maximum=this.rule,e}}},KL={implementation:MI({kind:`maxLength`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{parse:LL(`maxLength`)}},reduce:(e,t)=>e.rule===0?t.node(`exactLength`,e):void 0,normalize:NL(`maxLength`),defaults:{description:e=>`at most length ${e.rule}`,actual:e=>`${e.length}`},intersections:{maxLength:(e,t)=>e.isStricterThan(t)?e:t,minLength:(e,t,n)=>e.overlapsRange(t)?e.overlapIsUnit(t)?n.$.node(`exactLength`,{rule:e.rule}):null:$.init(`range`,e,t)}}),Node:class extends kL{impliedBasis=Q.intrinsic.lengthBoundable.internal;traverseAllows=e=>e.length<=this.rule;reduceJsonSchema(e){switch(e.type){case`string`:return e.maxLength=this.rule,e;case`array`:return e.maxItems=this.rule,e;default:return NI.throwInternalOperandError(`maxLength`,e)}}}},qL={implementation:MI({kind:`min`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{},exclusive:ML},normalize:e=>typeof e==`number`?{rule:e}:e,defaults:{description:e=>e.rule===0?e.exclusive?`positive`:`non-negative`:`${e.exclusive?`more than`:`at least`} ${e.rule}`},intersections:{min:(e,t)=>e.isStricterThan(t)?e:t},obviatesBasisDescription:!0}),Node:class extends kL{impliedBasis=Q.intrinsic.number.internal;traverseAllows=this.exclusive?e=>e>this.rule:e=>e>=this.rule;reduceJsonSchema(e){return this.exclusive?e.exclusiveMinimum=this.rule:e.minimum=this.rule,e}}},JL={implementation:MI({kind:`minLength`,collapsibleKey:`rule`,hasAssociatedError:!0,keys:{rule:{parse:LL(`minLength`)}},reduce:e=>e.rule===0?Q.intrinsic.unknown:void 0,normalize:NL(`minLength`),defaults:{description:e=>e.rule===1?`non-empty`:`at least length ${e.rule}`,actual:e=>e.length===0?``:`${e.length}`},intersections:{minLength:(e,t)=>e.isStricterThan(t)?e:t}}),Node:class extends kL{impliedBasis=Q.intrinsic.lengthBoundable.internal;traverseAllows=e=>e.length>=this.rule;reduceJsonSchema(e){switch(e.type){case`string`:return e.minLength=this.rule,e;case`array`:return e.minItems=this.rule,e;default:return NI.throwInternalOperandError(`minLength`,e)}}}},YL={min:qL.implementation,max:GL.implementation,minLength:JL.implementation,maxLength:KL.implementation,exactLength:WL.implementation,after:HL.implementation,before:UL.implementation},XL={min:qL.Node,max:GL.Node,minLength:JL.Node,maxLength:KL.Node,exactLength:WL.Node,after:HL.Node,before:UL.Node},ZL={implementation:MI({kind:`pattern`,collapsibleKey:`rule`,keys:{rule:{},flags:{}},normalize:e=>typeof e==`string`?{rule:e}:e instanceof RegExp?e.flags?{rule:e.source,flags:e.flags}:{rule:e.source}:e,obviatesBasisDescription:!0,obviatesBasisExpression:!0,hasAssociatedError:!0,intersectionIsOpen:!0,defaults:{description:e=>`matched by ${e.rule}`},intersections:{pattern:()=>null}}),Node:class extends mL{instance=new RegExp(this.rule,this.flags);expression=`${this.instance}`;traverseAllows=this.instance.test.bind(this.instance);compiledCondition=`${this.expression}.test(data)`;compiledNegation=`!${this.compiledCondition}`;impliedBasis=Q.intrinsic.string.internal;reduceJsonSchema(e,t){return e.pattern?t.fallback.patternIntersection({code:`patternIntersection`,base:e,pattern:this.rule}):(e.pattern=this.rule,e)}}},QL=(e,t)=>{let n=$L(e);return t&&!t.includes(n)?Z(`Root of kind ${n} should be one of ${t}`):n};var $L=e=>{if(hI(e,`root`))return e.kind;if(typeof e==`string`)return e[0]===`$`?`alias`:e in pP?`domain`:`proto`;if(typeof e==`function`)return`proto`;if(typeof e!=`object`||!e)return Z(eR(e));if(`morphs`in e)return`morph`;if(`branches`in e||HP(e))return`union`;if(`unit`in e)return`unit`;if(`reference`in e)return`alias`;let t=Object.keys(e);return t.length===0||t.some(e=>e in wI)?`intersection`:`proto`in e?`proto`:`domain`in e?`domain`:Z(eR(e))};const eR=e=>`${HF(e)} is not a valid type schema`;var tR={},nR=e=>HP(e)?e.map(e=>e.collapsibleJson):e.collapsibleJson;const rR={};Q.nodesByRegisteredId=rR;const iR=e=>(tR[e]??=0,`${e}${++tR[e]}`),aR=e=>{let t=Wz[e.kind],n=t.applyConfig?.(e.def,e.$.resolvedConfig)??e.def,r={},{meta:i,...a}=n,o=i===void 0?{}:typeof i==`string`?{description:i}:i,s=xP(a).sort(([e],[t])=>DI(e)?DI(t)?OI(e)-OI(t):1:DI(t)||e{if(e.startsWith(`meta.`)){let n=e.slice(5);return o[n]=t,!1}return!0});for(let n of s){let i=n[0],a=t.keys[i];if(!a)return Z(`Key ${i} is not valid on ${e.kind} schema`);let o=a.parse?a.parse(n[1],e):n[1];o!==NP&&(o!==void 0||a.preserveUndefined)&&(r[i]=o)}if(t.reduce&&!e.prereduced){let n=t.reduce(r,e.$);if(n)return n instanceof $?n.throw():cR(n,o)}return oR({id:e.id,kind:e.kind,inner:r,meta:o,$:e.$})},oR=({id:e,kind:t,inner:n,meta:r,$:i,ignoreCache:a})=>{let o=Wz[t],s=xP(n),c=[],l={};for(let[e,t]of s){let n=o.keys[e],r=n.serialize??(n.child?nR:AI);if(l[e]=r(t),n.child===!0){let e=t;HP(e)?c.push(...e):c.push(e)}else typeof n.child==`function`&&c.push(...n.child(t))}o.finalizeInnerJson&&(l=o.finalizeInnerJson(l));let u={...l},d={};kP(r)||(d=bP(r,(e,t)=>[e,e===`examples`?t:AI(t)]),u.meta=lR(d,`description`,!0)),l=lR(l,o.collapsibleKey,!1);let f=JSON.stringify({kind:t,...l});u=lR(u,o.collapsibleKey,!1);let p=lR(u,o.collapsibleKey,!0),m=JSON.stringify({kind:t,...u});if(i.nodesByHash[m]&&!a)return i.nodesByHash[m];let h={id:e,kind:t,impl:o,inner:n,innerEntries:s,innerJson:l,innerHash:f,meta:r,metaJson:d,json:u,hash:m,collapsibleJson:p,children:c};if(t!==`intersection`)for(let e in n)e!==`in`&&e!==`out`&&(h[e]=n[e]);let g=new Gz[t](h,i);return i.nodesByHash[m]=g},sR=(e,t)=>e.id===t?e:(gI(rR[t])&&gP(`Unexpected attempt to overwrite node id ${t}`),oR({id:t,kind:e.kind,inner:e.inner,meta:e.meta,$:e.$,ignoreCache:!0})),cR=(e,t,n)=>(n&&gI(rR[n])&&gP(`Unexpected attempt to overwrite node id ${n}`),oR({id:n??iR(t.alias??e.kind),kind:e.kind,inner:e.inner,meta:t,$:e.$}));var lR=(e,t,n)=>{let r=Object.keys(e);if(r.length===1&&r[0]===t){let r=e[t];if(n||dP(r,`object`)&&(Object.keys(r).length===1||Array.isArray(r)))return r}return e};const uR=(e,t,n)=>{if(e.key!==t.key)return null;let r=e.key,i=cL(e.value,t.value,n),a=e.required||t.required?`required`:`optional`;if(i instanceof $)if(a===`optional`)i=Q.intrinsic.never.internal;else return i.withPrefixKey(e.key,e.required&&t.required?`required`:`optional`);if(a===`required`)return n.$.node(`required`,{key:r,value:i});let o=e.hasDefault()?t.hasDefault()?e.default===t.default?e.default:Z(fR(e.default,t.default)):e.default:t.hasDefault()?t.default:NP;return n.$.node(`optional`,{key:r,value:i,default:o})};var dR=class extends pL{required=this.kind===`required`;optional=this.kind===`optional`;impliedBasis=Q.intrinsic.object.internal;serializedKey=cI(this.key);compiledKey=typeof this.key==`string`?this.key:this.serializedKey;flatRefs=aP(this.value.flatRefs.map(e=>QI([this.key,...e.path],e.node)),QI([this.key],this.value));_transform(e,t){t.path.push(this.key);let n=super._transform(e,t);return t.path.pop(),n}hasDefault(){return`default`in this.inner}traverseAllows=(e,t)=>this.key in e?GI(this.key,()=>this.value.traverseAllows(e[this.key],t),t):this.optional;traverseApply=(e,t)=>{this.key in e?GI(this.key,()=>this.value.traverseApply(e[this.key],t),t):this.hasKind(`required`)&&t.errorFromNodeContext(this.errorContext)};compile(e){e.if(`${this.serializedKey} in data`,()=>e.traverseKey(this.serializedKey,`data${e.prop(this.key)}`,this.value)),this.hasKind(`required`)&&e.else(()=>e.traversalKind===`Apply`?e.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`):e.return(!1)),e.traversalKind===`Allows`&&e.return(!0)}};const fR=(e,t)=>`Invalid intersection of default values ${HF(e)} & ${HF(t)}`,pR={implementation:MI({kind:`optional`,hasAssociatedError:!1,intersectionIsOpen:!0,keys:{key:{},value:{child:!0,parse:(e,t)=>t.$.parseSchema(e)},default:{preserveUndefined:!0}},normalize:e=>e,reduce:(e,t)=>{if(t.resolvedConfig.exactOptionalPropertyTypes===!1&&!e.value.allows(void 0))return t.node(`optional`,{...e,value:e.value.or(xB.undefined)},{prereduced:!0})},defaults:{description:e=>`${e.compiledKey}?: ${e.value.description}`},intersections:{optional:uR}}),Node:class extends dR{constructor(...e){super(...e),`default`in this.inner&&_R(this.value,this.inner.default,this.key)}get rawIn(){let e=super.rawIn;return this.hasDefault()?this.$.node(`optional`,OP(e.inner,{default:!0}),{prereduced:!0}):e}get outProp(){if(!this.hasDefault())return this;let{default:e,...t}=this.inner;return this.cacheGetter(`outProp`,this.$.node(`required`,t,{prereduced:!0}))}expression=this.hasDefault()?`${this.compiledKey}: ${this.value.expression} = ${HF(this.inner.default)}`:`${this.compiledKey}?: ${this.value.expression}`;defaultValueMorph=hR(this);defaultValueMorphRef=this.defaultValueMorph&&oI(this.defaultValueMorph)}};var mR={},hR=e=>{if(!e.hasDefault())return;let t=`{${e.compiledKey}: ${e.value.id} = ${AI(e.default)}}`;return mR[t]??=gR(e.key,e.value,e.default)};const gR=(e,t,n)=>{if(typeof n==`function`)return t.includesTransform?(r,i)=>(GI(e,()=>t(r[e]=n(),i),i),r):t=>(t[e]=n(),t);let r=t.includesTransform?t.assert(n):n;return dP(r,`object`)?(r,i)=>(GI(e,()=>t(r[e]=n,i),i),r):t=>(t[e]=r,t)},_R=(e,t,n)=>{let r=QP(t);dP(t,`object`)&&!r&&Z(vR(n));let i=e.in(r?t():t);return i instanceof VI&&(n===null&&Z(`Default ${i.summary}`),Z(`Default for ${i.transform(e=>e.transform(e=>({...e,prefixPath:[n]}))).summary}`)),t},vR=e=>`Non-primitive default ${e===null?``:typeof e==`number`?`for value at [${e}] `:`for ${cI(e)} `}must be specified as a function like () => ({my: 'object'})`;var yR=class extends KI{constructor(e,t){super(e,t),Object.defineProperty(this,mI,{value:`root`,enumerable:!1})}get rawIn(){return super.rawIn}get rawOut(){return super.rawOut}get internal(){return this}get"~standard"(){return{vendor:`arktype`,version:1,validate:e=>{let t=this(e);return t instanceof VI?t:{value:t}},jsonSchema:{input:e=>this.rawIn.toJsonSchema({target:CR(e.target),...e.libraryOptions}),output:e=>this.rawOut.toJsonSchema({target:CR(e.target),...e.libraryOptions})}}}as(){return this}brand(e){return e===``?Z(bR):this}readonly(){return this}branches=this.hasKind(`union`)?this.inner.branches:[this];distribute(e,t){let n=this.branches.map(e);return t?.(n)??n}get shortDescription(){return this.meta.description??this.defaultShortDescription}toJsonSchema(e={}){let t=II(this.$.resolvedConfig.toJsonSchema,e);t.useRefs||=this.isCyclic;let n=typeof t.dialect==`string`?{$schema:t.dialect}:{};if(Object.assign(n,this.toJsonSchemaRecurse(t)),t.useRefs){let e=bP(this.references,(e,n)=>n.isRoot()&&!n.alwaysExpandJsonSchema?[n.id,n.toResolvedJsonSchema(t)]:[]);t.target===`draft-07`?Object.assign(n,{definitions:e}):n.$defs=e}return n}toJsonSchemaRecurse(e){return e.useRefs&&!this.alwaysExpandJsonSchema?{$ref:`#/${e.target===`draft-07`?`definitions`:`$defs`}/${this.id}`}:this.toResolvedJsonSchema(e)}get alwaysExpandJsonSchema(){return this.isBasis()||this.kind===`alias`||this.hasKind(`union`)&&this.isBoolean}toResolvedJsonSchema(e){let t=this.innerToJsonSchema(e);return Object.assign(t,this.metaJson)}intersect(e){let t=this.$.parseDefinition(e),n=this.rawIntersect(t);return n instanceof $?n:this.$.finalize(n)}rawIntersect(e){return oL(this,e,this.$)}toNeverIfDisjoint(){return this}and(e){let t=this.intersect(e);return t instanceof $?t.throw():t}rawAnd(e){let t=this.rawIntersect(e);return t instanceof $?t.throw():t}or(e){let t=this.$.parseDefinition(e);return this.$.finalize(this.rawOr(t))}rawOr(e){let t=[...this.branches,...e.branches];return this.$.node(`union`,t)}map(e){return this.$.schema(this.applyStructuralOperation(`map`,[e]))}pick(...e){return this.$.schema(this.applyStructuralOperation(`pick`,e))}omit(...e){return this.$.schema(this.applyStructuralOperation(`omit`,e))}required(){return this.$.schema(this.applyStructuralOperation(`required`,[]))}partial(){return this.$.schema(this.applyStructuralOperation(`partial`,[]))}_keyof;keyof(){if(this._keyof)return this._keyof;let e=this.applyStructuralOperation(`keyof`,[]).reduce((e,t)=>e.intersect(t).toNeverIfDisjoint(),Q.intrinsic.unknown.internal);return e.branches.length===0&&Z(iL(`keyof ${this.expression}`)),this._keyof=this.$.finalize(e)}get props(){return this.branches.length===1?[...this.applyStructuralOperation(`props`,[])[0]]:Z(DR(this.expression))}merge(e){let t=this.$.parseDefinition(e);return this.$.schema(t.distribute(e=>this.applyStructuralOperation(`merge`,[ER(e)??Z(OR(`merge`,e.expression))])))}applyStructuralOperation(e,t){return this.distribute(n=>{if(n.equals(Q.intrinsic.object)&&e!==`merge`)return n;let r=ER(n);if(r||Z(OR(e,n.expression)),e===`keyof`)return r.keyof();if(e===`get`)return r.get(...t);if(e===`props`)return r.props;let i=e===`required`?`require`:e===`partial`?`optionalize`:e;return this.$.node(`intersection`,{domain:`object`,structure:r[i](...t)})})}get(...e){return e[0]===void 0?this:this.$.schema(this.applyStructuralOperation(`get`,e))}extract(e){let t=this.$.parseDefinition(e);return this.$.schema(this.branches.filter(e=>e.extends(t)))}exclude(e){let t=this.$.parseDefinition(e);return this.$.schema(this.branches.filter(e=>!e.extends(t)))}array(){return this.$.schema(this.isUnknown()?{proto:Array}:{proto:Array,sequence:this},{prereduced:!0})}overlaps(e){return!(this.intersect(e)instanceof $)}extends(e){if(this.isNever())return!0;let t=this.intersect(e);return!(t instanceof $)&&this.equals(t)}ifExtends(e){return this.extends(e)?this:void 0}subsumes(e){return this.$.parseDefinition(e).extends(this)}configure(e,t=`shallow`){return this.configureReferences(e,t)}describe(e,t=`shallow`){return this.configure({description:e},t)}optional(){return[this,`?`]}default(e){return _R(this,e,null),[this,`=`,e]}from(e){return this.assert(e)}_pipe(...e){let t=e.reduce((e,t)=>e.rawPipeOnce(t),this);return this.$.finalize(t)}tryPipe(...e){let t=e.reduce((e,t)=>e.rawPipeOnce(hI(t,`root`)?t:((e,n)=>{try{return t(e,n)}catch(e){return n.error({code:`predicate`,predicate:t,actual:`aborted due to error:\n ${e}\n`})}})),this);return this.$.finalize(t)}pipe=Object.assign(this._pipe.bind(this),{try:this.tryPipe.bind(this)});to(e){return this.$.finalize(this.toNode(this.$.parseDefinition(e)))}toNode(e){let t=sL(this,e,this.$);return t instanceof $?t.throw():t}rawPipeOnce(e){return hI(e,`root`)?this.toNode(e):this.distribute(t=>t.hasKind(`morph`)?this.$.node(`morph`,{in:t.inner.in,morphs:[...t.morphs,e]}):this.$.node(`morph`,{in:t,morphs:[e]}),this.$.parseSchema)}narrow(e){return this.constrainOut(`predicate`,e)}constrain(e,t){return this._constrain(`root`,e,t)}constrainIn(e,t){return this._constrain(`in`,e,t)}constrainOut(e,t){return this._constrain(`out`,e,t)}_constrain(e,t,n){let r=this.$.node(t,n);if(r.isRoot())return r.isUnknown()?this:gP(`Unexpected constraint node ${r}`);let i=e===`root`?this:e===`in`?this.rawIn:this.rawOut;if(i.hasKind(`morph`)||r.impliedBasis&&!i.extends(r.impliedBasis))return yL(t,r.impliedBasis,this);let a=this.$.node(`intersection`,{[r.kind]:r}),o=e===`out`?sL(this,a,this.$):oL(this,a,this.$);return o instanceof $&&o.throw(),this.$.finalize(o)}onUndeclaredKey(e){let t=typeof e==`string`?e:e.rule,n=typeof e==`string`?!1:e.deep;return this.$.finalize(this.transform((e,n)=>e===`structure`?t===`ignore`?OP(n,{undeclared:1}):{...n,undeclared:t}:n,n?void 0:{shouldTransform:e=>!rP(vI,e.kind)}))}hasEqualMorphs(e){return!this.includesTransform&&!e.includesTransform?!0:!(!uP(this.shallowMorphs,e.shallowMorphs)||!uP(this.flatMorphs,e.flatMorphs,{isEqual:(e,t)=>e.propString===t.propString&&(e.node.hasKind(`morph`)&&t.node.hasKind(`morph`)?e.node.hasEqualMorphs(t.node):e.node.hasKind(`intersection`)&&t.node.hasKind(`intersection`)?e.node.structure?.structuralMorphRef===t.node.structure?.structuralMorphRef:!1)}))}onDeepUndeclaredKey(e){return this.onUndeclaredKey({rule:e,deep:!0})}filter(e){return this.constrainIn(`predicate`,e)}divisibleBy(e){return this.constrain(`divisor`,e)}matching(e){return this.constrain(`pattern`,e)}atLeast(e){return this.constrain(`min`,e)}atMost(e){return this.constrain(`max`,e)}moreThan(e){return this.constrain(`min`,wR(e))}lessThan(e){return this.constrain(`max`,wR(e))}atLeastLength(e){return this.constrain(`minLength`,e)}atMostLength(e){return this.constrain(`maxLength`,e)}moreThanLength(e){return this.constrain(`minLength`,wR(e))}lessThanLength(e){return this.constrain(`maxLength`,wR(e))}exactlyLength(e){return this.constrain(`exactLength`,e)}atOrAfter(e){return this.constrain(`after`,e)}atOrBefore(e){return this.constrain(`before`,e)}laterThan(e){return this.constrain(`after`,wR(e))}earlierThan(e){return this.constrain(`before`,wR(e))}};const bR=`Expected a non-empty brand name after #`;var xR=[`draft-2020-12`,`draft-07`];const SR=e=>`JSONSchema target '${e}' is not supported (must be ${xR.map(e=>`"${e}"`).join(` or `)})`;var CR=e=>(rP(xR,e)||Z(SR(e)),e);const wR=e=>typeof e==`object`&&!(e instanceof Date)?{...e,exclusive:!0}:{rule:e,exclusive:!0},TR=(e,t)=>hI(t,`root`)?hI(e,`root`)?e.extends(t):t.allows(e):hI(e,`root`)?e.hasUnit(t):t===e;var ER=e=>e.hasKind(`morph`)?null:e.hasKind(`intersection`)?e.inner.structure??(e.basis?.domain===`object`?e.$.bindReference(Q.intrinsic.emptyStructure):null):e.isBasis()&&e.domain===`object`?e.$.bindReference(Q.intrinsic.emptyStructure):null;const DR=e=>`Props cannot be extracted from a union. Use .distribute to extract props from each branch instead. Received: +${e}`,OR=(e,t)=>`${e} operand must be an object (was ${t})`,kR=(e,t)=>bP(kI(e),(e,n)=>[n,t]),AR=e=>typeof e==`string`?{reference:e}:e;var jR=e=>e instanceof $?Q.intrinsic.never.internal:e,MR=MI({kind:`alias`,hasAssociatedError:!1,collapsibleKey:`reference`,keys:{reference:{serialize:e=>e.startsWith(`$`)?e:`$ark.${e}`},resolve:{}},normalize:AR,defaults:{description:e=>e.reference},intersections:{alias:(e,t,n)=>n.$.lazilyResolve(()=>jR(cL(e.resolution,t.resolution,n)),`${e.reference}${n.pipe?`=>`:`&`}${t.reference}`),...kR(`alias`,(e,t,n)=>t.isUnknown()?e:t.isNever()?t:t.isBasis()&&!t.overlaps(Q.intrinsic.object)?$.init(`assignability`,Q.intrinsic.object,t):n.$.lazilyResolve(()=>jR(cL(e.resolution,t,n)),`${e.reference}${n.pipe?`=>`:`&`}${t.id}`))}}),NR=class extends yR{expression=this.reference;structure=void 0;get resolution(){let e=this._resolve();return rR[this.id]=e}_resolve(){if(this.resolve)return this.resolve();if(this.reference[0]===`$`)return this.$.resolveRoot(this.reference.slice(1));let e=rR[this.reference],t=[];for(;hI(e,`context`);){if(t.includes(e.id))return Z(PR(e.id,t));t.push(e.id),e=rR[e.id]}return hI(e,`root`)?e:gP(`Unexpected resolution for reference ${this.reference} +Seen: [${t.join(`->`)}] +Resolution: ${HF(e)}`)}get resolutionId(){if(this.reference.includes(`&`)||this.reference.includes(`=>`))return this.resolution.id;if(this.reference[0]!==`$`)return this.reference;let e=this.reference.slice(1),t=this.$.resolutions[e];return typeof t==`string`?t:hI(t,`root`)?t.id:gP(`Unexpected resolution for reference ${this.reference}: ${HF(t)}`)}get defaultShortDescription(){return pP.object}innerToJsonSchema(e){return this.resolution.toJsonSchemaRecurse(e)}traverseAllows=(e,t)=>{let n=t.seen[this.reference];return n?.includes(e)?!0:(t.seen[this.reference]=aP(n,e),this.resolution.traverseAllows(e,t))};traverseApply=(e,t)=>{let n=t.seen[this.reference];n?.includes(e)||(t.seen[this.reference]=aP(n,e),this.resolution.traverseApply(e,t))};compile(e){let t=this.resolutionId;e.if(`ctx.seen.${t} && ctx.seen.${t}.includes(data)`,()=>e.return(!0)),e.if(`!ctx.seen.${t}`,()=>e.line(`ctx.seen.${t} = []`)),e.line(`ctx.seen.${t}.push(data)`),e.return(e.invoke(t))}};const PR=(e,t)=>`Alias '${e}' has a shallow resolution cycle: ${[...t,e].join(`->`)}`,FR={implementation:MR,Node:NR};var IR=class extends yR{traverseApply=(e,t)=>{this.traverseAllows(e,t)||t.errorFromNodeContext(this.errorContext)};get errorContext(){return{code:this.kind,description:this.description,meta:this.meta,...this.inner}}get compiledErrorContext(){return jI(this.errorContext)}compile(e){e.traversalKind===`Allows`?e.return(this.compiledCondition):e.if(this.compiledNegation,()=>e.line(`ctx.errorFromNodeContext(${this.compiledErrorContext})`))}};const LR={implementation:MI({kind:`domain`,hasAssociatedError:!0,collapsibleKey:`domain`,keys:{domain:{},numberAllowsNaN:{}},normalize:e=>typeof e==`string`?{domain:e}:CP(e,`numberAllowsNaN`)&&e.domain!==`number`?Z(LR.writeBadAllowNanMessage(e.domain)):e,applyConfig:(e,t)=>e.numberAllowsNaN===void 0&&e.domain===`number`&&t.numberAllowsNaN?{...e,numberAllowsNaN:!0}:e,defaults:{description:e=>pP[e.domain],actual:e=>Number.isNaN(e)?`NaN`:pP[fP(e)]},intersections:{domain:(e,t)=>e.domain===`number`&&t.domain===`number`?e.numberAllowsNaN?t:e:$.init(`domain`,e,t)}}),Node:class extends IR{requiresNaNCheck=this.domain===`number`&&!this.numberAllowsNaN;traverseAllows=this.requiresNaNCheck?e=>typeof e==`number`&&!Number.isNaN(e):e=>fP(e)===this.domain;compiledCondition=this.domain===`object`?`((typeof data === "object" && data !== null) || typeof data === "function")`:`typeof data === "${this.domain}"${this.requiresNaNCheck?` && !Number.isNaN(data)`:``}`;compiledNegation=this.domain===`object`?`((typeof data !== "object" || data === null) && typeof data !== "function")`:`typeof data !== "${this.domain}"${this.requiresNaNCheck?` || Number.isNaN(data)`:``}`;expression=this.numberAllowsNaN?`number | NaN`:this.domain;get nestableExpression(){return this.numberAllowsNaN?`(${this.expression})`:this.expression}get defaultShortDescription(){return pP[this.domain]}innerToJsonSchema(e){return this.domain===`bigint`||this.domain===`symbol`?e.fallback.domain({code:`domain`,base:{},domain:this.domain}):{type:this.domain}}},writeBadAllowNanMessage:e=>`numberAllowsNaN may only be specified with domain "number" (was ${e})`},RR={implementation:MI({kind:`intersection`,hasAssociatedError:!0,normalize:e=>{if(gI(e))return e;let{structure:t,...n}=e,r=!!t,i=t??{},a=bP(n,(e,t)=>SP(e,TI)?(r&&Z(`Flattened structure key ${e} cannot be specified alongside a root 'structure' key.`),i[e]=t,[]):[e,t]);return(hI(i,`constraint`)||!kP(i))&&(a.structure=i),a},finalizeInnerJson:({structure:e,...t})=>dP(e,`object`)?{...e,...t}:t,keys:{domain:{child:!0,parse:(e,t)=>t.$.node(`domain`,e)},proto:{child:!0,parse:(e,t)=>t.$.node(`proto`,e)},structure:{child:!0,parse:(e,t)=>t.$.node(`structure`,e),serialize:e=>{if(!e.sequence?.minLength)return e.collapsibleJson;let{sequence:t,...n}=e.collapsibleJson,{minVariadicLength:r,...i}=t,a=i.variadic&&Object.keys(i).length===1?i.variadic:i;return{...n,sequence:a}}},divisor:{child:!0,parse:hL(`divisor`)},max:{child:!0,parse:hL(`max`)},min:{child:!0,parse:hL(`min`)},maxLength:{child:!0,parse:hL(`maxLength`)},minLength:{child:!0,parse:hL(`minLength`)},exactLength:{child:!0,parse:hL(`exactLength`)},before:{child:!0,parse:hL(`before`)},after:{child:!0,parse:hL(`after`)},pattern:{child:!0,parse:hL(`pattern`)},predicate:{child:!0,parse:hL(`predicate`)}},reduce:(e,t)=>BR({},e,{$:t,invert:!1,pipe:!1}),defaults:{description:e=>{if(e.children.length===0)return`unknown`;if(e.structure)return e.structure.description;let t=[];if(e.basis&&!e.prestructurals.some(e=>e.impl.obviatesBasisDescription)&&t.push(e.basis.description),e.prestructurals.length){let n=e.prestructurals.slice().sort((e,t)=>e.kind===`min`&&t.kind===`max`?-1:0).map(e=>e.description);t.push(...n)}return e.inner.predicate&&t.push(...e.inner.predicate.map(e=>e.description)),t.join(` and `)},expected:e=>` ◦ ${e.errors.map(e=>e.expected).join(` + ◦ `)}`,problem:e=>`(${e.actual}) must be...\n${e.expected}`},intersections:{intersection:(e,t,n)=>BR(e.inner,t.inner,n),...kR(`intersection`,(e,t,n)=>{if(e.children.length===0)return t;let{domain:r,proto:i,...a}=e.inner,o=i??r,s=o?cL(o,t,n):t;return s instanceof $?s:e?.basis?.equals(s)?e:e.$.node(`intersection`,{...a,[s.kind]:s},{prereduced:!0})})}}),Node:class extends yR{basis=this.inner.domain??this.inner.proto??null;prestructurals=[];refinements=this.children.filter(e=>e.isRefinement()?(rP(yI,e.kind)&&this.prestructurals.push(e),!0):!1);structure=this.inner.structure;expression=zR(this);get shallowMorphs(){return this.inner.structure?.structuralMorph?[this.inner.structure.structuralMorph]:[]}get defaultShortDescription(){return this.basis?.defaultShortDescription??`present`}innerToJsonSchema(e){return this.children.reduce((t,n)=>n.isBasis()?n.toJsonSchemaRecurse(e):n.reduceJsonSchema(t,e),{})}traverseAllows=(e,t)=>this.children.every(n=>n.traverseAllows(e,t));traverseApply=(e,t)=>{let n=t.currentErrorCount;if(!(this.basis&&(this.basis.traverseApply(e,t),t.currentErrorCount>n))){if(this.prestructurals.length){for(let r=0;rn)return;if(this.prestructurals[this.prestructurals.length-1].traverseApply(e,t),t.currentErrorCount>n)return}if(!(this.structure&&(this.structure.traverseApply(e,t),t.currentErrorCount>n))&&this.inner.predicate){for(let r=0;rn)return;this.inner.predicate[this.inner.predicate.length-1].traverseApply(e,t)}}};compile(e){if(e.traversalKind===`Allows`){for(let t of this.children)e.check(t);e.return(!0);return}if(e.initializeErrorCount(),this.basis&&(e.check(this.basis),this.children.length>1&&e.returnIfFail()),this.prestructurals.length){for(let t=0;t{if(e.structure?.expression)return e.structure.expression;let t=e.basis&&!e.prestructurals.some(e=>e.impl.obviatesBasisExpression)?e.basis.nestableExpression:``,n=e.prestructurals.map(e=>e.expression).join(` & `),r=`${t}${t?` `:``}${n}`;return r===`Array == 0`?`[]`:r||`unknown`},BR=(e,t,n)=>{let r={},i=e.proto??e.domain,a=t.proto??t.domain,o=i?a?cL(i,a,n):i:a;return o instanceof $?o:(o&&(r[o.kind]=o),gL({kind:`intersection`,baseInner:r,l:_L(e),r:_L(t),roots:[],ctx:n}))};const VR={implementation:MI({kind:`morph`,hasAssociatedError:!1,keys:{in:{child:!0,parse:(e,t)=>t.$.parseSchema(e)},morphs:{parse:eP,serialize:e=>e.map(e=>hI(e,`root`)?e.json:oI(e))},declaredIn:{child:!1,serialize:e=>e.json},declaredOut:{child:!1,serialize:e=>e.json}},normalize:e=>e,defaults:{description:e=>`a morph from ${e.rawIn.description} to ${e.rawOut?.description??`unknown`}`},intersections:{morph:(e,t,n)=>{if(!e.hasEqualMorphs(t))return Z(HR(e.expression,t.expression));let r=cL(e.rawIn,t.rawIn,n);if(r instanceof $)return r;let i={morphs:e.morphs};if(e.declaredIn||t.declaredIn){let r=cL(e.rawIn,t.rawIn,n);if(r instanceof $)return r.throw();i.declaredIn=r}if(e.declaredOut||t.declaredOut){let r=cL(e.rawOut,t.rawOut,n);if(r instanceof $)return r.throw();i.declaredOut=r}return r.distribute(e=>n.$.node(`morph`,{...i,in:e}),n.$.parseSchema)},...kR(`morph`,(e,t,n)=>{let r=e.inner.in?cL(e.inner.in,t,n):t;return r instanceof $?r:r.equals(e.inner.in)?e:n.$.node(`morph`,{...e.inner,in:r})})}}),Node:class extends yR{serializedMorphs=this.morphs.map(oI);compiledMorphs=`[${this.serializedMorphs}]`;lastMorph=this.inner.morphs[this.inner.morphs.length-1];lastMorphIfNode=hI(this.lastMorph,`root`)?this.lastMorph:void 0;introspectableIn=this.inner.in;introspectableOut=this.lastMorphIfNode?Object.assign(this.referencesById,this.lastMorphIfNode.referencesById)&&this.lastMorphIfNode.rawOut:void 0;get shallowMorphs(){return Array.isArray(this.inner.in?.shallowMorphs)?[...this.inner.in.shallowMorphs,...this.morphs]:this.morphs}get rawIn(){return this.declaredIn??this.inner.in?.rawIn??Q.intrinsic.unknown.internal}get rawOut(){return this.declaredOut??this.introspectableOut??Q.intrinsic.unknown.internal}declareIn(e){return this.$.node(`morph`,{...this.inner,declaredIn:e})}declareOut(e){return this.$.node(`morph`,{...this.inner,declaredOut:e})}expression=`(In: ${this.rawIn.expression}) => ${this.lastMorphIfNode?`To`:`Out`}<${this.rawOut.expression}>`;get defaultShortDescription(){return this.rawIn.meta.description??this.rawIn.defaultShortDescription}innerToJsonSchema(e){return e.fallback.morph({code:`morph`,base:this.rawIn.toJsonSchemaRecurse(e),out:this.introspectableOut?.toJsonSchemaRecurse(e)??null})}compile(e){if(e.traversalKind===`Allows`){if(!this.introspectableIn)return;e.return(e.invoke(this.introspectableIn));return}this.introspectableIn&&e.line(e.invoke(this.introspectableIn)),e.line(`ctx.queueMorphs(${this.compiledMorphs})`)}traverseAllows=(e,t)=>!this.introspectableIn||this.introspectableIn.traverseAllows(e,t);traverseApply=(e,t)=>{this.introspectableIn&&this.introspectableIn.traverseApply(e,t),t.queueMorphs(this.morphs)};hasEqualMorphs(e){return uP(this.morphs,e.morphs,{isEqual:(e,t)=>e===t||hI(e,`root`)&&hI(t,`root`)&&e.equals(t)})}}},HR=(e,t)=>`The intersection of distinct morphs at a single path is indeterminate: +Left: ${e} +Right: ${t}`,UR={implementation:MI({kind:`proto`,hasAssociatedError:!0,collapsibleKey:`proto`,keys:{proto:{serialize:e=>qP(e)??AI(e)},dateAllowsInvalid:{}},normalize:e=>{let t=typeof e==`string`?{proto:zP[e]}:typeof e==`function`?gI(e)?e:{proto:e}:typeof e.proto==`string`?{...e,proto:zP[e.proto]}:e;return typeof t.proto!=`function`&&Z(UR.writeInvalidSchemaMessage(t.proto)),CP(t,`dateAllowsInvalid`)&&t.proto!==Date&&Z(UR.writeBadInvalidDateMessage(t.proto)),t},applyConfig:(e,t)=>e.dateAllowsInvalid===void 0&&e.proto===Date&&t.dateAllowsInvalid?{...e,dateAllowsInvalid:!0}:e,defaults:{description:e=>e.builtinName?KP[e.builtinName]:`an instance of ${e.proto.name}`,actual:e=>e instanceof Date&&e.toString()===`Invalid Date`?`an invalid Date`:VP(e)},intersections:{proto:(e,t)=>e.proto===Date&&t.proto===Date?e.dateAllowsInvalid?t:e:JP(e.proto,t.proto)?e:JP(t.proto,e.proto)?t:$.init(`proto`,e,t),domain:(e,t)=>t.domain===`object`?e:$.init(`domain`,Q.intrinsic.object.internal,t)}}),Node:class extends IR{builtinName=qP(this.proto);serializedConstructor=this.json.proto;requiresInvalidDateCheck=this.proto===Date&&!this.dateAllowsInvalid;traverseAllows=this.requiresInvalidDateCheck?e=>e instanceof Date&&e.toString()!==`Invalid Date`:e=>e instanceof this.proto;compiledCondition=`data instanceof ${this.serializedConstructor}${this.requiresInvalidDateCheck?` && data.toString() !== "Invalid Date"`:``}`;compiledNegation=`!(${this.compiledCondition})`;innerToJsonSchema(e){switch(this.builtinName){case`Array`:return{type:`array`};case`Date`:return e.fallback.date?.({code:`date`,base:{}})??e.fallback.proto({code:`proto`,base:{},proto:this.proto});default:return e.fallback.proto({code:`proto`,base:{},proto:this.proto})}}expression=this.dateAllowsInvalid?`Date | InvalidDate`:this.proto.name;get nestableExpression(){return this.dateAllowsInvalid?`(${this.expression})`:this.expression}domain=`object`;get defaultShortDescription(){return this.description}},writeBadInvalidDateMessage:e=>`dateAllowsInvalid may only be specified with constructor Date (was ${e.name})`,writeInvalidSchemaMessage:e=>`instanceOf operand must be a function (was ${fP(e)})`};var WR=MI({kind:`union`,hasAssociatedError:!0,collapsibleKey:`branches`,keys:{ordered:{},branches:{child:!0,parse:(e,t)=>{let n=[];for(let r of e){let e=hI(r,`root`)?r.branches:t.$.parseSchema(r).branches;for(let r of e)if(r.hasKind(`morph`)){let e=n.findIndex(e=>e.hasKind(`morph`)&&e.hasEqualMorphs(r));if(e===-1)n.push(r);else{let i=n[e];n[e]=t.$.node(`morph`,{...i.inner,in:i.rawIn.rawOr(r.rawIn)})}}else n.push(r)}return t.def.ordered||n.sort((e,t)=>e.hashHP(e)?{branches:e}:e,reduce:(e,t)=>{let n=az(e);if(n.length===1)return n[0];if(n.length!==e.branches.length)return t.node(`union`,{...e,branches:n},{prereduced:!0})},defaults:{description:e=>e.distribute(e=>e.description,rz),expected:e=>{let t=lP(e.errors,`propString`);return rz(Object.entries(t).map(([e,t])=>{let n=[];for(let e of t)cP(n,e.expected);let r=rz(n),i=t.every(e=>e.actual===t[0].actual)?t[0].actual:HF(t[0].data);return`${e&&`${e} `}must be ${r}${i&&` (was ${i})`}`}))},problem:e=>e.expected,message:e=>e.problem[0]===`[`?`value at ${e.problem}`:e.problem},intersections:{union:(e,t,n)=>{if(e.isNever!==t.isNever)return $.init(`presence`,e,t);let r;return e.ordered?(t.ordered&&Z(lz(e.expression,t.expression)),r=iz(t.branches,e.branches,n),r instanceof $&&r.invert()):r=iz(e.branches,t.branches,n),r instanceof $?r:n.$.parseSchema(e.ordered||t.ordered?{branches:r,ordered:!0}:{branches:r})},...kR(`union`,(e,t,n)=>{let r=iz(e.branches,[t],n);return r instanceof $?r:r.length===1?r[0]:n.$.parseSchema(e.ordered?{branches:r,ordered:!0}:{branches:r})})}}),GR=class extends yR{isBoolean=this.branches.length===2&&this.branches[0].hasUnit(!1)&&this.branches[1].hasUnit(!0);get branchGroups(){let e=[],t=-1;for(let n of this.branches){if(n.hasKind(`unit`)&&n.domain===`boolean`){t===-1?(t=e.length,e.push(n)):e[t]=Q.intrinsic.boolean;continue}e.push(n)}return e}unitBranches=this.branches.filter(e=>e.rawIn.hasKind(`unit`));discriminant=this.discriminate();discriminantJson=this.discriminant?ez(this.discriminant):null;expression=this.distribute(e=>e.nestableExpression,nz);createBranchedOptimisticRootApply(){return(e,t)=>{let n=this.traverseOptimistic(e);if(n!==NP)return n;let r=new WI(e,this.$.resolvedConfig);return this.traverseApply(e,r),r.finalize(t)}}get shallowMorphs(){return this.branches.reduce((e,t)=>cP(e,t.shallowMorphs),[])}get defaultShortDescription(){return this.distribute(e=>e.defaultShortDescription,rz)}innerToJsonSchema(e){if(this.branchGroups.length===1&&this.branchGroups[0].equals(Q.intrinsic.boolean))return{type:`boolean`};let t=this.branchGroups.map(t=>t.toJsonSchemaRecurse(e));return t.every(e=>Object.keys(e).length===1&&CP(e,`const`))?{enum:t.map(e=>e.const)}:{anyOf:t}}traverseAllows=(e,t)=>this.branches.some(n=>n.traverseAllows(e,t));traverseApply=(e,t)=>{let n=[];for(let r=0;r{for(let t=0;t{for(let t in n){let r=n[t],a=t===`default`?t:`case ${t}`,o;o=r===!0?i?`data`:`true`:i?r.rootApplyStrategy===`branchedOptimistic`?e.invoke(r,{kind:`Optimistic`}):r.contextFreeMorph?`${e.invoke(r)} ? ${oI(r.contextFreeMorph)}(data) : "${NP}"`:`${e.invoke(r)} ? data : "${NP}"`:e.invoke(r),e.line(`${a}: return ${o}`)}return e}),e.traversalKind===`Allows`){e.return(i?`"${NP}"`:!1);return}let a=rz(this.discriminant.kind===`domain`?r.map(e=>{let t=e.slice(1,-1);return t===`function`?pP.object:pP[t]}):r),o=this.discriminant.path.map(e=>typeof e==`symbol`?oI(e):JSON.stringify(e)),s=JSON.stringify(a),c=this.discriminant.kind===`domain`?`${ZR}[${t}]`:`${QR}(${t})`;e.line(`ctx.errorFromNodeContext({ + code: "predicate", + expected: ${s}, + actual: ${c}, + relativePath: [${o}], + meta: ${this.compiledMeta} +})`)}compileIndiscriminable(e){if(e.traversalKind===`Apply`){e.const(`errors`,`[]`);for(let t of this.branches)e.line(`ctx.pushBranch()`).line(e.invoke(t)).if(`!ctx.hasError()`,()=>e.return(t.includesTransform?`ctx.queuedMorphs.push(...ctx.popBranch().queuedMorphs)`:`ctx.popBranch()`)).line(`errors.push(ctx.popBranch().error)`);e.line(`ctx.errorFromNodeContext({ code: "union", errors, meta: ${this.compiledMeta} })`)}else{let{optimistic:t}=e;e.optimistic=!1;for(let n of this.branches)e.if(`${e.invoke(n)}`,()=>e.return(t?n.contextFreeMorph?`${oI(n.contextFreeMorph)}(data)`:`data`:!0));e.return(t?`"${NP}"`:!1)}}get nestableExpression(){return this.isBoolean?`boolean`:`(${this.expression})`}discriminate(){if(this.branches.length<2||this.isCyclic)return null;if(this.unitBranches.length===this.branches.length)return{kind:`unit`,path:[],optionallyChainedPropString:`data`,cases:bP(this.unitBranches,(e,t)=>[`${t.rawIn.serializedValue}`,t.hasKind(`morph`)?t:!0])};let e=[];for(let t=0;tuP(e.path,n.path)&&e.kind===n.kind);o?(o.cases[i]?o.cases[i].branchIndices=cP(o.cases[i].branchIndices,t):o.cases[i]??={branchIndices:[t],condition:n.l},o.cases[a]?o.cases[a].branchIndices=cP(o.cases[a].branchIndices,r):o.cases[a]??={branchIndices:[r],condition:n.r}):e.push({kind:n.kind,cases:{[i]:{branchIndices:[t],condition:n.l},[a]:{branchIndices:[r],condition:n.r}},path:n.path})}}}let t=this.ordered?JR(e,this.branches):e;if(!t.length)return null;let n=KR(t,this),r={};for(let e in n.best.cases){let t=qR(n,e);if(t===null){r[e]=!0;continue}if(t.length===this.branches.length)return null;this.ordered&&t.sort((e,t)=>e.originalIndex-t.originalIndex);let i=t.map(e=>e.branch),a=i.length===1?i[0]:this.$.node(`union`,this.ordered?{branches:i,ordered:!0}:i);Object.assign(this.referencesById,a.referencesById),r[e]=a}if(n.defaultEntries.length){let e=n.defaultEntries.map(e=>e.branch);r.default=this.$.node(`union`,this.ordered?{branches:e,ordered:!0}:e,{prereduced:!0}),Object.assign(this.referencesById,r.default.referencesById)}return Object.assign(n.location,{cases:r})}},KR=(e,t)=>{let n=e.sort((e,t)=>e.path.length===t.path.length?Object.keys(t.cases).length-Object.keys(e.cases).length:e.path.length-t.path.length)[0];return{best:n,location:{kind:n.kind,path:n.path,optionallyChainedPropString:XR(n.path)},defaultEntries:t.branches.map((e,t)=>({originalIndex:t,branch:e})),node:t}},qR=(e,t)=>{let n=e.best.cases[t],r=YR(n.condition,e.location.path,e.node.$),i=[],a=[];for(let t=0;te.filter(e=>{let n=Object.values(e.cases).map(e=>e.branchIndices);for(let e=0;er&&t[n].overlaps(t[r]))return!1}}return!0}),YR=(e,t,n)=>{let r=e===`undefined`?n.node(`unit`,{unit:void 0}):e===`null`?n.node(`unit`,{unit:null}):e===`boolean`?n.units([!0,!1]):e;for(let e=t.length-1;e>=0;e--){let i=t[e];r=n.node(`intersection`,typeof i==`number`?{proto:`Array`,sequence:[...iP(i).map(e=>({})),r]}:{domain:`object`,required:[{key:i,value:r}]})}return r},XR=e=>e.reduce((e,t)=>e+lI(t,!0),`data`),ZR=oI(mP),QR=oI(HF);const $R={implementation:WR,Node:GR};var ez=e=>({kind:e.kind,path:e.path.map(e=>typeof e==`string`?e:cI(e)),cases:bP(e.cases,(e,t)=>[e,t===!0?t:t.hasKind(`union`)&&t.discriminantJson?t.discriminantJson:t.json])}),tz={delimiter:` | `,finalDelimiter:` | `},nz=e=>rz(e,tz);const rz=(e,t)=>{let n=t?.delimiter??`, `,r=t?.finalDelimiter??` or `;if(e.length===0)return`never`;if(e.length===1)return e[0];if(e.length===2&&e[0]===`false`&&e[1]===`true`||e[0]===`true`&&e[1]===`false`)return`boolean`;let i={},a=e.filter(e=>i[e]?!1:i[e]=!0),o=a.pop();return`${a.join(n)}${a.length?r:``}${o}`},iz=(e,t,n)=>{let r=t.map(()=>[]);for(let i=0;ie?.flatMap(e=>e.branches)??t[n]);return i.length===0?$.init(`union`,e,t):i},az=({branches:e,ordered:t})=>{if(e.length<2)return e;let n=e.map(()=>!0);for(let r=0;rn[t])};var oz=(e,t)=>{!e.includesTransform&&!t.includesTransform||(uP(e.shallowMorphs,t.shallowMorphs)||Z(cz(e.expression,t.expression)),uP(e.flatMorphs,t.flatMorphs,{isEqual:(e,t)=>e.propString===t.propString&&(e.node.hasKind(`morph`)&&t.node.hasKind(`morph`)?e.node.hasEqualMorphs(t.node):e.node.hasKind(`intersection`)&&t.node.hasKind(`intersection`)?e.node.structure?.structuralMorphRef===t.node.structure?.structuralMorphRef:!1)})||Z(cz(e.expression,t.expression)))};const sz=(e,t)=>e.transform((e,t)=>e===`domain`||e===`unit`?null:t,{shouldTransform:(e,n)=>{let r=XR(n.path);return t.optionallyChainedPropString.startsWith(r)?e.hasKind(`domain`)&&e.domain===`object`||(e.hasKind(`domain`)||t.kind===`unit`)&&r===t.optionallyChainedPropString?!0:e.children.length!==0&&e.kind!==`index`:!1}}),cz=(e,t)=>`An unordered union of a type including a morph and a type with overlapping input is indeterminate: +Left: ${e} +Right: ${t}`,lz=(e,t)=>`The intersection of two ordered unions is indeterminate: +Left: ${e} +Right: ${t}`,uz={implementation:MI({kind:`unit`,hasAssociatedError:!0,keys:{unit:{preserveUndefined:!0,serialize:e=>e instanceof Date?e.toISOString():AI(e)}},normalize:e=>e,defaults:{description:e=>HF(e.unit),problem:({expected:e,actual:t})=>`${e===t?`must be reference equal to ${e} (serialized to the same value)`:`must be ${e} (was ${t})`}`},intersections:{unit:(e,t)=>$.init(`unit`,e,t),...kR(`unit`,(e,t)=>{if(t.allows(e.unit))return e;let n=t.hasKind(`intersection`)?t.basis:t;if(n){let t=n.hasKind(`domain`)?n:Q.intrinsic.object;if(e.domain!==t.domain){let n=e.domain===`undefined`||e.domain===`null`||e.domain===`boolean`?e.domain:Q.intrinsic[e.domain];return $.init(`domain`,n,t)}}return $.init(`assignability`,e,t.hasKind(`intersection`)?t.children.find(t=>!t.allows(e.unit)):t)})}}),Node:class extends IR{compiledValue=this.json.unit;serializedValue=typeof this.unit==`string`||this.unit instanceof Date?JSON.stringify(this.compiledValue):`${this.compiledValue}`;compiledCondition=dz(this.unit,this.serializedValue);compiledNegation=dz(this.unit,this.serializedValue,`negated`);expression=HF(this.unit);domain=fP(this.unit);get defaultShortDescription(){return this.domain===`object`?pP.object:this.description}innerToJsonSchema(e){return this.unit===null?{type:`null`}:Q.intrinsic.jsonPrimitive.allows(this.unit)?{const:this.unit}:e.fallback.unit({code:`unit`,base:{},unit:this.unit})}traverseAllows=this.unit instanceof Date?e=>e instanceof Date&&e.toISOString()===this.compiledValue:Number.isNaN(this.unit)?e=>Number.isNaN(e):e=>e===this.unit}};var dz=(e,t,n)=>{if(e instanceof Date){let e=`data instanceof Date && data.toISOString() === ${t}`;return n?`!(${e})`:e}return Number.isNaN(e)?`${n?`!`:``}Number.isNaN(data)`:`data ${n?`!`:`=`}== ${t}`};const fz={implementation:MI({kind:`index`,hasAssociatedError:!1,intersectionIsOpen:!0,keys:{signature:{child:!0,parse:(e,t)=>{let n=t.$.parseSchema(e);if(!n.extends(Q.intrinsic.key))return Z(mz(n.expression));let r=n.branches.filter(e=>e.hasKind(`unit`));return r.length?Z(pz(r.map(e=>HF(e.unit)))):n}},value:{child:!0,parse:(e,t)=>t.$.parseSchema(e)}},normalize:e=>e,defaults:{description:e=>`[${e.signature.expression}]: ${e.value.description}`},intersections:{index:(e,t,n)=>{if(e.signature.equals(t.signature)){let r=cL(e.value,t.value,n),i=r instanceof $?Q.intrinsic.never.internal:r;return n.$.node(`index`,{signature:e.signature,value:i})}return e.signature.extends(t.signature)&&e.value.subsumes(t.value)?t:t.signature.extends(e.signature)&&t.value.subsumes(e.value)?e:null}}}),Node:class extends pL{impliedBasis=Q.intrinsic.object.internal;expression=`[${this.signature.expression}]: ${this.value.expression}`;flatRefs=aP(this.value.flatRefs.map(e=>QI([this.signature,...e.path],e.node)),QI([this.signature],this.value));traverseAllows=(e,t)=>AP(e).every(e=>this.signature.traverseAllows(e[0],t)?GI(e[0],()=>this.value.traverseAllows(e[1],t),t):!0);traverseApply=(e,t)=>{for(let n of AP(e))this.signature.traverseAllows(n[0],t)&&GI(n[0],()=>this.value.traverseApply(n[1],t),t)};_transform(e,t){t.path.push(this.signature);let n=super._transform(e,t);return t.path.pop(),n}compile(){}}},pz=e=>`Index keys ${e.join(`, `)} should be specified as named props.`,mz=e=>`Indexed key definition '${e}' must be a string or symbol`,hz={implementation:MI({kind:`required`,hasAssociatedError:!0,intersectionIsOpen:!0,keys:{key:{},value:{child:!0,parse:(e,t)=>t.$.parseSchema(e)}},normalize:e=>e,defaults:{description:e=>`${e.compiledKey}: ${e.value.description}`,expected:e=>e.missingValueDescription,actual:()=>`missing`},intersections:{required:uR,optional:uR}}),Node:class extends dR{expression=`${this.compiledKey}: ${this.value.expression}`;errorContext=Object.freeze({code:`required`,missingValueDescription:this.value.defaultShortDescription,relativePath:[this.key],meta:this.meta});compiledErrorContext=jI(this.errorContext)}};var gz=MI({kind:`sequence`,hasAssociatedError:!1,collapsibleKey:`variadic`,keys:{prefix:{child:!0,parse:(e,t)=>{if(e.length!==0)return e.map(e=>t.$.parseSchema(e))}},optionals:{child:!0,parse:(e,t)=>{if(e.length!==0)return e.map(e=>t.$.parseSchema(e))}},defaultables:{child:e=>e.map(e=>e[0]),parse:(e,t)=>{if(e.length!==0)return e.map(e=>{let n=t.$.parseSchema(e[0]);return _R(n,e[1],null),[n,e[1]]})},serialize:e=>e.map(e=>[e[0].collapsibleJson,AI(e[1])]),reduceIo:(e,t,n)=>{if(e===`in`){t.optionals=n.map(e=>e[0].rawIn);return}t.prefix=n.map(e=>e[0].rawOut)}},variadic:{child:!0,parse:(e,t)=>t.$.parseSchema(e,t)},minVariadicLength:{parse:e=>e===0?void 0:e},postfix:{child:!0,parse:(e,t)=>{if(e.length!==0)return e.map(e=>t.$.parseSchema(e))}}},normalize:e=>{if(typeof e==`string`)return{variadic:e};if(`variadic`in e||`prefix`in e||`defaultables`in e||`optionals`in e||`postfix`in e||`minVariadicLength`in e){if(e.postfix?.length){if(!e.variadic)return Z(wz);if(e.optionals?.length||e.defaultables?.length)return Z(Cz)}return e.minVariadicLength&&!e.variadic?Z(`minVariadicLength may not be specified without a variadic element`):e}return{variadic:e}},reduce:(e,t)=>{let n=e.minVariadicLength??0,r=e.prefix?.slice()??[],i=e.defaultables?.slice()??[],a=e.optionals?.slice()??[],o=e.postfix?.slice()??[];if(e.variadic){for(;a[a.length-1]?.equals(e.variadic);)a.pop();if(a.length===0&&i.length===0)for(;r[r.length-1]?.equals(e.variadic);)r.pop(),n++;for(;o[0]?.equals(e.variadic);)o.shift(),n++}else a.length===0&&i.length===0&&r.push(...o.splice(0));if(n!==e.minVariadicLength||e.prefix&&e.prefix.length!==r.length)return t.node(`sequence`,{...e,prefix:r,defaultables:i,optionals:a,postfix:o,minVariadicLength:n},{prereduced:!0})},defaults:{description:e=>e.isVariadicOnly?`${e.variadic.nestableExpression}[]`:`[${e.tuple.map(e=>e.kind===`defaultables`?`${e.node.nestableExpression} = ${HF(e.default)}`:e.kind===`optionals`?`${e.node.nestableExpression}?`:e.kind===`variadic`?`...${e.node.nestableExpression}[]`:e.node.expression).join(`, `)}]`},intersections:{sequence:(e,t,n)=>{let r=Tz({l:e.tuple,r:t.tuple,disjoint:new $,result:[],fixedVariants:[],ctx:n}),i=r.disjoint.length===0?[r,...r.fixedVariants]:r.fixedVariants;return i.length===0?r.disjoint:i.length===1?n.$.node(`sequence`,Sz(i[0].result)):n.$.node(`union`,i.map(e=>({proto:Array,sequence:Sz(e.result)})))}}}),_z=class extends pL{impliedBasis=Q.intrinsic.Array.internal;tuple=xz(this.inner);prefixLength=this.prefix?.length??0;defaultablesLength=this.defaultables?.length??0;optionalsLength=this.optionals?.length??0;postfixLength=this.postfix?.length??0;defaultablesAndOptionals=[];prevariadic=this.tuple.filter(e=>e.kind===`defaultables`||e.kind===`optionals`?(this.defaultablesAndOptionals.push(e.node),!0):e.kind===`prefix`);variadicOrPostfix=oP(this.variadic&&[this.variadic],this.postfix);flatRefs=this.addFlatRefs();addFlatRefs(){return eL(this.flatRefs,this.prevariadic.flatMap((e,t)=>aP(e.node.flatRefs.map(e=>QI([`${t}`,...e.path],e.node)),QI([`${t}`],e.node)))),eL(this.flatRefs,this.variadicOrPostfix.flatMap(e=>aP(e.flatRefs.map(e=>QI([Q.intrinsic.nonNegativeIntegerString.internal,...e.path],e.node)),QI([Q.intrinsic.nonNegativeIntegerString.internal],e)))),this.flatRefs}isVariadicOnly=this.prevariadic.length+this.postfixLength===0;minVariadicLength=this.inner.minVariadicLength??0;minLength=this.prefixLength+this.minVariadicLength+this.postfixLength;minLengthNode=this.minLength===0?null:this.$.node(`minLength`,this.minLength);maxLength=this.variadic?null:this.tuple.length;maxLengthNode=this.maxLength===null?null:this.$.node(`maxLength`,this.maxLength);impliedSiblings=this.minLengthNode?this.maxLengthNode?[this.minLengthNode,this.maxLengthNode]:[this.minLengthNode]:this.maxLengthNode?[this.maxLengthNode]:[];defaultValueMorphs=yz(this);defaultValueMorphsReference=this.defaultValueMorphs.length?oI(this.defaultValueMorphs):void 0;elementAtIndex(e,t){if(t=n?{kind:`postfix`,node:this.postfix[t-n]}:{kind:`variadic`,node:this.variadic??gP(`Unexpected attempt to access index ${t} on ${this}`)}}traverseAllows=(e,t)=>{for(let n=0;n{let n=0;for(;nthis.elementAtIndex(e,n).node.traverseApply(e[n],t),t)};get element(){return this.cacheGetter(`element`,this.$.node(`union`,this.children))}compile(e){if(this.prefix)for(let[t,n]of this.prefix.entries())e.traverseKey(`${t}`,`data[${t}]`,n);for(let[t,n]of this.defaultablesAndOptionals.entries()){let r=`${t+this.prefixLength}`;e.if(`${r} >= data.length`,()=>e.traversalKind===`Allows`?e.return(!0):e.return()),e.traverseKey(r,`data[${r}]`,n)}if(this.variadic&&(this.postfix&&e.const(`firstPostfixIndex`,`data.length${this.postfix?`- ${this.postfix.length}`:``}`),e.for(`i < ${this.postfix?`firstPostfixIndex`:`data.length`}`,()=>e.traverseKey(`i`,`data[i]`,this.variadic),this.prevariadic.length),this.postfix))for(let[t,n]of this.postfix.entries()){let r=`firstPostfixIndex + ${t}`;e.traverseKey(r,`data[${r}]`,n)}e.traversalKind===`Allows`&&e.return(!0)}_transform(e,t){t.path.push(Q.intrinsic.nonNegativeIntegerString.internal);let n=super._transform(e,t);return t.path.pop(),n}expression=this.description;reduceJsonSchema(e,t){let n=t.target===`draft-07`;if(this.prevariadic.length){let r=this.prevariadic.map(e=>{let n=e.node.toJsonSchemaRecurse(t);if(e.kind===`defaultables`){let r=typeof e.default==`function`?e.default():e.default;n.default=Q.intrinsic.jsonData.allows(r)?r:t.fallback.defaultValue({code:`defaultValue`,base:n,value:r})}return n});n?e.items=r:e.prefixItems=r}if(this.minLength&&(e.minItems=this.minLength),this.variadic){let r=this.variadic.toJsonSchemaRecurse(t);if(n&&this.prevariadic.length?e.additionalItems=r:e.items=r,this.maxLength&&(e.maxItems=this.maxLength),this.postfix){let n=this.postfix.map(e=>e.toJsonSchemaRecurse(t));e=t.fallback.arrayPostfix({code:`arrayPostfix`,base:e,elements:n})}}else n?e.additionalItems=!1:e.items=!1,delete e.maxItems;return e}},vz={},yz=e=>{if(!e.defaultables)return[];let t=[],n=`[`,r=e.prefixLength+e.defaultablesLength-1;for(let i=e.prefixLength;i<=r;i++){let[r,a]=e.defaultables[i-e.prefixLength];t.push(gR(i,r,a)),n+=`${i}: ${r.id} = ${AI(a)}, `}return n+=`]`,vz[n]??=t};const bz={implementation:gz,Node:_z};var xz=e=>{let t=[];if(e.prefix)for(let n of e.prefix)t.push({kind:`prefix`,node:n});if(e.defaultables)for(let[n,r]of e.defaultables)t.push({kind:`defaultables`,node:n,default:r});if(e.optionals)for(let n of e.optionals)t.push({kind:`optionals`,node:n});if(e.variadic&&t.push({kind:`variadic`,node:e.variadic}),e.postfix)for(let n of e.postfix)t.push({kind:`postfix`,node:n});return t},Sz=e=>e.reduce((e,t)=>(t.kind===`variadic`?e.variadic=t.node:t.kind===`defaultables`?e.defaultables=aP(e.defaultables,[[t.node,t.default]]):e[t.kind]=aP(e[t.kind],t.node),e),{});const Cz=`A postfix required element cannot follow an optional or defaultable element`,wz=`A postfix element requires a variadic element`;var Tz=e=>{let[t,...n]=e.l,[r,...i]=e.r;if(!t||!r)return e;let a=n[n.length-1]?.kind===`postfix`,o=i[i.length-1]?.kind===`postfix`,s=t.kind===`prefix`||r.kind===`prefix`?`prefix`:t.kind===`postfix`||r.kind===`postfix`?`postfix`:t.kind===`variadic`&&r.kind===`variadic`?`variadic`:a||o?`prefix`:t.kind===`defaultables`||r.kind===`defaultables`?`defaultables`:`optionals`;if(t.kind===`prefix`&&r.kind===`variadic`&&o){let t=Tz({...e,fixedVariants:[],r:i.map(e=>({...e,kind:`prefix`}))});t.disjoint.length===0&&e.fixedVariants.push(t)}else if(r.kind===`prefix`&&t.kind===`variadic`&&a){let t=Tz({...e,fixedVariants:[],l:n.map(e=>({...e,kind:`prefix`}))});t.disjoint.length===0&&e.fixedVariants.push(t)}let c=cL(t.node,r.node,e.ctx);if(c instanceof $)if(s===`prefix`||s===`postfix`)e.disjoint.push(...c.withPrefixKey(s===`prefix`?e.result.length:`-${n.length+1}`,Ez(t)&&Ez(r)?`required`:`optional`)),e.result=[...e.result,{kind:s,node:Q.intrinsic.never.internal}];else if(s===`optionals`||s===`defaultables`)return e;else return Tz({...e,fixedVariants:[],l:n.map(e=>({...e,kind:`prefix`})),r:n.map(e=>({...e,kind:`prefix`}))});else s===`defaultables`?(t.kind===`defaultables`&&r.kind===`defaultables`&&t.default!==r.default&&Z(fR(t.default,r.default)),e.result=[...e.result,{kind:s,node:c,default:t.kind===`defaultables`?t.default:r.kind===`defaultables`?r.default:gP(`Unexpected defaultable intersection from ${t.kind} and ${r.kind} elements.`)}]):e.result=[...e.result,{kind:s,node:c}];let l=e.l.length,u=e.r.length;return(t.kind!==`variadic`||l>=u&&(r.kind===`variadic`||u===1))&&(e.l=n),(r.kind!==`variadic`||u>=l&&(t.kind===`variadic`||l===1))&&(e.r=i),Tz(e)},Ez=e=>e.kind===`prefix`||e.kind===`postfix`,Dz=e=>t=>{if(t.props.length||t.index){let n=t.index?.map(t=>t[e])??[];for(let r of t.props)n.push(r[e]);t.undeclared&&n.push(`+ (undeclared): ${t.undeclared}`);let r=`{ ${n.join(`, `)} }`;return t.sequence?`${r} & ${t.sequence.description}`:r}return t.sequence?.description??`{}`},Oz=Dz(`description`),kz=Dz(`expression`),Az=(e,t,n)=>{let r=e.required?`required`:`optional`;if(!t.signature.allows(e.key))return null;let i=oL(e.value,t.value,n);return i instanceof $?r===`optional`?n.node(`optional`,{key:e.key,value:Q.intrinsic.never.internal}):i.withPrefixKey(e.key,e.kind):null},jz=MI({kind:`structure`,hasAssociatedError:!1,normalize:e=>e,applyConfig:(e,t)=>!e.undeclared&&t.onUndeclaredKey!==`ignore`?{...e,undeclared:t.onUndeclaredKey}:e,keys:{required:{child:!0,parse:hL(`required`),reduceIo:(e,t,n)=>{t.required=aP(t.required,n.map(t=>e===`in`?t.rawIn:t.rawOut))}},optional:{child:!0,parse:hL(`optional`),reduceIo:(e,t,n)=>{if(e===`in`){t.optional=n.map(e=>e.rawIn);return}for(let e of n)t[e.outProp.kind]=aP(t[e.outProp.kind],e.outProp.rawOut)}},index:{child:!0,parse:hL(`index`)},sequence:{child:!0,parse:hL(`sequence`)},undeclared:{parse:e=>e===`ignore`?void 0:e,reduceIo:(e,t,n)=>{if(n===`reject`){t.undeclared=`reject`;return}e===`in`?delete t.undeclared:t.undeclared=`reject`}}},defaults:{description:Oz},intersections:{structure:(e,t,n)=>{let r={...e.inner},i={...t.inner},a=new $;if(e.undeclared){let r=e.keyof();for(let e of t.requiredKeys)r.allows(e)||a.add(`presence`,Q.intrinsic.never.internal,t.propsByKey[e].value,{path:[e]});i.optional&&=i.optional.filter(e=>r.allows(e.key)),i.index&&=i.index.flatMap(e=>{if(e.signature.extends(r))return e;let t=oL(r,e.signature,n.$);if(t instanceof $)return[];let a=Bz(t,e.value,n.$);return a.required&&(i.required=oP(i.required,a.required)),a.optional&&(i.optional=oP(i.optional,a.optional)),a.index??[]})}if(t.undeclared){let i=t.keyof();for(let t of e.requiredKeys)i.allows(t)||a.add(`presence`,e.propsByKey[t].value,Q.intrinsic.never.internal,{path:[t]});r.optional&&=r.optional.filter(e=>i.allows(e.key)),r.index&&=r.index.flatMap(e=>{if(e.signature.extends(i))return e;let t=oL(i,e.signature,n.$);if(t instanceof $)return[];let a=Bz(t,e.value,n.$);return a.required&&(r.required=oP(r.required,a.required)),a.optional&&(r.optional=oP(r.optional,a.optional)),a.index??[]})}let o={};(e.undeclared||t.undeclared)&&(o.undeclared=e.undeclared===`reject`||t.undeclared===`reject`?`reject`:`delete`);let s=gL({kind:`structure`,baseInner:o,l:_L(r),r:_L(i),roots:[],ctx:n});return s instanceof $&&a.push(...s),a.length?a:s}},reduce:(e,t)=>{if(!e.required&&!e.optional)return;let n={},r=!1,i=e.optional?[...e.optional]:[];if(e.required)for(let r=0;re.impliedSiblings??[]);props=oP(this.required,this.optional);propsByKey=bP(this.props,(e,t)=>[t.key,t]);propsByKeyReference=oI(this.propsByKey);expression=kz(this);requiredKeys=this.required?.map(e=>e.key)??[];optionalKeys=this.optional?.map(e=>e.key)??[];literalKeys=[...this.requiredKeys,...this.optionalKeys];_keyof;keyof(){if(this._keyof)return this._keyof;let e=this.$.units(this.literalKeys).branches;if(this.index)for(let{signature:t}of this.index)e=e.concat(t.branches);return this._keyof=this.$.node(`union`,e)}map(e){return this.$.node(`structure`,this.props.flatMap(e).reduce((e,t)=>{let n=this.propsByKey[t.key];if(gI(t))return t.kind!==`required`&&t.kind!==`optional`?Z(`Map result must have kind "required" or "optional" (was ${t.kind})`):(e[t.kind]=aP(e[t.kind],t),e);let r=t.kind??n?.kind??`required`,i=bP(t,(e,t)=>e in pR.implementation.keys?[e,t]:[]);return e[r]=aP(e[r],this.$.node(r,i)),e},{}))}assertHasKeys(e){let t=e.filter(e=>!TR(e,this.keyof()));if(t.length)return Z(Hz(this.expression,t))}get(e,...t){let n,r=!1,i=Rz(e);if((typeof i==`string`||typeof i==`symbol`)&&this.propsByKey[i]&&(n=this.propsByKey[i].value,r=this.propsByKey[i].required),this.index)for(let e of this.index)TR(i,e.signature)&&(n=n?.and(e.value)??e.value);if(this.sequence&&TR(i,Q.intrinsic.nonNegativeIntegerString))if(hI(i,`root`))this.sequence.variadic&&(n=n?.and(this.sequence.element)??this.sequence.element);else{let e=Number.parseInt(i);if(ee.hasKind(`required`)?this.$.node(`optional`,e.inner):e)})}require(){let{optional:e,...t}=this.inner;return this.$.node(`structure`,{...t,required:this.props.map(e=>e.hasKind(`optional`)?{key:e.key,value:e.value}:e)})}merge(e){let t=this.filterKeys(`omit`,[e.keyof()]);return e.required&&(t.required=aP(t.required,e.required)),e.optional&&(t.optional=aP(t.optional,e.optional)),e.index&&(t.index=aP(t.index,e.index)),e.sequence&&(t.sequence=e.sequence),e.undeclared?t.undeclared=e.undeclared:delete t.undeclared,this.$.node(`structure`,t)}filterKeys(e,t){let n=pI(this.inner),r=n=>{let r=t.some(e=>TR(n,e));return e===`pick`?r:!r};return n.required&&=n.required.filter(e=>r(e.key)),n.optional&&=n.optional.filter(e=>r(e.key)),n.index&&=n.index.filter(e=>r(e.signature)),n}traverseAllows=(e,t)=>this._traverse(`Allows`,e,t);traverseApply=(e,t)=>this._traverse(`Apply`,e,t);_traverse=(e,t,n)=>{let r=n?.currentErrorCount??0;for(let i=0;ir)return!1;if(this.sequence){if(e===`Allows`){if(!this.sequence.traverseAllows(t,n))return!1}else if(this.sequence.traverseApply(t,n),n.failFast&&n.currentErrorCount>r)return!1}if(this.index||this.undeclared===`reject`){let i=Object.keys(t);i.push(...Object.getOwnPropertySymbols(t));for(let a=0;ai.value.traverseAllows(t[o],n),n))return!1}else if(GI(o,()=>i.value.traverseApply(t[o],n),n),n.failFast&&n.currentErrorCount>r)return!1}}if(this.undeclared===`reject`&&!this.declaresKey(o)&&(e===`Allows`||(n.errorFromNodeContext({code:`predicate`,expected:`removed`,actual:``,relativePath:[o],meta:this.meta}),n.failFast)))return!1}}return this.structuralMorph&&n&&!n.hasError()&&n.queueMorphs([this.structuralMorph]),!0};get defaultable(){return this.cacheGetter(`defaultable`,this.optional?.filter(e=>e.hasDefault())??[])}declaresKey=e=>e in this.propsByKey||this.index?.some(t=>t.signature.allows(e))||this.sequence!==void 0&&Q.intrinsic.nonNegativeIntegerString.allows(e);_compileDeclaresKey(e){let t=[];if(this.props.length&&t.push(`k in ${this.propsByKeyReference}`),this.index)for(let n of this.index)t.push(e.invoke(n.signature,{kind:`Allows`,arg:`k`}));return this.sequence&&t.push(`$ark.intrinsic.nonNegativeIntegerString.allows(k)`),t.join(` || `)||`false`}get structuralMorph(){return this.cacheGetter(`structuralMorph`,Fz(this))}structuralMorphRef=this.structuralMorph&&oI(this.structuralMorph);compile(e){e.traversalKind===`Apply`&&e.initializeErrorCount();for(let t of this.props)e.check(t),e.traversalKind===`Apply`&&e.returnIfFailFast();if(this.sequence&&(e.check(this.sequence),e.traversalKind===`Apply`&&e.returnIfFailFast()),(this.index||this.undeclared===`reject`)&&(e.const(`keys`,`Object.keys(data)`),e.line(`keys.push(...Object.getOwnPropertySymbols(data))`),e.for(`i < keys.length`,()=>this.compileExhaustiveEntry(e))),e.traversalKind===`Allows`)return e.return(!0);this.structuralMorphRef&&e.if(`ctx && !ctx.hasError()`,()=>(e.line(`ctx.queueMorphs([`),Iz(e,this),e.line(`])`)))}compileExhaustiveEntry(e){if(e.const(`k`,`keys[i]`),this.index)for(let t of this.index)e.if(`${e.invoke(t.signature,{arg:`k`,kind:`Allows`})}`,()=>e.traverseKey(`k`,`data[k]`,t.value));return this.undeclared===`reject`&&e.if(`!(${this._compileDeclaresKey(e)})`,()=>e.traversalKind===`Allows`?e.return(!1):e.line(`ctx.errorFromNodeContext({ code: "predicate", expected: "removed", actual: "", relativePath: [k], meta: ${this.compiledMeta} })`).if(`ctx.failFast`,()=>e.return())),e}reduceJsonSchema(e,t){switch(e.type){case`object`:return this.reduceObjectJsonSchema(e,t);case`array`:let n=this.sequence?.reduceJsonSchema(e,t)??e;return this.props.length||this.index?t.fallback.arrayObject({code:`arrayObject`,base:n,object:this.reduceObjectJsonSchema({type:`object`},t)}):n;default:return NI.throwInternalOperandError(`structure`,e)}}reduceObjectJsonSchema(e,t){if(this.props.length){e.properties={};for(let n of this.props){let r=n.value.toJsonSchemaRecurse(t);if(typeof n.key==`symbol`){t.fallback.symbolKey({code:`symbolKey`,base:e,key:n.key,value:r,optional:n.optional});continue}if(n.hasDefault()){let e=typeof n.default==`function`?n.default():n.default;r.default=Q.intrinsic.jsonData.allows(e)?e:t.fallback.defaultValue({code:`defaultValue`,base:r,value:e})}e.properties[n.key]=r}this.requiredKeys.length&&e.properties&&(e.required=this.requiredKeys.filter(t=>typeof t==`string`&&t in e.properties))}if(this.index)for(let n of this.index){let r=n.value.toJsonSchemaRecurse(t);if(n.signature.equals(Q.intrinsic.string)){e.additionalProperties=r;continue}for(let i of n.signature.branches){if(!i.extends(Q.intrinsic.string)){e=t.fallback.symbolKey({code:`symbolKey`,base:e,key:null,value:r,optional:!1});continue}let n={type:`string`};if(i.hasKind(`morph`)&&(n=t.fallback.morph({code:`morph`,base:i.rawIn.toJsonSchemaRecurse(t),out:i.rawOut.toJsonSchemaRecurse(t)})),!i.hasKind(`intersection`))return gP(`Unexpected index branch kind ${i.kind}.`);let{pattern:a}=i.inner;if(a){let i=Object.assign(n,{pattern:a[0].rule});for(let e=1;e{let t=``;for(let n=0;n{let t=Pz(e);return t?Nz[t]?Nz[t]:Nz[t]=(t,n)=>{for(let r=0;r{let n=`(data${t.defaultable.some(e=>e.defaultValueMorph.length===2)||t.sequence?.defaultValueMorphs.some(e=>e.length===2)?`, ctx`:``})`;return e.block(`${n} => `,e=>{for(let r=0;re.line(`${a}${n}`))}return t.sequence?.defaultables&&e.for(`i < ${t.sequence.defaultables.length}`,e=>e.set(`data[i]`,5),`data.length - ${t.sequence.prefixLength}`),t.undeclared===`delete`&&e.forIn(`data`,e=>e.if(`!(${t._compileDeclaresKey(e)})`,e=>e.line(`delete data[k]`))),e.return(`data`)})};const Lz={implementation:jz,Node:Mz};var Rz=e=>(hI(e,`root`)&&e.hasKind(`unit`)&&(e=e.unit),typeof e==`number`&&(e=`${e}`),e);const zz=(e,t)=>`${e} is not allowed as an array index on ${t}. Use the 'nonNegativeIntegerString' keyword instead.`,Bz=(e,t,n)=>{let[r,i]=tP(e.branches,e=>e.hasKind(`unit`));if(!r.length)return{index:n.node(`index`,{signature:e,value:t})};let a={};for(let e of r){let r=n.node(`required`,{key:e.unit,value:t});a[r.kind]=aP(a[r.kind],r)}return i.length&&(a.index=n.node(`index`,{signature:i,value:t})),a},Vz=e=>hI(e,`root`)?e.expression:HF(e),Hz=(e,t)=>`Key${t.length===1?``:`s`} ${t.map(Vz).join(`, `)} ${t.length===1?`does`:`do`} not exist on ${e}`,Uz=e=>`Duplicate key ${cI(e)}`,Wz={...YL,alias:FR.implementation,domain:LR.implementation,unit:uz.implementation,proto:UR.implementation,union:$R.implementation,morph:VR.implementation,intersection:RR.implementation,divisor:EL.implementation,pattern:ZL.implementation,predicate:TL.implementation,required:hz.implementation,optional:pR.implementation,index:fz.implementation,sequence:bz.implementation,structure:Lz.implementation};Q.defaultConfig=MP(Object.assign(bP(Wz,(e,t)=>[e,t.defaults]),{jitless:tF(),clone:YP,onUndeclaredKey:`ignore`,exactOptionalPropertyTypes:!0,numberAllowsNaN:!1,dateAllowsInvalid:!1,onFail:null,keywords:{},toJsonSchema:NI.defaultConfig})),Q.resolvedConfig=PI(Q.defaultConfig,Q.config);const Gz={...XL,alias:FR.Node,domain:LR.Node,unit:uz.Node,proto:UR.Node,union:$R.Node,morph:VR.Node,intersection:RR.Node,divisor:EL.Node,pattern:ZL.Node,predicate:TL.Node,required:hz.Node,optional:pR.Node,index:fz.Node,sequence:bz.Node,structure:Lz.Node};var Kz=class extends wP{get[mI](){return`module`}};const qz=(e,t)=>new Kz(bP(e,(e,n)=>[e,hI(n,`module`)?qz(n,t):t.bindReference(n)]));var Jz=e=>HP(e)?e:`branches`in e&&HP(e.branches)?e.branches:void 0,Yz=(e,t)=>Z(`Node of kind ${t} is not valid as a ${e} definition`);const Xz=e=>`#${e} duplicates public alias ${e}`;var Zz={};Q.ambient??={};var Qz,$z=`function $`,eB=e=>tB(e,nB(e)),tB=(e,t)=>{let n=t.write($z,4),r=t.compile()();for(let t of e)t.precompilation||=(t.traverseAllows=r[`${t.id}Allows`].bind(r),t.isRoot()&&!t.allowsRequiresContext&&(t.allows=t.traverseAllows),t.traverseApply=r[`${t.id}Apply`].bind(r),r[`${t.id}Optimistic`]&&(t.traverseOptimistic=r[`${t.id}Optimistic`].bind(r)),n)},nB=e=>new sI().return(e.reduce((e,t)=>{let n=new fI({kind:`Allows`}).indent();t.compile(n);let r=n.write(`${t.id}Allows`),i=new fI({kind:`Apply`}).indent();t.compile(i);let a=`${e}${r},\n${i.write(`${t.id}Apply`)},\n`;if(!t.hasKind(`union`))return a;let o=new fI({kind:`Allows`,optimistic:!0}).indent();return t.compile(o),`${a}${o.write(`${t.id}Optimistic`)},\n`},`{ +`)+`}`),rB=class{config;resolvedConfig;name;get[mI](){return`scope`}referencesById={};references=[];resolutions={};exportedNames=[];aliases={};resolved=!1;nodesByHash={};intrinsic;constructor(e,t){this.config=PI(Q.config,t),this.resolvedConfig=PI(Q.resolvedConfig,t),this.name=this.resolvedConfig.name??`anonymousScope${Object.keys(Zz).length}`,this.name in Zz&&Z(`A Scope already named ${this.name} already exists`),Zz[this.name]=this;let n=Object.entries(e).map(e=>this.preparseOwnAliasEntry(...e));for(let[e,t]of n){let n=e;if(e[0]===`#`?(n=e.slice(1),n in this.aliases&&Z(Xz(n)),this.aliases[n]=t):(n in this.aliases&&Z(Xz(e)),this.aliases[n]=t,this.exportedNames.push(n)),!hI(t,`module`)&&!hI(t,`generic`)&&!QP(t)){let e=this.preparseOwnDefinitionFormat(t,{alias:n});this.resolutions[n]=hI(e,`root`)?this.bindReference(e):this.createParseContext(e).id}}Qz??=this.node(`union`,{branches:[`string`,`number`,`object`,`bigint`,`symbol`,{unit:!0},{unit:!1},{unit:void 0},{unit:null}]},{prereduced:!0}),this.nodesByHash[Qz.hash]=this.node(`intersection`,{},{prereduced:!0}),this.intrinsic=Q.intrinsic?bP(Q.intrinsic,(e,t)=>e.startsWith(`json`)?[]:[e,this.bindReference(t)]):{}}cacheGetter(e,t){return Object.defineProperty(this,e,{value:t}),t}get internal(){return this}_json;get json(){return this._json||this.export(),this._json}defineSchema(e){return e}generic=(...e)=>{let t=this;return(n,r)=>new CL(e,r?new SL(n):n,t,t,r??null)};units=(e,t)=>{let n=[];for(let t of e)n.includes(t)||n.push(t);let r=n.map(e=>this.node(`unit`,{unit:e},t));return this.node(`union`,r,{...t,prereduced:!0})};lazyResolutions=[];lazilyResolve(e,t){let n=this.node(`alias`,{reference:t??`synthetic`,resolve:e},{prereduced:!0});return this.resolved||this.lazyResolutions.push(n),n}schema=(e,t)=>this.finalize(this.parseSchema(e,t));parseSchema=(e,t)=>this.node(QL(e),e,t);preparseNode(e,t,n){let r=typeof e==`string`?e:QL(t,e);if(gI(t)&&t.kind===r)return t;if(r===`alias`&&!n?.prereduced){let{reference:e}=FR.implementation.normalize(t,this);if(e.startsWith(`$`)){let n=this.resolveRoot(e.slice(1));t=n,r=n.kind}}else if(r===`union`&&dP(t,`object`)){let e=Jz(t);e?.length===1&&(t=e[0],r=QL(t))}if(gI(t)&&t.kind===r)return t;let i=Wz[r].normalize?.(t,this)??t;return gI(i)?i.kind===r?i:Yz(r,i.kind):{...n,$:this,kind:r,def:i,prefix:n.alias??r}}bindReference(e){let t;return t=gI(e)?e.$===this?e:new e.constructor(e.attachments,this):e.$===this?e:new CL(e.params,e.bodyDef,e.$,this,e.hkt),this.resolved||Object.assign(this.referencesById,t.referencesById),t}resolveRoot(e){return this.maybeResolveRoot(e)??Z(dB(e))}maybeResolveRoot(e){let t=this.maybeResolve(e);if(!hI(t,`generic`))return t}maybeResolveSubalias(e){return sB(this.aliases,e)??sB(this.ambient,e)}get ambient(){return Q.ambient}maybeResolve(e){let t=this.resolutions[e];if(t){if(typeof t!=`string`)return this.bindReference(t);let n=rR[t];if(hI(n,`root`))return this.resolutions[e]=n;if(hI(n,`context`)){if(n.phase===`resolving`)return this.node(`alias`,{reference:`$${e}`},{prereduced:!0});if(n.phase===`resolved`)return gP(`Unexpected resolved context for was uncached by its scope: ${HF(n)}`);n.phase=`resolving`;let t=this.bindReference(this.parseOwnDefinitionFormat(n.def,n));return n.phase=`resolved`,rR[t.id]=t,rR[n.id]=t,this.resolutions[e]=t}return gP(`Unexpected nodesById entry for ${t}: ${HF(n)}`)}let n=this.aliases[e]??this.ambient?.[e];return n?(n=this.normalizeRootScopeValue(n),hI(n,`generic`)?this.resolutions[e]=this.bindReference(n):hI(n,`module`)?(n.root||Z(pB(e)),this.resolutions[e]=this.bindReference(n.root)):this.resolutions[e]=this.parse(n,{alias:e})):this.maybeResolveSubalias(e)}createParseContext(e){let t=e.id??iR(e.prefix);return rR[t]=Object.assign(e,{[mI]:`context`,$:this,id:t,phase:`unresolved`})}traversal(e){return new WI(e,this.resolvedConfig)}import(...e){return new Kz(bP(this.export(...e),(e,t)=>[`#${e}`,t]))}precompilation;_exportedResolutions;_exports;export(...e){if(!this._exports){this._exports={};for(let e of this.exportedNames){let t=this.aliases[e];this._exports[e]=hI(t,`module`)?qz(t,this):aB(this.maybeResolve(e))}for(let e of this.lazyResolutions)e.resolution;if(this._exportedResolutions=uB(this,this._exports),this._json=oB(this._exportedResolutions),Object.assign(this.resolutions,this._exportedResolutions),this.references=Object.values(this.referencesById),!this.resolvedConfig.jitless){let e=nB(this.references);this.precompilation=e.write($z,4),tB(this.references,e)}this.resolved=!0}return new Kz(bP(e.length?e:this.exportedNames,(e,t)=>[t,this._exports[t]]))}resolve(e){return this.export()[e]}node=(e,t,n={})=>{let r=this.preparseNode(e,t,n);if(gI(r))return this.bindReference(r);let i=this.createParseContext(r),a=aR(i),o=this.bindReference(a);return rR[i.id]=o};parse=(e,t={})=>this.finalize(this.parseDefinition(e,t));parseDefinition(e,t={}){if(hI(e,`root`))return this.bindReference(e);let n=this.preparseOwnDefinitionFormat(e,t);if(hI(n,`root`))return this.bindReference(n);let r=this.createParseContext(n);rR[r.id]=r;let i=this.bindReference(this.parseOwnDefinitionFormat(e,r));return i.isCyclic&&(i=sR(i,r.id)),rR[r.id]=i,i}finalize(e){return aB(e),!e.precompilation&&!this.resolvedConfig.jitless&&eB(e.references),e}},iB=class extends rB{parseOwnDefinitionFormat(e,t){return aR(t)}preparseOwnDefinitionFormat(e,t){return this.preparseNode(QL(e),e,t)}preparseOwnAliasEntry(e,t){return[e,t]}normalizeRootScopeValue(e){return e}},aB=e=>{let t=e.references.filter(e=>e.hasKind(`alias`));for(let n of t){Object.assign(n.referencesById,n.resolution.referencesById);for(let t of e.references)n.id in t.referencesById&&Object.assign(t.referencesById,n.referencesById)}return e},oB=e=>bP(e,(e,t)=>[e,hI(t,`root`)||hI(t,`generic`)?t.json:hI(t,`module`)?oB(t):gP(`Unexpected resolution ${HF(t)}`)]),sB=(e,t)=>{let n=t.indexOf(`.`);if(n===-1)return;let r=t.slice(0,n),i=e[r];if(i===void 0)return;if(!hI(i,`module`))return Z(fB(r));let a=t.slice(n+1),o=i[a];if(o===void 0)return sB(i,a);if(hI(o,`root`)||hI(o,`generic`))return o;if(hI(o,`module`))return o.root??Z(pB(t));gP(`Unexpected resolution for alias '${t}': ${HF(o)}`)};const cB=(e,t)=>new iB(e,t),lB=new iB({});var uB=(e,t)=>{let n={};for(let r in t){let i=t[r];if(hI(i,`module`)){let t=bP(uB(e,i),(e,t)=>[`${r}.${e}`,t]);Object.assign(n,t)}else hI(i,`root`)||hI(i,`generic`)?n[r]=i:gP(`Unexpected scope resolution ${HF(i)}`)}return n};const dB=e=>`'${e}' is unresolvable`,fB=e=>`'${e}' must reference a module to be accessed using dot syntax`,pB=e=>`Reference to submodule '${e}' must specify an alias`;lB.export();const mB=lB.schema,hB=lB.node;lB.defineSchema;const gB=lB.generic,_B=`^(?:0|[1-9]\\d*)$`;oI(new RegExp(_B));var vB=cB({bigint:`bigint`,boolean:[{unit:!1},{unit:!0}],false:{unit:!1},never:[],null:{unit:null},number:`number`,object:`object`,string:`string`,symbol:`symbol`,true:{unit:!0},unknown:{},undefined:{unit:void 0},Array,Date},{prereducedAliases:!0}).export();Q.intrinsic={...vB};var yB=cB({integer:{domain:`number`,divisor:1},lengthBoundable:[`string`,Array],key:[`string`,`symbol`],nonNegativeIntegerString:{domain:`string`,pattern:_B}},{prereducedAliases:!0}).export();Object.assign(Q.intrinsic,yB);var bB=cB({jsonPrimitive:[`string`,`number`,{unit:!0},{unit:!1},{unit:null}],jsonObject:{domain:`object`,index:{signature:`string`,value:`$jsonData`}},jsonData:[`$jsonPrimitive`,`$jsonObject`]},{prereducedAliases:!0}).export();const xB={...vB,...yB,...bB,emptyStructure:hB(`structure`,{},{prereduced:!0})};Q.intrinsic={...xB};const SB=((e,t)=>new RegExp(e,t));Object.assign(SB,{as:SB});const CB=e=>typeof e==`string`&&e[0]===`d`&&(e[1]===`'`||e[1]===`"`)&&e[e.length-1]===e[1],wB=e=>e.toString()!==`Invalid Date`,TB=e=>e.slice(2,-1),EB=e=>`'${e}' could not be parsed by the Date constructor`,DB=(e,t)=>OB(e,t);var OB=(e,t)=>{let n=new Date(e);if(wB(n))return n;let r=kF(e);if(r!==void 0){let e=new Date(r);if(wB(e))return e}return t?Z(t===!0?EB(e):t):void 0},kB=mB({proto:`Array`,sequence:`string`,required:{key:`groups`,value:[`object`,{unit:void 0}]}});const AB=(e,t)=>{let n=e.scanner.shiftUntilEscapable(IB[FB[t]]);if(e.scanner.lookahead===``)return e.error(RB(n,t));if(e.scanner.shift(),t in PB){let r;try{r=new RegExp(n)}catch(e){Z(String(e))}e.root=e.ctx.$.node(`intersection`,{domain:`string`,pattern:n},{prereduced:!0}),t===`x/`&&(e.root=e.ctx.$.node(`morph`,{in:e.root,morphs:e=>r.exec(e),declaredOut:kB}))}else if(SP(t,jB))e.root=e.ctx.$.node(`unit`,{unit:n});else{let t=DB(n,EB(n));e.root=e.ctx.$.node(`unit`,{meta:n,unit:t})}},jB={"'":1,'"':1},MB={"/":1,"'":1,'"':1},NB={"d'":`'`,'d"':`"`,"'":`'`,'"':`"`},PB={"/":`/`,"x/":`/`},FB={...NB,...PB},IB={"'":e=>e.lookahead===`'`,'"':e=>e.lookahead===`"`,"/":e=>e.lookahead===`/`};var LB={'"':`double-quote`,"'":`single-quote`,"/":`forward slash`};const RB=(e,t)=>`${t}${e} requires a closing ${LB[FB[t]]}`,zB=e=>`Private type references should not include '#'. Use '${e}' instead.`,BB=`Optional definitions like 'string?' are only valid as properties in an object or tuple`,VB=`Defaultable definitions like 'number = 0' are only valid as properties in an object or tuple`,HB={"<":1,">":1,"=":1,"|":1,"&":1,")":1,"[":1,"%":1,",":1,":":1,"?":1,"#":1,...lF},UB=(e,t)=>e===`>`?t[0]===`=`?t[1]===`=`:t.trimStart()===``||SP(t.trimStart()[0],HB):e===`=`?t[0]!==`=`:e===`,`||e===`?`,WB=(e,t,n)=>GB(e,t,n,[]);var GB=(e,t,n,r)=>{let i=n.parseUntilFinalizer();return r.push(i.root),i.finalizer===`>`?r.length===t.params.length?r:n.error(KB(e,t.names,r.map(e=>e.expression))):i.finalizer===`,`?GB(e,t,n,r):i.error(tI(`>`))};const KB=(e,t,n)=>`${e}<${t.join(`, `)}> requires exactly ${t.length} args (got ${n.length}${n.length===0?``:`: ${n.join(`, `)}`})`,qB=e=>{let t=e.scanner.shiftUntilLookahead(HB);t===`keyof`?e.addPrefix(`keyof`):e.root=YB(e,t)},JB=(e,t,n)=>(n.scanner.shiftUntilNonWhitespace(),n.scanner.shift()===`<`?t(...WB(e,t,n)):n.error(KB(e,t.names,[])));var YB=(e,t)=>XB(e,t)??ZB(e,t)??e.error(t===``?e.scanner.lookahead===`#`?zB(e.shiftedBy(1).scanner.shiftUntilLookahead(HB)):QB(e):dB(t)),XB=(e,t)=>{if(e.ctx.args?.[t]){let n=e.ctx.args[t];return typeof n==`string`?e.ctx.$.node(`alias`,{reference:n},{prereduced:!0}):n}let n=e.ctx.$.maybeResolve(t);if(hI(n,`root`))return n;if(n!==void 0)return hI(n,`generic`)?JB(t,n,e):Z(`Unexpected resolution ${HF(n)}`)},ZB=(e,t)=>{let n=AF(t);if(n!==void 0)return e.ctx.$.node(`unit`,{unit:n});let r=NF(t);if(r!==void 0)return e.ctx.$.node(`unit`,{unit:r})};const QB=e=>{let t=e.previousOperator();return t?$B(t,e.scanner.unscanned):eV(e.scanner.unscanned)},$B=(e,t=``)=>`Token '${e}' requires a right operand${t?` before '${t}'`:``}`,eV=e=>`Expected an expression${e?` before '${e}'`:``}`,tV=e=>e.scanner.lookahead===``?e.error(QB(e)):e.scanner.lookahead===`(`?e.shiftedBy(1).reduceGroupOpen():e.scanner.lookaheadIsIn(MB)?AB(e,e.scanner.shift()):e.scanner.lookaheadIsIn(lF)?tV(e.shiftedBy(1)):e.scanner.lookahead===`d`?e.scanner.nextLookahead in jB?AB(e,`${e.scanner.shift()}${e.scanner.shift()}`):qB(e):e.scanner.lookahead===`x`&&e.scanner.nextLookahead===`/`?e.shiftedBy(2)&&AB(e,`x/`):qB(e),nV={">":!0,">=":!0},rV={"<":!0,"<=":!0},iV={"<":`>`,">":`<`,"<=":`>=`,">=":`<=`,"==":`==`},aV=(e,t)=>`Left bounds are only valid when paired with right bounds (try ...${t}${e})`,oV=e=>`Left-bounded expressions must specify their limits using < or <= (was ${e})`,sV=(e,t,n,r)=>`An expression may have at most one left bound (parsed ${e}${iV[t]}, ${n}${iV[r]})`,cV=(e,t)=>{let n=uV(e,t);if(e.root.hasKind(`unit`)){if(typeof e.root.unit==`number`){e.reduceLeftBound(e.root.unit,n),e.unsetRoot();return}if(e.root.unit instanceof Date){let t=`d'${e.root.description??e.root.unit.toISOString()}'`;e.unsetRoot(),e.reduceLeftBound(t,n);return}}return pV(e,n)},lV={"<":1,">":1,"=":1};var uV=(e,t)=>e.scanner.lookaheadIs(`=`)?`${t}${e.scanner.shift()}`:t;const dV=(e,t,n,r)=>n.extends(Q.intrinsic.number)?typeof t==`number`?e===`==`?[`min`,`max`]:e[0]===`>`?[`min`]:[`max`]:Z(mV(e,t,r)):n.extends(Q.intrinsic.lengthBoundable)?typeof t==`number`?e===`==`?[`exactLength`]:e[0]===`>`?[`minLength`]:[`maxLength`]:Z(mV(e,t,r)):n.extends(Q.intrinsic.Date)?e===`==`?[`after`,`before`]:e[0]===`>`?[`after`]:[`before`]:Z(VL(n.expression));var fV=e=>({rule:CB(e.limit)?TB(e.limit):e.limit,exclusive:e.comparator.length===1});const pV=(e,t)=>{let n=e.unsetRoot(),r=e.scanner.location;e.parseOperand();let i=e.unsetRoot(),a=e.scanner.sliceChars(r,e.scanner.location);if(e.root=n,!i.hasKind(`unit`)||typeof i.unit!=`number`&&!(i.unit instanceof Date))return e.error(mV(t,a,`right`));let o=i.unit,s=t.length===1,c=dV(t,typeof o==`number`?o:a,n,`right`);for(let n of c)e.constrainRoot(n,t===`==`?{rule:o}:{rule:o,exclusive:s});if(!e.branches.leftBound)return;if(!SP(t,rV))return e.error(oV(t));let l=dV(e.branches.leftBound.comparator,e.branches.leftBound.limit,n,`left`);e.constrainRoot(l[0],fV(e.branches.leftBound)),e.branches.leftBound=null},mV=(e,t,n)=>`Comparator ${n===`left`?iV[e]:e} must be ${n===`left`?`preceded`:`followed`} by a corresponding literal (was ${t})`,hV=e=>{e.scanner.shiftUntilNonWhitespace();let t=e.scanner.shiftUntilLookahead(HB);e.root=e.root.brand(t)},gV=e=>{e.scanner.shiftUntilNonWhitespace();let t=e.scanner.shiftUntilLookahead(HB),n=jF(t,{errorOnFail:_V(t)});n===0&&e.error(_V(0)),e.root=e.root.constrain(`divisor`,n)},_V=e=>`% operator must be followed by a non-zero integer literal (was ${e})`,vV=e=>{let t=e.scanner.shift();return t===``?e.finalize(``):t===`[`?e.scanner.shift()===`]`?e.setRoot(e.root.array()):e.error(bV):t===`|`?e.scanner.lookahead===`>`?e.shiftedBy(1).pushRootToBranch(`|>`):e.pushRootToBranch(t):t===`&`?e.pushRootToBranch(t):t===`)`?e.finalizeGroup():UB(t,e.scanner.unscanned)?e.finalize(t):SP(t,lV)?cV(e,t):t===`%`?gV(e):t===`#`?hV(e):t in lF?vV(e):e.error(yV(t))},yV=(e,t=``)=>`'${e}' is not allowed here${t&&` (should be ${t})`}`,bV=`Missing expected ']'`,xV=e=>{let t=e.unsetRoot();e.parseOperand();let n=e.unsetRoot();return n.hasKind(`unit`)?[t,`=`,n.unit instanceof Date?()=>new Date(n.unit):n.unit]:e.error(SV(n.expression))},SV=e=>`Default value '${e}' must be a literal value`,CV=(e,t)=>{let n=t.$.maybeResolveRoot(e);if(n)return n;if(e.endsWith(`[]`)){let n=t.$.maybeResolveRoot(e.slice(0,-2));if(n)return n.array()}let r=new DV(new $F(e),t),i=wV(r);return r.finalizer===`>`&&Z(yV(`>`)),i},wV=e=>{e.parseOperand();let t=TV(e).root;return t?(e.finalizer===`=`?t=xV(e):e.finalizer===`?`&&(t=[t,`?`]),e.scanner.shiftUntilNonWhitespace(),e.scanner.lookahead&&Z(yV(e.scanner.lookahead)),t):gP(`Root was unexpectedly unset after parsing string '${e.scanner.scanned}'`)},TV=e=>{for(;e.finalizer===void 0;)EV(e);return e};var EV=e=>e.hasRoot()?e.parseOperator():e.parseOperand(),DV=class e{root;branches={prefixes:[],leftBound:null,intersection:null,union:null,pipe:null};finalizer;groups=[];scanner;ctx;constructor(e,t){this.scanner=e,this.ctx=t}error(e){return Z(e)}hasRoot(){return this.root!==void 0}setRoot(e){this.root=e}unsetRoot(){let e=this.root;return this.root=void 0,e}constrainRoot(...e){this.root=this.root.constrain(e[0],e[1])}finalize(e){if(this.groups.length)return this.error(tI(`)`));this.finalizeBranches(),this.finalizer=e}reduceLeftBound(e,t){let n=iV[t];if(!SP(n,nV))return this.error(oV(t));if(this.branches.leftBound)return this.error(sV(this.branches.leftBound.limit,this.branches.leftBound.comparator,e,n));this.branches.leftBound={comparator:n,limit:e}}finalizeBranches(){if(this.assertRangeUnset(),this.branches.pipe){this.pushRootToBranch(`|>`),this.root=this.branches.pipe;return}if(this.branches.union){this.pushRootToBranch(`|`),this.root=this.branches.union;return}if(this.branches.intersection){this.pushRootToBranch(`&`),this.root=this.branches.intersection;return}this.applyPrefixes()}finalizeGroup(){this.finalizeBranches();let e=this.groups.pop();if(!e)return this.error(eI(`)`,this.scanner.unscanned));this.branches=e}addPrefix(e){this.branches.prefixes.push(e)}applyPrefixes(){for(;this.branches.prefixes.length;){let e=this.branches.prefixes.pop();this.root=e===`keyof`?this.root.keyof():gP(`Unexpected prefix '${e}'`)}}pushRootToBranch(e){this.assertRangeUnset(),this.applyPrefixes();let t=this.root;this.root=void 0,this.branches.intersection=this.branches.intersection?.rawAnd(t)??t,e!==`&`&&(this.branches.union=this.branches.union?.rawOr(this.branches.intersection)??this.branches.intersection,this.branches.intersection=null,e!==`|`&&(this.branches.pipe=this.branches.pipe?.rawPipeOnce(this.branches.union)??this.branches.union,this.branches.union=null))}parseUntilFinalizer(){return TV(new e(this.scanner,this.ctx))}parseOperator(){return vV(this)}parseOperand(){return tV(this)}assertRangeUnset(){if(this.branches.leftBound)return this.error(aV(this.branches.leftBound.limit,this.branches.leftBound.comparator))}reduceGroupOpen(){this.groups.push(this.branches),this.branches={prefixes:[],leftBound:null,union:null,intersection:null,pipe:null}}previousOperator(){return this.branches.leftBound?.comparator??this.branches.prefixes[this.branches.prefixes.length-1]??(this.branches.intersection?`&`:this.branches.union?`|`:this.branches.pipe?`|>`:void 0)}shiftedBy(e){return this.scanner.jumpForward(e),this}};const OV=(e,t,n)=>{e.shiftUntilNonWhitespace();let r=e.shiftUntilLookahead(HB);return r===``?e.lookahead===``&&t.length?t:Z(`An empty string is not a valid generic parameter name`):(e.shiftUntilNonWhitespace(),AV(e,r,t,n))};var kV=`extends `,AV=(e,t,n,r)=>{if(e.shiftUntilNonWhitespace(),e.unscanned.startsWith(kV))e.jumpForward(8);else return e.lookahead===`,`&&e.shift(),n.push(t),OV(e,n,r);let i=TV(new DV(e,r));return n.push([t,i.root]),OV(e,n,r)},jV=class extends eF{constructor(e){let t={$:e,raw:e.fn};super((...t)=>{let n=t.indexOf(`:`),r=n===-1?t.length-1:n-1,i=t.slice(0,r+1),a=e.parse(i).assertHasKind(`intersection`),o=e.intrinsic.unknown;if(n!==-1){if(n!==t.length-2)return Z(NV);o=e.parse(t[n+1])}return e=>new MV(e,a,o)},{attach:t})}},MV=class extends eF{raw;params;returns;expression;constructor(e,t,n){let r=`typed ${e.name}`,i={[r]:(...r)=>{let i=e(...t.assert(r));return n.assert(i)}}[r];super(i),this.raw=e,this.params=t,this.returns=n;let a=t.expression;a[0]===`[`&&a[a.length-1]===`]`?a=a.slice(1,-1):a.endsWith(`[]`)&&(a=`...${a}`),this.expression=`(${a}) => ${n?.expression??`unknown`}`}};const NV=`":" must be followed by exactly one return type e.g: +fn("string", ":", "number")(s => s.length)`;var PV=class extends eF{$;constructor(e){super((...t)=>new FV(e)(...t),{bind:e}),this.$=e}in(e){return new FV(this.$,e===void 0?void 0:this.$.parse(e))}at(e,t){return new FV(this.$).at(e,t)}case(e,t){return new FV(this.$).case(e,t)}},FV=class extends eF{$;in;key;branches=[];constructor(e,t){super(e=>this.caseEntries(Object.entries(e).map(([e,t])=>e===`default`?[e,t]:[this.$.parse(e),t]))),this.$=e,this.in=t}at(e,t){return this.key&&Z(RV),this.branches.length&&Z(LV),this.key=e,t?this.match(t):this}case(e,t){return this.caseEntry(this.$.parse(e),t)}caseEntry(e,t){let n=(this.key?this.$.parse({[this.key]:e}):e).pipe(t);return this.branches.push(n),this}match(e){return this(e)}strings(e){return this.caseEntries(Object.entries(e).map(([e,t])=>e===`default`?[e,t]:[this.$.node(`unit`,{unit:e}),t]))}caseEntries(e){for(let t=0;te.throw(),LV=`A key matcher must be specified before the first case i.e. match.at('foo') or match.in().at('bar')`,RV=`At most one key matcher may be specified per expression`,zV=(e,t)=>{if(HP(e)){if(e[1]===`=`)return[t.$.parseOwnDefinitionFormat(e[0],t),`=`,e[2]];if(e[1]===`?`)return[t.$.parseOwnDefinitionFormat(e[0],t),`?`]}return bH(e,t)},BV=(e,t)=>{let n,r={},i=AP(e);for(let[e,a]of i){let i=WV(e);if(i.kind===`spread`){if(!kP(r))return Z(UV);let e=t.$.parseOwnDefinitionFormat(a,t);if(e.equals(xB.object))continue;if(!e.hasKind(`intersection`)||!e.basis?.equals(xB.object))return Z(GV(e.expression));n=e.structure;continue}if(i.kind===`undeclared`){a!==`reject`&&a!==`delete`&&a!==`ignore`&&Z(HV(a)),r.undeclared=a;continue}let o=zV(a,t),s=i;if(i.kind===`required`){HP(o)?VV(r,`optional`,o[1]===`=`?{key:i.normalized,value:o[0],default:o[2]}:{key:i.normalized,value:o[0]},t):VV(r,`required`,{key:i.normalized,value:o},t);continue}if(HP(o)&&(o[1]===`?`&&Z(`Only required keys may make their values optional, e.g. { [mySymbol]: ['number', '?'] }`),o[1]===`=`&&Z(`Only required keys may specify default values, e.g. { value: 'number = 0' }`)),i.kind===`optional`){VV(r,`optional`,{key:i.normalized,value:o},t);continue}let c=Bz(t.$.parseOwnDefinitionFormat(s.normalized,t),o,t.$);c.index&&(r.index=aP(r.index,c.index)),c.required&&(r.required=aP(r.required,c.required))}let a=t.$.node(`structure`,r);return t.$.parseSchema({domain:`object`,structure:n?.merge(a)??a})};var VV=(e,t,n,r)=>{e[t]=aP(e[t],r.$.node(t,n))};const HV=e=>`Value of '+' key must be 'reject', 'delete', or 'ignore' (was ${HF(e)})`,UV=`Spread operator may only be used as the first key in an object`,WV=e=>typeof e==`symbol`?{kind:`required`,normalized:e}:e[e.length-1]===`?`?e[e.length-2]===`\\`?{kind:`required`,normalized:`${e.slice(0,-2)}?`}:{kind:`optional`,normalized:e.slice(0,-1)}:e[0]===`[`&&e[e.length-1]===`]`?{kind:`index`,normalized:e.slice(1,-1)}:e[0]===`\\`&&e[1]===`[`&&e[e.length-1]===`]`?{kind:`required`,normalized:e.slice(1)}:e===`...`?{kind:`spread`}:e===`+`?{kind:`undeclared`}:{kind:`required`,normalized:e===`\\...`?`...`:e===`\\+`?`+`:e},GV=e=>`Spread operand must resolve to an object literal type (was ${e})`,KV=(e,t)=>oH(e)?aH[e[0]](e,t):iH(e)?rH[e[1]](e,t):null,qV=(e,t)=>t.$.parseOwnDefinitionFormat(e[1],t).keyof();var JV=(e,t)=>{if(e[2]===void 0)return Z($B(e[1],``));let n=t.$.parseOwnDefinitionFormat(e[0],t),r=t.$.parseOwnDefinitionFormat(e[2],t);if(e[1]===`|`)return t.$.node(`union`,{branches:[n,r]});let i=e[1]===`&`?oL(n,r,t.$):sL(n,r,t.$);return i instanceof $?i.throw():i},YV=(e,t)=>t.$.parseOwnDefinitionFormat(e[0],t).array();const XV=(e,t)=>typeof e[2]==`function`?t.$.parseOwnDefinitionFormat(e[0],t).pipe(e[2]):Z(ZV(`=>`,e[2])),ZV=(e,t)=>`${e===`:`?`Narrow`:`Morph`} expression requires a function following '${e}' (was ${typeof t})`,QV=(e,t)=>typeof e[2]==`function`?t.$.parseOwnDefinitionFormat(e[0],t).constrain(`predicate`,e[2]):Z(ZV(`:`,e[2]));var $V=(e,t)=>t.$.parseOwnDefinitionFormat(e[0],t).configure(e[2],e[3]),eH=e=>e,tH=eH({"[]":YV,"?":()=>Z(BB)}),nH=eH({"|":JV,"&":JV,":":QV,"=>":XV,"|>":JV,"@":$V,"=":()=>Z(VB)}),rH={...tH,...nH},iH=e=>rH[e[1]]!==void 0,aH=(e=>e)({keyof:qV,instanceof:(e,t)=>{if(typeof e[1]!=`function`)return Z(sH(VP(e[1])));let n=e.slice(1).map(e=>typeof e==`function`?t.$.node(`proto`,{proto:e}):Z(sH(VP(e))));return n.length===1?n[0]:t.$.node(`union`,{branches:n})},"===":(e,t)=>t.$.units(e.slice(1))}),oH=e=>aH[e[0]]!==void 0;const sH=e=>`Expected a constructor following 'instanceof' operator (was ${e})`,cH=(e,t)=>{let n=[{}],r=0;for(;ro.distribute(t=>pH(pI(e),t)))}else n=n.map(e=>s===`?`?uH(e,o):s===`=`?dH(e,o,c):lH(e,o))}return t.$.parseSchema(n.map(e=>kP(e)?{proto:Array,exactLength:0}:{proto:Array,sequence:e}))};var lH=(e,t)=>e.defaultables||e.optionals?Z(e.variadic?Cz:gH):(e.variadic?e.postfix=aP(e.postfix,t):e.prefix=aP(e.prefix,t),e),uH=(e,t)=>e.variadic?Z(_H):(e.optionals=aP(e.optionals,t),e),dH=(e,t,n)=>e.variadic?Z(_H):e.optionals?Z(vH):(e.defaultables=aP(e.defaultables,[[t,n]]),e),fH=(e,t)=>(e.postfix&&Z(hH),e.variadic?e.variadic.equals(t)||Z(hH):e.variadic=t.internal,e),pH=(e,t)=>{let n=t.select({method:`find`,kind:`sequence`});if(!n)return fH(e,Q.intrinsic.unknown);if(n.prefix)for(let t of n.prefix)lH(e,t);if(n.optionals)for(let t of n.optionals)uH(e,t);if(n.variadic&&fH(e,n.variadic),n.postfix)for(let t of n.postfix)lH(e,t);return e};const mH=e=>`Spread element must be an array (was ${e})`,hH=`A tuple may have at most one variadic element`,gH=`A required element may not follow an optional element`,_H=`An optional element may not follow a variadic element`,vH=`A defaultable element may not follow an optional element without a default`;var yH={};const bH=(e,t)=>{if(typeof e==`string`){if(t.args&&Object.keys(t.args).some(t=>e.includes(t)))return CV(e,t);let n=yH[t.$.name]??={};return n[e]??=CV(e,t)}return dP(e,`object`)?xH(e,t):Z(wH(fP(e)))},xH=(e,t)=>{let n=BP(e);switch(n){case void 0:return hI(e,`root`)?e:`~standard`in e?SH(e,t):BV(e,t);case`Array`:return CH(e,t);case`RegExp`:return t.$.node(`intersection`,{domain:`string`,pattern:e},{prereduced:!0});case`Function`:{let t=QP(e)?e():e;return hI(t,`root`)?t:Z(wH(`Function`))}default:return Z(wH(n??HF(e)))}};var SH=(e,t)=>t.$.intrinsic.unknown.pipe((t,n)=>{let r=e[`~standard`].validate(t);if(!r.issues)return r.value;for(let{message:e,path:t}of r.issues)t&&t.length?n.error({problem:aF(e),relativePath:t.map(e=>typeof e==`object`?e.key:e)}):n.error({message:e})});const CH=(e,t)=>KV(e,t)??cH(e,t),wH=e=>`Type definitions must be strings or objects (was ${e})`;var TH=class extends eF{constructor(e){let t=Object.assign({errors:VI,hkt:nF,$:e,raw:e.parse,module:e.constructor.module,scope:e.constructor.scope,declare:e.declare,define:e.define,match:e.match,generic:e.generic,schema:e.schema,keywords:e.ambient,unit:e.unit,enumerated:e.enumerated,instanceOf:e.instanceOf,valueOf:e.valueOf,or:e.or,and:e.and,merge:e.merge,pipe:e.pipe,fn:e.fn},e.ambientAttachments);super((...t)=>{if(t.length===1)return e.parse(t[0]);if(t.length===2&&typeof t[0]==`string`&&t[0][0]===`<`&&t[0][t[0].length-1]===`>`){let n=t[0].slice(1,-1);return new CL(e.parseGenericParams(n,{}),t[1],e,e,null)}return e.parse(t)},{attach:t})}};const EH=Q;var DH=class e extends rB{get ambientAttachments(){if(EH.typeAttachments)return this.cacheGetter(`ambientAttachments`,bP(EH.typeAttachments,(e,t)=>[e,this.bindReference(t)]))}preparseOwnAliasEntry(e,t){let n=e.indexOf(`<`);if(n===-1){if(hI(t,`module`)||hI(t,`generic`))return[e,t];let n=this.name===`ark`?e:e===`root`?this.name:`${this.name}.${e}`,r=this.resolvedConfig.keywords?.[n];return r&&(t=[t,`@`,r]),[e,t]}e[e.length-1]!==`>`&&Z(`'>' must be the last character of a generic declaration in a scope`);let r=e.slice(0,n),i=e.slice(n+1,-1);return[r,()=>xL(this.parseGenericParams(i,{alias:r}),t,this)]}parseGenericParams(e,t){return OV(new $F(e),[],this.createParseContext({...t,def:e,prefix:`generic`}))}normalizeRootScopeValue(e){return QP(e)&&!hI(e,`generic`)?e():e}preparseOwnDefinitionFormat(e,t){return{...t,def:e,prefix:t.alias??`type`}}parseOwnDefinitionFormat(e,t){!(t.alias&&t.alias in this.aliases)&&!t.args&&(t.args={this:t.id});let n=bH(e,t);if(HP(n)){if(n[1]===`=`)return Z(VB);if(n[1]===`?`)return Z(BB)}return n}unit=e=>this.units([e]);valueOf=e=>this.units(PP(e));enumerated=(...e)=>this.units(e);instanceOf=e=>this.node(`proto`,{proto:e},{prereduced:!0});or=(...e)=>this.schema(e.map(e=>this.parse(e)));and=(...e)=>e.reduce((e,t)=>e.and(this.parse(t)),this.intrinsic.unknown);merge=(...e)=>e.reduce((e,t)=>e.merge(this.parse(t)),this.intrinsic.object);pipe=(...e)=>this.intrinsic.unknown.pipe(...e);fn=new jV(this);match=new PV(this);declare=()=>({type:this.type});define(e){return e}type=new TH(this);static scope=((t,n={})=>new e(t,n));static module=((e,t={})=>this.scope(e,t).export())};const OH=Object.assign(DH.scope,{define:e=>e}),kH=DH;var AH=class extends nF{description='merge an object\'s properties onto another like `Merge(User, { isAdmin: "true" })`'},jH=gB([`base`,xB.object],[`props`,xB.object])(e=>e.base.merge(e.props),AH);const MH=kH.module({Key:xB.key,Merge:jH});var NH=class extends nF{},PH=gB(`element`)(e=>{let t=e.element.exclude(xB.Array),n=t.array();return t.rawOr(n).pipe(eP).distribute(e=>e.assertHasKind(`morph`).declareOut(n),mB)},NH);const FH=kH.module({root:xB.Array,readonly:`root`,index:xB.nonNegativeIntegerString,liftFrom:PH},{name:`Array`});var IH=mB([`string`,PF.FileConstructor]),LH=mB({meta:`an object representing parsed form data`,domain:`object`,index:{signature:`string`,value:IH.rawOr(IH.array())}});const RH=kH.module({root:[`instanceof`,FormData],value:IH,parsed:LH,parse:mB({in:FormData,morphs:e=>{let t={};for(let[n,r]of e)if(n in t){let e=t[n];typeof e==`string`||e instanceof PF.FileConstructor?t[n]=[e,r]:e.push(r)}else t[n]=r;return t},declaredOut:LH})},{name:`FormData`}),zH=kH.module({Int8:[`instanceof`,Int8Array],Uint8:[`instanceof`,Uint8Array],Uint8Clamped:[`instanceof`,Uint8ClampedArray],Int16:[`instanceof`,Int16Array],Uint16:[`instanceof`,Uint16Array],Int32:[`instanceof`,Int32Array],Uint32:[`instanceof`,Uint32Array],Float32:[`instanceof`,Float32Array],Float64:[`instanceof`,Float64Array],BigInt64:[`instanceof`,BigInt64Array],BigUint64:[`instanceof`,BigUint64Array]},{name:`TypedArray`});var BH={Boolean:1,Number:1,String:1};const VH=kH.module({...bP({...FP,...LP},(e,t)=>e in BH?[]:[e,[`instanceof`,t]]),Array:FH,TypedArray:zH,FormData:RH}),HH=mB({domain:{domain:`number`,meta:`a number representing a Unix timestamp`},divisor:{rule:1,meta:`an integer representing a Unix timestamp`},min:{rule:-864e13,meta:`a Unix timestamp after -8640000000000000`},max:{rule:864e13,meta:`a Unix timestamp before 8640000000000000`},meta:`an integer representing a safe Unix timestamp`}),UH=mB({domain:`number`,divisor:1}),WH=kH.module({root:xB.number,integer:UH,epoch:HH,safe:mB({domain:{domain:`number`,numberAllowsNaN:!1},min:-(2**53-1),max:2**53-1}),NaN:[`===`,NaN],Infinity:[`===`,1/0],NegativeInfinity:[`===`,-1/0]},{name:`number`}),GH=(e,t,n)=>{let r={domain:`string`,pattern:{rule:e.source,flags:e.flags,meta:t}};return n&&(r.meta={format:n}),hB(`intersection`,r)};var KH=GH(bF,`a well-formed integer string`);const qH=kH.module({root:KH,parse:mB({in:KH,morphs:(e,t)=>{let n=Number.parseInt(e);return Number.isSafeInteger(n)?n:t.error(`an integer in the range Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER`)},declaredOut:xB.integer})},{name:`string.integer`});var JH=GH(/^[\dA-Fa-f]+$/,`hex characters only`),YH=kH.module({root:GH(/^(?:[\d+/A-Za-z]{4})*(?:[\d+/A-Za-z]{2}==|[\d+/A-Za-z]{3}=)?$/,`base64-encoded`),url:GH(/^(?:[\w-]{4})*(?:[\w-]{2}(?:==|%3D%3D)?|[\w-]{3}(?:=|%3D)?)?$/,`base64url-encoded`)},{name:`string.base64`}),XH=GH(/^[A-Z].*$/,`capitalized`);const ZH=kH.module({root:mB({in:`string`,morphs:e=>e.charAt(0).toUpperCase()+e.slice(1),declaredOut:XH}),preformatted:XH},{name:`string.capitalize`}),QH=mB({domain:`string`,pattern:{meta:`a credit card number`,rule:`^(?:4\\d{12}(?:\\d{3,6})?|5[1-5]\\d{14}|(222[1-9]|22[3-9]\\d|2[3-6]\\d{2}|27[01]\\d|2720)\\d{12}|6(?:011|5\\d\\d)\\d{12,15}|3[47]\\d{13}|3(?:0[0-5]|[68]\\d)\\d{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27]\\d{14}|^(81\\d{14,17}))$`},predicate:{meta:`a credit card number`,predicate:e=>{let t=e.replace(/[ -]+/g,``),n=0,r,i,a=!1;for(let e=t.length-1;e>=0;e--)r=t.substring(e,e+1),i=Number.parseInt(r,10),a?(i*=2,n+=i>=10?i%10+1:i):n+=i,a=!a;return!!(n%10==0&&t)}}}),$H=/^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;var eU=mB({domain:`string`,predicate:{meta:`a parsable date`,predicate:e=>!Number.isNaN(new Date(e).valueOf())}}).assertHasKind(`intersection`),tU=qH.root.internal.narrow((e,t)=>{let n=Number.parseInt(e),r=WH.epoch(n);return r instanceof VI?(t.errors.merge(r),!1):!0}).configure({description:`an integer string representing a safe Unix timestamp`},`self`).assertHasKind(`intersection`),nU=kH.module({root:tU,parse:mB({in:tU,morphs:e=>new Date(e),declaredOut:xB.Date})},{name:`string.date.epoch`}),rU=GH($H,`an ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ) date`).internal.assertHasKind(`intersection`),iU=kH.module({root:rU,parse:mB({in:rU,morphs:e=>new Date(e),declaredOut:xB.Date})},{name:`string.date.iso`});const aU=kH.module({root:eU,parse:mB({declaredIn:eU,in:`string`,morphs:(e,t)=>{let n=new Date(e);return Number.isNaN(n.valueOf())?t.error(`a parsable date`):n},declaredOut:xB.Date}),iso:iU,epoch:nU},{name:`string.date`});var oU=GH(/^[\w%+.-]+@[\d.A-Za-z-]+\.[A-Za-z]{2,}$/,`an email address`,`email`),sU=`(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])`,cU=`(${sU}[.]){3}${sU}`,lU=RegExp(`^${cU}$`),uU=`(?:[0-9a-fA-F]{1,4})`,dU=RegExp(`^((?:${uU}:){7}(?:${uU}|:)|(?:${uU}:){6}(?:${cU}|:${uU}|:)|(?:${uU}:){5}(?::${cU}|(:${uU}){1,2}|:)|(?:${uU}:){4}(?:(:${uU}){0,1}:${cU}|(:${uU}){1,3}|:)|(?:${uU}:){3}(?:(:${uU}){0,2}:${cU}|(:${uU}){1,4}|:)|(?:${uU}:){2}(?:(:${uU}){0,3}:${cU}|(:${uU}){1,5}|:)|(?:${uU}:){1}(?:(:${uU}){0,4}:${cU}|(:${uU}){1,6}|:)|(?::((?::${uU}){0,5}:${cU}|(?::${uU}){1,7}|:)))(%[0-9a-zA-Z.]{1,})?\$`);const fU=kH.module({root:[`v4 | v6`,`@`,`an IP address`],v4:GH(lU,`an IPv4 address`,`ipv4`),v6:GH(dU,`an IPv6 address`,`ipv6`)},{name:`string.ip`});var pU=`a JSON string`;const mU=e=>{if(!(e instanceof SyntaxError))throw e;return`must be ${pU} (${e})`};var hU=mB({meta:pU,domain:`string`,predicate:{meta:pU,predicate:(e,t)=>{try{return JSON.parse(e),!0}catch(e){return t.reject({code:`predicate`,expected:pU,problem:mU(e)})}}}});const gU=kH.module({root:hU,parse:mB({meta:`safe JSON string parser`,in:`string`,morphs:(e,t)=>{if(e.length===0)return t.error({code:`predicate`,expected:pU,actual:`empty`});try{return JSON.parse(e)}catch(e){return t.error({code:`predicate`,expected:pU,problem:mU(e)})}},declaredOut:xB.jsonObject})},{name:`string.json`});var _U=GH(/^[a-z]*$/,`only lowercase letters`),vU=kH.module({root:mB({in:`string`,morphs:e=>e.toLowerCase(),declaredOut:_U}),preformatted:_U},{name:`string.lower`});const yU=[`NFC`,`NFD`,`NFKC`,`NFKD`];var bU=bP(yU,(e,t)=>[t,mB({domain:`string`,predicate:e=>e.normalize(t)===e,meta:`${t}-normalized unicode`})]),xU=bP(yU,(e,t)=>[t,mB({in:`string`,morphs:e=>e.normalize(t),declaredOut:bU[t]})]);const SU=kH.module({root:xU.NFC,preformatted:bU.NFC},{name:`string.normalize.NFC`}),CU=kH.module({root:xU.NFD,preformatted:bU.NFD},{name:`string.normalize.NFD`}),wU=kH.module({root:xU.NFKC,preformatted:bU.NFKC},{name:`string.normalize.NFKC`}),TU=kH.module({root:xU.NFKD,preformatted:bU.NFKD},{name:`string.normalize.NFKD`}),EU=kH.module({root:`NFC`,NFC:SU,NFD:CU,NFKC:wU,NFKD:TU},{name:`string.normalize`});var DU=GH(_F,`a well-formed numeric string`);const OU=kH.module({root:DU,parse:mB({in:DU,morphs:e=>Number.parseFloat(e),declaredOut:xB.number})},{name:`string.numeric`});var kU=`a regex pattern`,AU=mB({domain:`string`,predicate:{meta:kU,predicate:(e,t)=>{try{return new RegExp(e),!0}catch(e){return t.reject({code:`predicate`,expected:kU,problem:String(e)})}}},meta:{format:`regex`}}),jU=GH(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*))*))?(?:\+([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?$/,`a semantic version (see https://semver.org/)`),MU=GH(/^\S.*\S$|^\S?$/,`trimmed`),NU=kH.module({root:mB({in:`string`,morphs:e=>e.trim(),declaredOut:MU}),preformatted:MU},{name:`string.trim`}),PU=GH(/^[A-Z]*$/,`only uppercase letters`),FU=kH.module({root:mB({in:`string`,morphs:e=>e.toUpperCase(),declaredOut:PU}),preformatted:PU},{name:`string.upper`}),IU=mB({domain:`string`,predicate:{meta:`a URL string`,predicate:e=>URL.canParse(e)},meta:{format:`uri`}});const LU=kH.module({root:IU,parse:mB({declaredIn:IU,in:`string`,morphs:(e,t)=>{try{return new URL(e)}catch{return t.error(`a URL string`)}},declaredOut:mB(URL)})},{name:`string.url`}),RU=kH.module({root:[`versioned | nil | max`,`@`,{description:`a UUID`,format:`uuid`}],"#nil":`'00000000-0000-0000-0000-000000000000'`,"#max":`'ffffffff-ffff-ffff-ffff-ffffffffffff'`,"#versioned":/[\da-f]{8}-[\da-f]{4}-[1-8][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}/i,v1:GH(/^[\da-f]{8}-[\da-f]{4}-1[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv1`),v2:GH(/^[\da-f]{8}-[\da-f]{4}-2[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv2`),v3:GH(/^[\da-f]{8}-[\da-f]{4}-3[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv3`),v4:GH(/^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv4`),v5:GH(/^[\da-f]{8}-[\da-f]{4}-5[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv5`),v6:GH(/^[\da-f]{8}-[\da-f]{4}-6[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv6`),v7:GH(/^[\da-f]{8}-[\da-f]{4}-7[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv7`),v8:GH(/^[\da-f]{8}-[\da-f]{4}-8[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/i,`a UUIDv8`)},{name:`string.uuid`}),zU=kH.module({root:xB.string,alpha:GH(/^[A-Za-z]*$/,`only letters`),alphanumeric:GH(/^[\dA-Za-z]*$/,`only letters and digits 0-9`),hex:JH,base64:YH,capitalize:ZH,creditCard:QH,date:aU,digits:GH(/^\d*$/,`only digits 0-9`),email:oU,integer:qH,ip:fU,json:gU,lower:vU,normalize:EU,numeric:OU,regex:AU,semver:jU,trim:NU,upper:FU,url:LU,uuid:RU},{name:`string`}),BU=kH.module({bigint:xB.bigint,boolean:xB.boolean,false:xB.false,never:xB.never,null:xB.null,number:xB.number,object:xB.object,string:xB.string,symbol:xB.symbol,true:xB.true,unknown:xB.unknown,undefined:xB.undefined}),VU=kH.module({root:xB.unknown,any:xB.unknown},{name:`unknown`}),HU=kH.module({root:xB.jsonObject,stringify:hB(`morph`,{in:xB.jsonObject,morphs:e=>JSON.stringify(e),declaredOut:xB.string})},{name:`object.json`}),UU=kH.module({root:xB.object,json:HU},{name:`object`});var WU=class extends nF{description='instantiate an object from an index signature and corresponding value type like `Record("string", "number")`'},GU=gB([`K`,xB.key],`V`)(e=>({domain:`object`,index:{signature:e.K,value:e.V}}),WU),KU=class extends nF{description='pick a set of properties from an object like `Pick(User, "name | age")`'},qU=gB([`T`,xB.object],[`K`,xB.key])(e=>e.T.pick(e.K),KU),JU=class extends nF{description='omit a set of properties from an object like `Omit(User, "age")`'},YU=gB([`T`,xB.object],[`K`,xB.key])(e=>e.T.omit(e.K),JU),XU=class extends nF{description="make all named properties of an object optional like `Partial(User)`"},ZU=gB([`T`,xB.object])(e=>e.T.partial(),XU),QU=class extends nF{description="make all named properties of an object required like `Required(User)`"},$U=gB([`T`,xB.object])(e=>e.T.required(),QU),eW=class extends nF{description='exclude branches of a union like `Exclude("boolean", "true")`'},tW=gB(`T`,`U`)(e=>e.T.exclude(e.U),eW),nW=class extends nF{description='extract branches of a union like `Extract("0 | false | 1", "number")`'},rW=gB(`T`,`U`)(e=>e.T.extract(e.U),nW);const iW=kH.module({Exclude:tW,Extract:rW,Omit:YU,Partial:ZU,Pick:qU,Record:GU,Required:$U}),aW=OH({...BU,...iW,...VH,...MH,string:zU,number:WH,object:UU,unknown:VU},{prereducedAliases:!0,name:`ark`}),oW=aW.export();Object.assign(EH.ambient,oW),EH.typeAttachments={string:oW.string.root,number:oW.number.root,bigint:oW.bigint,boolean:oW.boolean,symbol:oW.symbol,undefined:oW.undefined,null:oW.null,object:oW.object.root,unknown:oW.unknown.root,false:oW.false,true:oW.true,never:oW.never,arrayIndex:oW.Array.index,Key:oW.Key,Record:oW.Record,Array:oW.Array.root,Date:oW.Date};const sW=Object.assign(aW.type,EH.typeAttachments);aW.match,aW.fn,aW.generic,aW.schema,aW.define,aW.declare;var cW={slots:{overlay:`fixed inset-0`,content:`bg-default divide-y divide-default flex flex-col focus:outline-none`,header:`flex items-center gap-1.5 p-4 sm:px-6 min-h-16`,wrapper:``,body:`flex-1 p-4 sm:p-6`,footer:`flex items-center gap-1.5 p-4 sm:px-6`,title:`text-highlighted font-semibold`,description:`mt-1 text-muted text-sm`,close:`absolute top-4 end-4`},variants:{transition:{true:{overlay:`data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]`,content:`data-[state=open]:animate-[scale-in_200ms_ease-out] data-[state=closed]:animate-[scale-out_200ms_ease-in]`}},fullscreen:{true:{content:`inset-0`},false:{content:`w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default`}},overlay:{true:{overlay:`bg-elevated/75`}},scrollable:{true:{overlay:`overflow-y-auto`,content:`relative`},false:{content:`fixed`,body:`overflow-y-auto`}}},compoundVariants:[{scrollable:!0,fullscreen:!1,class:{overlay:`grid place-items-center p-4 sm:py-8`}},{scrollable:!1,fullscreen:!1,class:{content:`top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden`}}]},lW={__name:`Modal`,props:{title:{type:String,required:!1},description:{type:String,required:!1},content:{type:Object,required:!1},overlay:{type:Boolean,required:!1,default:!0},scrollable:{type:Boolean,required:!1},transition:{type:Boolean,required:!1,default:!0},fullscreen:{type:Boolean,required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:!0},close:{type:[Boolean,Object],required:!1,default:!0},closeIcon:{type:null,required:!1},dismissible:{type:Boolean,required:!1,default:!0},class:{type:null,required:!1},ui:{type:null,required:!1},open:{type:Boolean,required:!1},defaultOpen:{type:Boolean,required:!1},modal:{type:Boolean,required:!1,default:!0}},emits:[`after:leave`,`after:enter`,`close:prevent`,`update:open`],setup(e,{emit:t}){let n=e,r=t,i=lo(),{t:a}=$f(),o=wf(),s=bg(jd(n,`open`,`defaultOpen`,`modal`),r),c=RS(lr(()=>n.portal)),l=lr(()=>n.content),u=W(()=>n.dismissible?n.scrollable?{pointerDownOutside:e=>{let t=e.detail.originalEvent,n=t.target;(t.offsetX>n.clientWidth||t.offsetY>n.clientHeight)&&e.preventDefault()}}:{}:[`pointerDownOutside`,`interactOutside`,`escapeKeyDown`].reduce((e,t)=>(e[t]=e=>{e.preventDefault(),r(`close:prevent`)},e),{})),[d,f]=zd(),p=W(()=>Mw({extend:Mw(cW),...o.ui?.modal||{}})({transition:n.transition,fullscreen:n.fullscreen,overlay:n.overlay,scrollable:n.scrollable}));return(t,m)=>(L(),z(M(a_),it(ec(M(s))),{default:N(({open:s,close:h})=>[V(M(d),null,{default:N(()=>[V(M(R_),U({"data-slot":`content`,class:p.value.content({class:[!i.default&&n.class,n.ui?.content]})},l.value,{onAfterEnter:m[0]||=e=>r(`after:enter`),onAfterLeave:m[1]||=e=>r(`after:leave`)},Xa(u.value)),{default:N(()=>[i.content&&(e.title||i.title||e.description||i.description)?(L(),z(M(Z_),{key:0},{default:N(()=>[e.title||i.title?(L(),z(M(W_),{key:0},{default:N(()=>[F(t.$slots,`title`,{},()=>[nc(ft(e.title),1)])]),_:3})):H(``,!0),e.description||i.description?(L(),z(M(z_),{key:1},{default:N(()=>[F(t.$slots,`description`,{},()=>[nc(ft(e.description),1)])]),_:3})):H(``,!0)]),_:3})):H(``,!0),F(t.$slots,`content`,{close:h},()=>[i.header||e.title||i.title||e.description||i.description||n.close||i.close?(L(),R(`div`,{key:0,"data-slot":`header`,class:A(p.value.header({class:n.ui?.header}))},[F(t.$slots,`header`,{close:h},()=>[B(`div`,{"data-slot":`wrapper`,class:A(p.value.wrapper({class:n.ui?.wrapper}))},[e.title||i.title?(L(),z(M(W_),{key:0,"data-slot":`title`,class:A(p.value.title({class:n.ui?.title}))},{default:N(()=>[F(t.$slots,`title`,{},()=>[nc(ft(e.title),1)])]),_:3},8,[`class`])):H(``,!0),e.description||i.description?(L(),z(M(z_),{key:1,"data-slot":`description`,class:A(p.value.description({class:n.ui?.description}))},{default:N(()=>[F(t.$slots,`description`,{},()=>[nc(ft(e.description),1)])]),_:3},8,[`class`])):H(``,!0)],2),F(t.$slots,`actions`),n.close||i.close?(L(),z(M(o_),{key:0,"as-child":``},{default:N(()=>[F(t.$slots,`close`,{ui:p.value},()=>[n.close?(L(),z(YE,U({key:0,icon:e.closeIcon||M(o).ui.icons.close,color:`neutral`,variant:`ghost`,"aria-label":M(a)(`modal.close`)},typeof n.close==`object`?n.close:{},{"data-slot":`close`,class:p.value.close({class:n.ui?.close})}),null,16,[`icon`,`aria-label`,`class`])):H(``,!0)])]),_:2},1024)):H(``,!0)])],2)):H(``,!0),i.body?(L(),R(`div`,{key:1,"data-slot":`body`,class:A(p.value.body({class:n.ui?.body}))},[F(t.$slots,`body`,{close:h})],2)):H(``,!0),i.footer?(L(),R(`div`,{key:2,"data-slot":`footer`,class:A(p.value.footer({class:n.ui?.footer}))},[F(t.$slots,`footer`,{close:h})],2)):H(``,!0)])]),_:2},1040,[`class`])]),_:2},1024),i.default?(L(),z(M(G_),{key:0,"as-child":``,class:A(n.class)},{default:N(()=>[F(t.$slots,`default`,{open:s})]),_:2},1032,[`class`])):H(``,!0),V(M(U_),it(ec(M(c))),{default:N(()=>[e.scrollable?(L(),z(M(V_),{key:0,"data-slot":`overlay`,class:A(p.value.overlay({class:n.ui?.overlay}))},{default:N(()=>[V(M(f))]),_:1},8,[`class`])):(L(),R(I,{key:1},[e.overlay?(L(),z(M(V_),{key:0,"data-slot":`overlay`,class:A(p.value.overlay({class:n.ui?.overlay}))},null,8,[`class`])):H(``,!0),V(M(f))],64))]),_:1},16)]),_:3},16))}};function uW(e){return`schema`in e&&typeof e.coercer==`function`&&typeof e.validator==`function`&&typeof e.refiner==`function`}function dW(e){return`~standard`in e}async function fW(e,t){let n=await t[`~standard`].validate(e);return n.issues?{errors:n.issues?.map(e=>({name:e.path?.map(e=>typeof e==`object`?e.key:e).join(`.`)||``,message:e.message}))||[],result:null}:{errors:null,result:n.value}}async function pW(e,t){let[n,r]=t.validate(e);return n?{errors:n.failures().map(e=>({message:e.message,name:e.path.join(`.`)})),result:null}:{errors:null,result:r}}function mW(e,t){if(dW(t))return fW(e,t);if(uW(t))return pW(e,t);throw Error(`Form validation failed: Unsupported form schema`)}function hW(e,t){return t?t.split(`.`).reduce((e,t)=>e?.[t],e):e}function gW(e,t,n){if(!t)return Object.assign(e,n);if(!e)return e;let r=t.split(`.`),i=e;for(let e=0;e!0},nested:{type:Boolean,required:!1},loadingAuto:{type:Boolean,required:!1,default:!0},class:{type:null,required:!1},onSubmit:{type:Function,required:!1}},emits:[`submit`,`error`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=wf(),o=W(()=>Mw({extend:Mw(vW),...a.ui?.form||{}})),s=r.id??Gi(),c=gf(`form-${s}`),l=r.nested===!0&&oi(AE,void 0),u=r.nested===!0?oi(jE,void 0):void 0,d=W(()=>u?.value?r.name?hW(u.value,r.name):u.value:r.state);ai(AE,c),ai(jE,d);let f=j(new Map);Aa(async()=>{l&&(await zr(),l.emit({type:`attach`,validate:x,formId:s,name:r.name,api:ce}))}),Pa(()=>{c.reset(),l&&l.emit({type:`detach`,formId:s})}),Aa(async()=>{c.on(async e=>{e.type===`attach`?f.value.set(e.formId,{validate:e.validate,name:e.name,api:e.api}):e.type===`detach`?f.value.delete(e.formId):r.validateOn?.includes(e.type)&&!ee.value&&(e.type===`input`?(e.eager||_.has(e.name))&&await x({name:e.name,silent:!0,nested:!1}):await x({name:e.name,silent:!0,nested:!1})),e.type===`blur`&&_.add(e.name),(e.type===`change`||e.type===`input`||e.type===`blur`||e.type===`focus`)&&g.add(e.name),(e.type===`change`||e.type===`input`)&&h.add(e.name)})});let p=j([]);ai(IE,p);let m=j({});ai(PE,m);let h=Ln(new Set),g=Ln(new Set),_=Ln(new Set);function v(e){return e.map(e=>({...e,id:e?.name?m.value[e.name]?.id:void 0}))}let y=j(null);async function b(){let e=r.validate?await r.validate(d.value)??[]:[];if(r.schema){let{errors:t,result:n}=await mW(d.value,r.schema);t?e=e.concat(t):y.value=n}return v(e)}async function x(e={silent:!1,nested:!1,transform:!1}){let t=e.name&&!Array.isArray(e.name)?[e.name]:e.name,n=[],r=[];if(!t&&e.nested){let t=Array.from(f.value.values()).map(t=>w(t,e)),i=await Promise.all(t);r=i.filter(e=>e.error).flatMap(e=>e.error.errors.map(t=>te(t,e.name))),n=i.filter(e=>e.output!==void 0)}let i=[...await b(),...r];if(t?p.value=oe(i,t):p.value=i,p.value?.length){if(e.silent)return!1;throw new _W(s,p.value)}return e.transform?(n.forEach(e=>{e.name?gW(y.value,e.name,e.output):Object.assign(y.value,e.output)}),y.value??d.value):d.value}let ee=j(!1);ai(FE,zn(ee));async function S(e){ee.value=r.loadingAuto&&!0;let t=e;try{t.data=await x({nested:!0,transform:r.transform}),await r.onSubmit?.(t),h.clear()}catch(e){if(!(e instanceof _W))throw e;i(`error`,{...t,errors:e.errors})}finally{ee.value=!1}}let C=W(()=>r.disabled||ee.value);ai(kE,W(()=>({disabled:C.value,validateOnInputDelay:r.validateOnInputDelay})));async function w(e,t){try{let n=await e.validate({...t,silent:!1});return{name:e.name,output:n}}catch(t){if(!(t instanceof _W))throw t;return{name:e.name,error:t}}}function te(e,t){return!t||!e.name?e:{...e,name:t+`.`+e.name}}function ne(e,t){let n=t+`.`,r=e?.name?.startsWith(n)?e.name.substring(n.length):e.name;return{...e,name:r}}function re(e,t){return t?e.filter(e=>e?.name?.startsWith(t+`.`)).map(e=>ne(e,t)):e}function T(e){return e.api.getErrors().map(t=>e.name?{...t,name:e.name+`.`+t.name}:t)}function ie(e,t){return!e||!t?!0:e instanceof RegExp?e.test(t):t===e||typeof e==`string`&&e.startsWith(t+`.`)}function ae(e,t){if(!e||e instanceof RegExp)return e;if(t!==e)return typeof e==`string`&&e.startsWith(t+`.`)?e.substring(t.length+1):e}function oe(e,t){let n=new Set(t),r=t.map(e=>m.value?.[e]?.pattern).filter(Boolean),i=e=>e.name?n.has(e.name)?!0:r.some(t=>t.test(e.name)):!1,a=p.value.filter(e=>!i(e)),o=e.filter(i);return[...a,...o]}function se(e,t){return e.filter(e=>t instanceof RegExp?!(e.name&&t.test(e.name)):!e.name||e.name!==t)}function E(e){return!e.name||!!m.value[e.name]}let ce={validate:x,errors:p,setErrors(e,t){let n=v(e.filter(E)),r=[];for(let n of f.value.values())if(ie(t,n.name)){let i=re(e,n.name);n.api.setErrors(i,ae(t,n.name||``)),r.push(...T(n))}t?p.value=[...se(p.value,t),...n,...r]:p.value=[...n,...r]},async submit(){await S(new Event(`submit`))},getErrors(e){return e?p.value.filter(t=>e instanceof RegExp?t.name&&e.test(t.name):t.name===e):p.value},clear(e){let t=e?p.value.filter(t=>E(t)&&(e instanceof RegExp?!(t.name&&e.test(t.name)):t.name!==e)):[],n=[];for(let t of f.value.values())ie(e,t.name)&&t.api.clear(),n.push(...T(t));p.value=[...t,...n]},disabled:C,loading:ee,dirty:W(()=>!!h.size),dirtyFields:zn(h),blurredFields:zn(_),touchedFields:zn(g)};return t(ce),(e,t)=>(L(),z(Ua(M(l)?`div`:`form`),{id:M(s),class:A(o.value({class:r.class})),onSubmit:Mu(S,[`prevent`])},{default:N(()=>[F(e.$slots,`default`,{errors:p.value,loading:ee.value})]),_:3},40,[`id`,`class`]))}};function bW(e,t){if(typeof e!=`object`||!e)return!1;let n=Vf(e,t);return n!=null&&n!==``}function xW(e,t){return t?{xs:44,sm:48,md:52,lg:56,xl:60}[e]:{xs:24,sm:28,md:32,lg:36,xl:40}[e]}function SW(e,t,n){return xW(t,n?e.some(e=>bW(e,n)):!1)}var CW={slots:{root:`relative inline-flex items-center`,base:[`w-full rounded-md border-0 appearance-none placeholder:text-dimmed focus:outline-none disabled:cursor-not-allowed disabled:opacity-75`,`transition-colors`],leading:`absolute inset-y-0 start-0 flex items-center`,leadingIcon:`shrink-0 text-dimmed`,leadingAvatar:`shrink-0`,leadingAvatarSize:``,trailing:`absolute inset-y-0 end-0 flex items-center`,trailingIcon:`shrink-0 text-dimmed`},variants:{fieldGroup:{horizontal:{root:`group has-focus-visible:z-[1]`,base:`group-not-only:group-first:rounded-e-none group-not-only:group-last:rounded-s-none group-not-last:group-not-first:rounded-none`},vertical:{root:`group has-focus-visible:z-[1]`,base:`group-not-only:group-first:rounded-b-none group-not-only:group-last:rounded-t-none group-not-last:group-not-first:rounded-none`}},size:{xs:{base:`px-2 py-1 text-xs gap-1`,leading:`ps-2`,trailing:`pe-2`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`},sm:{base:`px-2.5 py-1.5 text-xs gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`},md:{base:`px-2.5 py-1.5 text-sm gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`},lg:{base:`px-3 py-2 text-sm gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`},xl:{base:`px-3 py-2 text-base gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-6`,leadingAvatarSize:`xs`,trailingIcon:`size-6`}},variant:{outline:`text-highlighted bg-default ring ring-inset ring-accented`,soft:`text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50`,subtle:`text-highlighted bg-elevated ring ring-inset ring-accented`,ghost:`text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent`,none:`text-highlighted bg-transparent`},color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},leading:{true:``},trailing:{true:``},loading:{true:``},highlight:{true:``},type:{file:`file:me-1.5 file:font-medium file:text-muted file:outline-none`}},compoundVariants:[{color:`primary`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary`},{color:`secondary`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary`},{color:`success`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success`},{color:`info`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info`},{color:`warning`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning`},{color:`error`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error`},{color:`primary`,highlight:!0,class:`ring ring-inset ring-primary`},{color:`secondary`,highlight:!0,class:`ring ring-inset ring-secondary`},{color:`success`,highlight:!0,class:`ring ring-inset ring-success`},{color:`info`,highlight:!0,class:`ring ring-inset ring-info`},{color:`warning`,highlight:!0,class:`ring ring-inset ring-warning`},{color:`error`,highlight:!0,class:`ring ring-inset ring-error`},{color:`neutral`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted`},{color:`neutral`,highlight:!0,class:`ring ring-inset ring-inverted`},{leading:!0,size:`xs`,class:`ps-7`},{leading:!0,size:`sm`,class:`ps-8`},{leading:!0,size:`md`,class:`ps-9`},{leading:!0,size:`lg`,class:`ps-10`},{leading:!0,size:`xl`,class:`ps-11`},{trailing:!0,size:`xs`,class:`pe-7`},{trailing:!0,size:`sm`,class:`pe-8`},{trailing:!0,size:`md`,class:`pe-9`},{trailing:!0,size:`lg`,class:`pe-10`},{trailing:!0,size:`xl`,class:`pe-11`},{loading:!0,leading:!0,class:{leadingIcon:`animate-spin`}},{loading:!0,leading:!1,trailing:!0,class:{trailingIcon:`animate-spin`}}],defaultVariants:{size:`md`,color:`primary`,variant:`outline`}},wW=[`id`,`type`,`value`,`name`,`placeholder`,`disabled`,`required`,`autocomplete`],TW=Object.assign({inheritAttrs:!1},{__name:`Input`,props:{as:{type:null,required:!1},id:{type:String,required:!1},name:{type:String,required:!1},type:{type:null,required:!1,default:`text`},placeholder:{type:String,required:!1},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},required:{type:Boolean,required:!1},autocomplete:{type:null,required:!1,default:`off`},autofocus:{type:Boolean,required:!1},autofocusDelay:{type:Number,required:!1,default:0},disabled:{type:Boolean,required:!1},highlight:{type:Boolean,required:!1},modelValue:{type:null,required:!1},defaultValue:{type:null,required:!1},modelModifiers:{type:Object,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},trailingIcon:{type:null,required:!1},loading:{type:Boolean,required:!1},loadingIcon:{type:null,required:!1}},emits:[`update:modelValue`,`blur`,`change`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=lo(),o=xf(r,`modelValue`,i,{defaultValue:r.defaultValue}),s=wf(),{emitFormBlur:c,emitFormInput:l,emitFormChange:u,size:d,color:f,id:p,name:m,highlight:h,disabled:g,emitFormFocus:_,ariaAttrs:v}=LE(r,{deferInputValidation:!0}),{orientation:y,size:b}=OE(r),{isLeading:x,isTrailing:ee,leadingIconName:S,trailingIconName:C}=EE(r),w=W(()=>b.value||d.value),te=W(()=>Mw({extend:Mw(CW),...s.ui?.input||{}})({type:r.type,color:f.value,variant:r.variant,size:w?.value,loading:r.loading,highlight:h.value,leading:x.value||!!r.avatar||!!a.leading,trailing:ee.value||!!a.trailing,fieldGroup:y.value})),ne=qi(`inputRef`);function re(e){r.modelModifiers?.trim&&(e=e?.trim()??null),(r.modelModifiers?.number||r.type===`number`)&&(e=Hf(e)),r.modelModifiers?.nullable&&(e||=null),r.modelModifiers?.optional&&(e||=void 0),o.value=e,l()}function T(e){r.modelModifiers?.lazy||re(e.target.value)}function ie(e){let t=e.target.value;r.modelModifiers?.lazy&&re(t),r.modelModifiers?.trim&&(e.target.value=t.trim()),u(),i(`change`,e)}function ae(e){c(),i(`blur`,e)}function oe(){r.autofocus&&ne.value?.focus()}return Aa(()=>{setTimeout(()=>{oe()},r.autofocusDelay)}),t({inputRef:ne}),(t,n)=>(L(),z(M(K),{as:e.as,"data-slot":`root`,class:A(te.value.root({class:[r.ui?.root,r.class]}))},{default:N(()=>[B(`input`,U({id:M(p),ref_key:`inputRef`,ref:ne,type:e.type,value:M(o),name:M(m),placeholder:e.placeholder,"data-slot":`base`,class:te.value.base({class:r.ui?.base}),disabled:M(g),required:e.required,autocomplete:e.autocomplete},{...t.$attrs,...M(v)},{onInput:T,onBlur:ae,onChange:ie,onFocus:n[0]||=(...e)=>M(_)&&M(_)(...e)}),null,16,wW),F(t.$slots,`default`,{ui:te.value}),M(x)||e.avatar||a.leading?(L(),R(`span`,{key:0,"data-slot":`leading`,class:A(te.value.leading({class:r.ui?.leading}))},[F(t.$slots,`leading`,{ui:te.value},()=>[M(x)&&M(S)?(L(),z(yE,{key:0,name:M(S),"data-slot":`leadingIcon`,class:A(te.value.leadingIcon({class:r.ui?.leadingIcon}))},null,8,[`name`,`class`])):e.avatar?(L(),z(TE,U({key:1,size:r.ui?.leadingAvatarSize||te.value.leadingAvatarSize()},e.avatar,{"data-slot":`leadingAvatar`,class:te.value.leadingAvatar({class:r.ui?.leadingAvatar})}),null,16,[`size`,`class`])):H(``,!0)])],2)):H(``,!0),M(ee)||a.trailing?(L(),R(`span`,{key:1,"data-slot":`trailing`,class:A(te.value.trailing({class:r.ui?.trailing}))},[F(t.$slots,`trailing`,{ui:te.value},()=>[M(C)?(L(),z(yE,{key:0,name:M(C),"data-slot":`trailingIcon`,class:A(te.value.trailingIcon({class:r.ui?.trailingIcon}))},null,8,[`name`,`class`])):H(``,!0)])],2)):H(``,!0)]),_:3},8,[`as`,`class`]))}}),EW={slots:{base:[`relative group rounded-md inline-flex items-center focus:outline-none disabled:cursor-not-allowed disabled:opacity-75`,`transition-colors`],leading:`absolute inset-y-0 start-0 flex items-center`,leadingIcon:`shrink-0 text-dimmed`,leadingAvatar:`shrink-0`,leadingAvatarSize:``,trailing:`absolute inset-y-0 end-0 flex items-center`,trailingIcon:`shrink-0 text-dimmed`,value:`truncate pointer-events-none`,placeholder:`truncate text-dimmed`,arrow:`fill-default`,content:[`max-h-60 w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-select-content-transform-origin) pointer-events-auto flex flex-col`,`origin-(--reka-combobox-content-transform-origin) w-(--reka-combobox-trigger-width)`],viewport:`relative scroll-py-1 overflow-y-auto flex-1`,group:`p-1 isolate`,empty:`text-center text-muted`,label:`font-semibold text-highlighted`,separator:`-mx-1 my-1 h-px bg-border`,item:[`group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50`,`transition-colors before:transition-colors`],itemLeadingIcon:[`shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default`,`transition-colors`],itemLeadingAvatar:`shrink-0`,itemLeadingAvatarSize:``,itemLeadingChip:`shrink-0`,itemLeadingChipSize:``,itemTrailing:`ms-auto inline-flex gap-1.5 items-center`,itemTrailingIcon:`shrink-0`,itemWrapper:`flex-1 flex flex-col min-w-0`,itemLabel:`truncate`,itemDescription:`truncate text-muted`,input:`border-b border-default`,focusScope:`flex flex-col min-h-0`},variants:{fieldGroup:{horizontal:`not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]`,vertical:`not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]`},size:{xs:{base:`px-2 py-1 text-xs gap-1`,leading:`ps-2`,trailing:`pe-2`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`,label:`p-1 text-[10px]/3 gap-1`,item:`p-1 text-xs gap-1`,itemLeadingIcon:`size-4`,itemLeadingAvatarSize:`3xs`,itemLeadingChip:`size-4`,itemLeadingChipSize:`sm`,itemTrailingIcon:`size-4`,empty:`p-1 text-xs`},sm:{base:`px-2.5 py-1.5 text-xs gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`,label:`p-1.5 text-[10px]/3 gap-1.5`,item:`p-1.5 text-xs gap-1.5`,itemLeadingIcon:`size-4`,itemLeadingAvatarSize:`3xs`,itemLeadingChip:`size-4`,itemLeadingChipSize:`sm`,itemTrailingIcon:`size-4`,empty:`p-1.5 text-xs`},md:{base:`px-2.5 py-1.5 text-sm gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`,label:`p-1.5 text-xs gap-1.5`,item:`p-1.5 text-sm gap-1.5`,itemLeadingIcon:`size-5`,itemLeadingAvatarSize:`2xs`,itemLeadingChip:`size-5`,itemLeadingChipSize:`md`,itemTrailingIcon:`size-5`,empty:`p-1.5 text-sm`},lg:{base:`px-3 py-2 text-sm gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`,label:`p-2 text-xs gap-2`,item:`p-2 text-sm gap-2`,itemLeadingIcon:`size-5`,itemLeadingAvatarSize:`2xs`,itemLeadingChip:`size-5`,itemLeadingChipSize:`md`,itemTrailingIcon:`size-5`,empty:`p-2 text-sm`},xl:{base:`px-3 py-2 text-base gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-6`,leadingAvatarSize:`xs`,trailingIcon:`size-6`,label:`p-2 text-sm gap-2`,item:`p-2 text-base gap-2`,itemLeadingIcon:`size-6`,itemLeadingAvatarSize:`xs`,itemLeadingChip:`size-6`,itemLeadingChipSize:`lg`,itemTrailingIcon:`size-6`,empty:`p-2 text-base`}},variant:{outline:`text-highlighted bg-default ring ring-inset ring-accented`,soft:`text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50`,subtle:`text-highlighted bg-elevated ring ring-inset ring-accented`,ghost:`text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent`,none:`text-highlighted bg-transparent`},color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},leading:{true:``},trailing:{true:``},loading:{true:``},highlight:{true:``},type:{file:`file:me-1.5 file:font-medium file:text-muted file:outline-none`},virtualize:{true:{viewport:`p-1 isolate`},false:{viewport:`divide-y divide-default`}}},compoundVariants:[{color:`primary`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary`},{color:`secondary`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary`},{color:`success`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success`},{color:`info`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info`},{color:`warning`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning`},{color:`error`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error`},{color:`primary`,highlight:!0,class:`ring ring-inset ring-primary`},{color:`secondary`,highlight:!0,class:`ring ring-inset ring-secondary`},{color:`success`,highlight:!0,class:`ring ring-inset ring-success`},{color:`info`,highlight:!0,class:`ring ring-inset ring-info`},{color:`warning`,highlight:!0,class:`ring ring-inset ring-warning`},{color:`error`,highlight:!0,class:`ring ring-inset ring-error`},{color:`neutral`,variant:[`outline`,`subtle`],class:`focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted`},{color:`neutral`,highlight:!0,class:`ring ring-inset ring-inverted`},{leading:!0,size:`xs`,class:`ps-7`},{leading:!0,size:`sm`,class:`ps-8`},{leading:!0,size:`md`,class:`ps-9`},{leading:!0,size:`lg`,class:`ps-10`},{leading:!0,size:`xl`,class:`ps-11`},{trailing:!0,size:`xs`,class:`pe-7`},{trailing:!0,size:`sm`,class:`pe-8`},{trailing:!0,size:`md`,class:`pe-9`},{trailing:!0,size:`lg`,class:`pe-10`},{trailing:!0,size:`xl`,class:`pe-11`},{loading:!0,leading:!0,class:{leadingIcon:`animate-spin`}},{loading:!0,leading:!1,trailing:!0,class:{trailingIcon:`animate-spin`}}],defaultVariants:{size:`md`,color:`primary`,variant:`outline`}},DW=Object.assign({inheritAttrs:!1},{__name:`SelectMenu`,props:ho({id:{type:String,required:!1},placeholder:{type:String,required:!1},searchInput:{type:[Boolean,Object],required:!1,default:!0},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},required:{type:Boolean,required:!1},trailingIcon:{type:null,required:!1},selectedIcon:{type:null,required:!1},content:{type:Object,required:!1},arrow:{type:[Boolean,Object],required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:!0},virtualize:{type:[Boolean,Object],required:!1,default:!1},valueKey:{type:null,required:!1},labelKey:{type:null,required:!1,default:`label`},descriptionKey:{type:null,required:!1,default:`description`},items:{type:null,required:!1},defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1},modelModifiers:{type:Object,required:!1},multiple:{type:Boolean,required:!1},highlight:{type:Boolean,required:!1},createItem:{type:[Boolean,String,Object],required:!1},filterFields:{type:Array,required:!1},ignoreFilter:{type:Boolean,required:!1},autofocus:{type:Boolean,required:!1},autofocusDelay:{type:Number,required:!1,default:0},class:{type:null,required:!1},ui:{type:null,required:!1},open:{type:Boolean,required:!1},defaultOpen:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},name:{type:String,required:!1},resetSearchTermOnBlur:{type:Boolean,required:!1,default:!0},resetSearchTermOnSelect:{type:Boolean,required:!1,default:!0},highlightOnHover:{type:Boolean,required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},loading:{type:Boolean,required:!1},loadingIcon:{type:null,required:!1}},{searchTerm:{type:String,default:``},searchTermModifiers:{}}),emits:ho([`update:open`,`change`,`blur`,`focus`,`create`,`highlight`,`update:modelValue`],[`update:searchTerm`]),setup(e,{expose:t,emit:n}){let r=e,i=n,a=lo(),o=Lo(e,`searchTerm`,{type:String,default:``}),{t:s}=$f(),c=wf(),{contains:l}=mg({sensitivity:`base`}),u=bg(jd(r,`modelValue`,`defaultValue`,`open`,`defaultOpen`,`required`,`multiple`,`resetSearchTermOnBlur`,`resetSearchTermOnSelect`,`highlightOnHover`),i),d=RS(lr(()=>r.portal)),f=lr(()=>Af(r.content,{side:`bottom`,sideOffset:8,collisionPadding:8,position:`popper`})),p=lr(()=>r.arrow),m=lr(()=>r.virtualize?Af(typeof r.virtualize==`boolean`?{}:r.virtualize,{estimateSize:SW(pe.value,r.size||`md`,r.descriptionKey)}):!1),h=lr(()=>Af(r.searchInput,{placeholder:s(`selectMenu.search`),variant:`none`})),{emitFormBlur:g,emitFormFocus:_,emitFormInput:v,emitFormChange:y,size:b,color:x,id:ee,name:S,highlight:C,disabled:w,ariaAttrs:te}=LE(r),{orientation:ne,size:re}=OE(r),{isLeading:T,isTrailing:ie,leadingIconName:ae,trailingIconName:oe}=EE(lr(()=>Af(r,{trailingIcon:c.ui.icons.chevronDown}))),se=W(()=>re.value||b.value),[E,ce]=zd(),[le,ue]=zd({props:{item:{type:[Object,String,Number,Boolean],required:!0},index:{type:Number,required:!1}}}),D=W(()=>Mw({extend:Mw(EW),...c.ui?.selectMenu||{}})({color:x.value,variant:r.variant,size:se?.value,loading:r.loading,highlight:C.value,leading:T.value||!!r.avatar||!!a.leading,trailing:ie.value||!!a.trailing,fieldGroup:ne.value,virtualize:!!r.virtualize}));function de(e){if(r.multiple&&Array.isArray(e)){let t=e.map(e=>Gf(pe.value,e,{labelKey:r.labelKey,valueKey:r.valueKey})).filter(e=>e!=null&&e!==``);return t.length>0?t.join(`, `):void 0}return Gf(pe.value,e,{labelKey:r.labelKey,valueKey:r.valueKey})}let fe=W(()=>r.items?.length?Kf(r.items)?r.items:[r.items]:[]),pe=W(()=>fe.value.flatMap(e=>e)),me=W(()=>{if(r.ignoreFilter||!o.value)return fe.value;let e=Array.isArray(r.filterFields)?r.filterFields:[r.labelKey];return fe.value.map(t=>t.filter(t=>t==null?!1:typeof t==`object`?t.type&&[`label`,`separator`].includes(t.type)?!0:e.some(e=>{let n=Vf(t,e);return n!=null&&l(String(n),o.value)}):l(String(t),o.value))).filter(e=>e.filter(e=>!O(e)||!e.type||![`label`,`separator`].includes(e.type)).length>0)}),he=W(()=>me.value.flatMap(e=>e)),ge=W(()=>{if(!r.createItem||!o.value)return!1;let e=r.valueKey?{[r.valueKey]:o.value}:o.value;return typeof r.createItem==`object`&&r.createItem.when===`always`||r.createItem===`always`?!he.value.find(t=>Uf(t,e,r.valueKey)):!he.value.length}),_e=W(()=>typeof r.createItem==`object`?r.createItem.position:`bottom`),ve=qi(`triggerRef`);function ye(){r.autofocus&&ve.value?.$el?.focus({focusVisible:!0})}Aa(()=>{setTimeout(()=>{ye()},r.autofocusDelay)});function be(e){Kn(r.modelValue)!==e&&(r.modelModifiers?.trim&&(e=e?.trim()??null),r.modelModifiers?.number&&(e=Hf(e)),r.modelModifiers?.nullable&&(e??=null),r.modelModifiers?.optional&&(e??=void 0),i(`change`,new Event(`change`,{target:{value:e}})),y(),v(),r.resetSearchTermOnSelect&&(o.value=``))}function xe(e){let t;e?(i(`focus`,new FocusEvent(`focus`)),_(),clearTimeout(t)):(i(`blur`,new FocusEvent(`blur`)),g(),r.resetSearchTermOnBlur&&(t=setTimeout(()=>{o.value=``},100)))}function Se(e){e.preventDefault(),e.stopPropagation(),i(`create`,o.value)}function Ce(e,t){if(O(t)){if(t.disabled){e.preventDefault();return}t.onSelect?.(e)}}function O(e){return typeof e==`object`&&!!e}return t({triggerRef:lr(()=>ve.value?.$el)}),(t,n)=>(L(),R(I,null,[V(M(E),null,{default:N(()=>[V(M(ox),{"data-slot":`item`,class:A(D.value.item({class:r.ui?.item})),value:o.value,onSelect:Se},{default:N(()=>[B(`span`,{"data-slot":`itemLabel`,class:A(D.value.itemLabel({class:r.ui?.itemLabel}))},[F(t.$slots,`create-item-label`,{item:o.value},()=>[nc(ft(M(s)(`selectMenu.create`,{label:o.value})),1)])],2)]),_:3},8,[`class`,`value`])]),_:3}),V(M(le),null,{default:N(({item:n,index:i})=>[O(n)&&n.type===`label`?(L(),z(M(cx),{key:0,"data-slot":`label`,class:A(D.value.label({class:[r.ui?.label,n.ui?.label,n.class]}))},{default:N(()=>[nc(ft(M(Vf)(n,r.labelKey)),1)]),_:2},1032,[`class`])):O(n)&&n.type===`separator`?(L(),z(M(ux),{key:1,"data-slot":`separator`,class:A(D.value.separator({class:[r.ui?.separator,n.ui?.separator,n.class]}))},null,8,[`class`])):(L(),z(M(ox),{key:2,"data-slot":`item`,class:A(D.value.item({class:[r.ui?.item,O(n)&&n.ui?.item,O(n)&&n.class]})),disabled:O(n)&&n.disabled,value:r.valueKey&&O(n)?M(Vf)(n,r.valueKey):n,onSelect:e=>Ce(e,n)},{default:N(()=>[F(t.$slots,`item`,{item:n,index:i,ui:D.value},()=>[F(t.$slots,`item-leading`,{item:n,index:i,ui:D.value},()=>[O(n)&&n.icon?(L(),z(yE,{key:0,name:n.icon,"data-slot":`itemLeadingIcon`,class:A(D.value.itemLeadingIcon({class:[r.ui?.itemLeadingIcon,n.ui?.itemLeadingIcon]}))},null,8,[`name`,`class`])):O(n)&&n.avatar?(L(),z(TE,U({key:1,size:n.ui?.itemLeadingAvatarSize||r.ui?.itemLeadingAvatarSize||D.value.itemLeadingAvatarSize()},n.avatar,{"data-slot":`itemLeadingAvatar`,class:D.value.itemLeadingAvatar({class:[r.ui?.itemLeadingAvatar,n.ui?.itemLeadingAvatar]})}),null,16,[`size`,`class`])):O(n)&&n.chip?(L(),z(CE,U({key:2,size:r.ui?.itemLeadingChipSize||D.value.itemLeadingChipSize(),inset:``,standalone:``},n.chip,{"data-slot":`itemLeadingChip`,class:D.value.itemLeadingChip({class:[r.ui?.itemLeadingChip,n.ui?.itemLeadingChip]})}),null,16,[`size`,`class`])):H(``,!0)]),B(`span`,{"data-slot":`itemWrapper`,class:A(D.value.itemWrapper({class:[r.ui?.itemWrapper,O(n)&&n.ui?.itemWrapper]}))},[B(`span`,{"data-slot":`itemLabel`,class:A(D.value.itemLabel({class:[r.ui?.itemLabel,O(n)&&n.ui?.itemLabel]}))},[F(t.$slots,`item-label`,{item:n,index:i},()=>[nc(ft(O(n)?M(Vf)(n,r.labelKey):n),1)])],2),O(n)&&(M(Vf)(n,r.descriptionKey)||a[`item-description`])?(L(),R(`span`,{key:0,"data-slot":`itemDescription`,class:A(D.value.itemDescription({class:[r.ui?.itemDescription,O(n)&&n.ui?.itemDescription]}))},[F(t.$slots,`item-description`,{item:n,index:i},()=>[nc(ft(M(Vf)(n,r.descriptionKey)),1)])],2)):H(``,!0)],2),B(`span`,{"data-slot":`itemTrailing`,class:A(D.value.itemTrailing({class:[r.ui?.itemTrailing,O(n)&&n.ui?.itemTrailing]}))},[F(t.$slots,`item-trailing`,{item:n,index:i,ui:D.value}),V(M(sx),{"as-child":``},{default:N(()=>[V(yE,{name:e.selectedIcon||M(c).ui.icons.check,"data-slot":`itemTrailingIcon`,class:A(D.value.itemTrailingIcon({class:[r.ui?.itemTrailingIcon,O(n)&&n.ui?.itemTrailingIcon]}))},null,8,[`name`,`class`])]),_:2},1024)],2)])]),_:2},1032,[`class`,`disabled`,`value`,`onSelect`]))]),_:3}),V(M(Yb),U({id:M(ee)},{...M(u),...t.$attrs,...M(te)},{"ignore-filter":``,"as-child":``,name:M(S),disabled:M(w),"onUpdate:modelValue":be,"onUpdate:open":xe}),{default:N(({modelValue:i,open:c})=>[V(M(db),{"as-child":``},{default:N(()=>[V(M(dx),{ref_key:`triggerRef`,ref:ve,"data-slot":`base`,class:A(D.value.base({class:[r.ui?.base,r.class]})),tabindex:`0`},{default:N(()=>[M(T)||e.avatar||a.leading?(L(),R(`span`,{key:0,"data-slot":`leading`,class:A(D.value.leading({class:r.ui?.leading}))},[F(t.$slots,`leading`,{modelValue:i,open:c,ui:D.value},()=>[M(T)&&M(ae)?(L(),z(yE,{key:0,name:M(ae),"data-slot":`leadingIcon`,class:A(D.value.leadingIcon({class:r.ui?.leadingIcon}))},null,8,[`name`,`class`])):e.avatar?(L(),z(TE,U({key:1,size:r.ui?.itemLeadingAvatarSize||D.value.itemLeadingAvatarSize()},e.avatar,{"data-slot":`itemLeadingAvatar`,class:D.value.itemLeadingAvatar({class:r.ui?.itemLeadingAvatar})}),null,16,[`size`,`class`])):H(``,!0)])],2)):H(``,!0),F(t.$slots,`default`,{modelValue:i,open:c,ui:D.value},()=>[(L(!0),R(I,null,qa([de(i)],t=>(L(),R(I,{key:t},[t==null?(L(),R(`span`,{key:1,"data-slot":`placeholder`,class:A(D.value.placeholder({class:r.ui?.placeholder}))},ft(e.placeholder??`\xA0`),3)):(L(),R(`span`,{key:0,"data-slot":`value`,class:A(D.value.value({class:r.ui?.value}))},ft(t),3))],64))),128))]),M(ie)||a.trailing?(L(),R(`span`,{key:1,"data-slot":`trailing`,class:A(D.value.trailing({class:r.ui?.trailing}))},[F(t.$slots,`trailing`,{modelValue:i,open:c,ui:D.value},()=>[M(oe)?(L(),z(yE,{key:0,name:M(oe),"data-slot":`trailingIcon`,class:A(D.value.trailingIcon({class:r.ui?.trailingIcon}))},null,8,[`name`,`class`])):H(``,!0)])],2)):H(``,!0)]),_:2},1032,[`class`])]),_:2},1024),V(M(lx),it(ec(M(d))),{default:N(()=>[V(M(ex),U({"data-slot":`content`,class:D.value.content({class:r.ui?.content})},f.value),{default:N(()=>[V(M(k_),{trapped:``,"data-slot":`focusScope`,class:A(D.value.focusScope({class:r.ui?.focusScope}))},{default:N(()=>[F(t.$slots,`content-top`),e.searchInput?(L(),z(M(ax),{key:0,modelValue:o.value,"onUpdate:modelValue":n[1]||=e=>o.value=e,"display-value":()=>o.value,"as-child":``},{default:N(()=>[V(TW,U({autofocus:``,autocomplete:`off`,size:e.size},h.value,{"data-slot":`input`,class:D.value.input({class:r.ui?.input}),onChange:n[0]||=Mu(()=>{},[`stop`])}),null,16,[`size`,`class`])]),_:1},8,[`modelValue`,`display-value`])):H(``,!0),V(M(tx),{"data-slot":`empty`,class:A(D.value.empty({class:r.ui?.empty}))},{default:N(()=>[F(t.$slots,`empty`,{searchTerm:o.value},()=>[nc(ft(o.value?M(s)(`selectMenu.noMatch`,{searchTerm:o.value}):M(s)(`selectMenu.noData`)),1)])]),_:3},8,[`class`]),B(`div`,{role:`presentation`,"data-slot":`viewport`,class:A(D.value.viewport({class:r.ui?.viewport}))},[e.virtualize?(L(),R(I,{key:0},[ge.value&&_e.value===`top`?(L(),z(M(ce),{key:0})):H(``,!0),V(M(fx),U({options:he.value,"text-content":e=>O(e)?M(Vf)(e,r.labelKey):String(e)},m.value),{default:N(({option:e,virtualItem:t})=>[V(M(ue),{item:e,index:t.index},null,8,[`item`,`index`])]),_:1},16,[`options`,`text-content`]),ge.value&&_e.value===`bottom`?(L(),z(M(ce),{key:1})):H(``,!0)],64)):(L(),R(I,{key:1},[ge.value&&_e.value===`top`?(L(),z(M(ix),{key:0,"data-slot":`group`,class:A(D.value.group({class:r.ui?.group}))},{default:N(()=>[V(M(ce))]),_:1},8,[`class`])):H(``,!0),(L(!0),R(I,null,qa(me.value,(e,t)=>(L(),z(M(ix),{key:`group-${t}`,"data-slot":`group`,class:A(D.value.group({class:r.ui?.group}))},{default:N(()=>[(L(!0),R(I,null,qa(e,(e,n)=>(L(),z(M(ue),{key:`group-${t}-${n}`,item:e,index:n},null,8,[`item`,`index`]))),128))]),_:2},1032,[`class`]))),128)),ge.value&&_e.value===`bottom`?(L(),z(M(ix),{key:1,"data-slot":`group`,class:A(D.value.group({class:r.ui?.group}))},{default:N(()=>[V(M(ce))]),_:1},8,[`class`])):H(``,!0)],64))],2),F(t.$slots,`content-bottom`)]),_:3},8,[`class`]),e.arrow?(L(),z(M($b),U({key:0},p.value,{"data-slot":`arrow`,class:D.value.arrow({class:r.ui?.arrow})}),null,16,[`class`])):H(``,!0)]),_:3},16,[`class`])]),_:3},16)]),_:3},16,[`id`,`name`,`disabled`])],64))}}),OW=Object.assign({inheritAttrs:!1},{__name:`ColorModeSelect`,props:{id:{type:String,required:!1},placeholder:{type:String,required:!1},searchInput:{type:[Boolean,Object],required:!1,default:!1},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},required:{type:Boolean,required:!1},trailingIcon:{type:null,required:!1},selectedIcon:{type:null,required:!1},content:{type:Object,required:!1},arrow:{type:[Boolean,Object],required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0},virtualize:{type:[Boolean,Object],required:!1},valueKey:{type:null,required:!1},labelKey:{type:null,required:!1},descriptionKey:{type:null,required:!1},defaultValue:{type:null,required:!1},modelModifiers:{type:Object,required:!1},multiple:{type:Boolean,required:!1},highlight:{type:Boolean,required:!1},createItem:{type:[Boolean,String,Object],required:!1},filterFields:{type:Array,required:!1},ignoreFilter:{type:Boolean,required:!1},autofocus:{type:Boolean,required:!1},autofocusDelay:{type:Number,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},open:{type:Boolean,required:!1},defaultOpen:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1},name:{type:String,required:!1},resetSearchTermOnBlur:{type:Boolean,required:!1},resetSearchTermOnSelect:{type:Boolean,required:!1},highlightOnHover:{type:Boolean,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},loading:{type:Boolean,required:!1},loadingIcon:{type:null,required:!1}},setup(e){let t=e,{t:n}=$f(),r=ep(),i=wf(),a=yg(t),o=W(()=>[{label:n(`colorMode.system`),value:`system`,icon:i.ui.icons.system},{label:n(`colorMode.light`),value:`light`,icon:i.ui.icons.light},{label:n(`colorMode.dark`),value:`dark`,icon:i.ui.icons.dark}]),s=W({get(){return o.value.find(e=>e.value===r.preference)||o.value[0]},set(e){r.preference=e.value}});return(e,t)=>(L(),z(DW,U({modelValue:s.value,"onUpdate:modelValue":t[0]||=e=>s.value=e,icon:s.value?.icon},{...M(a),...e.$attrs},{items:o.value}),null,16,[`modelValue`,`icon`,`items`]))}}),kW={slots:{root:``,wrapper:``,labelWrapper:`flex content-center items-center justify-between gap-1`,label:`block font-medium text-default`,container:`relative`,description:`text-muted`,error:`mt-2 text-error`,hint:`text-muted`,help:`mt-2 text-muted`},variants:{size:{xs:{root:`text-xs`},sm:{root:`text-xs`},md:{root:`text-sm`},lg:{root:`text-sm`},xl:{root:`text-base`}},required:{true:{label:`after:content-['*'] after:ms-0.5 after:text-error`}},orientation:{vertical:{container:`mt-1`},horizontal:{root:`flex justify-between place-items-baseline gap-2`}}},defaultVariants:{size:`md`,orientation:`vertical`}},AW=[`id`],jW=[`id`],MW=[`id`],NW=[`id`],PW={__name:`FormField`,props:{as:{type:null,required:!1},name:{type:String,required:!1},errorPattern:{type:null,required:!1},label:{type:String,required:!1},description:{type:String,required:!1},help:{type:String,required:!1},error:{type:[Boolean,String],required:!1,default:void 0},hint:{type:String,required:!1},size:{type:null,required:!1},required:{type:Boolean,required:!1},eagerValidation:{type:Boolean,required:!1},validateOnInputDelay:{type:Number,required:!1},orientation:{type:null,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1}},setup(e){let t=e,n=lo(),r=wf(),i=W(()=>Mw({extend:Mw(kW),...r.ui?.formField||{}})({size:t.size,required:t.required,orientation:t.orientation})),a=oi(IE,null),o=W(()=>t.error||a?.value?.find(e=>e.name===t.name||t.errorPattern&&e.name?.match(t.errorPattern))?.message),s=j(Gi()),c=s.value,l=oi(PE,void 0);return pi(s,()=>{l&&t.name&&(l.value[t.name]={id:s.value,pattern:t.errorPattern})},{immediate:!0}),ai(NE,s),ai(ME,W(()=>({error:o.value,name:t.name,size:t.size,eagerValidation:t.eagerValidation,validateOnInputDelay:t.validateOnInputDelay,errorPattern:t.errorPattern,hint:t.hint,description:t.description,help:t.help,ariaId:c}))),(r,a)=>(L(),z(M(K),{as:e.as,"data-orientation":e.orientation,"data-slot":`root`,class:A(i.value.root({class:[t.ui?.root,t.class]}))},{default:N(()=>[B(`div`,{"data-slot":`wrapper`,class:A(i.value.wrapper({class:t.ui?.wrapper}))},[e.label||n.label?(L(),R(`div`,{key:0,"data-slot":`labelWrapper`,class:A(i.value.labelWrapper({class:t.ui?.labelWrapper}))},[V(M(px),{for:s.value,"data-slot":`label`,class:A(i.value.label({class:t.ui?.label}))},{default:N(()=>[F(r.$slots,`label`,{label:e.label},()=>[nc(ft(e.label),1)])]),_:3},8,[`for`,`class`]),e.hint||n.hint?(L(),R(`span`,{key:0,id:`${M(c)}-hint`,"data-slot":`hint`,class:A(i.value.hint({class:t.ui?.hint}))},[F(r.$slots,`hint`,{hint:e.hint},()=>[nc(ft(e.hint),1)])],10,AW)):H(``,!0)],2)):H(``,!0),e.description||n.description?(L(),R(`p`,{key:1,id:`${M(c)}-description`,"data-slot":`description`,class:A(i.value.description({class:t.ui?.description}))},[F(r.$slots,`description`,{description:e.description},()=>[nc(ft(e.description),1)])],10,jW)):H(``,!0)],2),B(`div`,{class:A([(e.label||!!n.label||e.description||!!n.description)&&i.value.container({class:t.ui?.container})])},[F(r.$slots,`default`,{error:o.value}),t.error!==!1&&(typeof o.value==`string`&&o.value||n.error)?(L(),R(`div`,{key:0,id:`${M(c)}-error`,"data-slot":`error`,class:A(i.value.error({class:t.ui?.error}))},[F(r.$slots,`error`,{error:o.value},()=>[nc(ft(o.value),1)])],10,MW)):e.help||n.help?(L(),R(`div`,{key:1,id:`${M(c)}-help`,"data-slot":`help`,class:A(i.value.help({class:t.ui?.help}))},[F(r.$slots,`help`,{help:e.help},()=>[nc(ft(e.help),1)])],10,NW)):H(``,!0)],2)]),_:3},8,[`as`,`data-orientation`,`class`]))}},FW={slots:{base:[`relative group rounded-md inline-flex items-center focus:outline-none disabled:cursor-not-allowed disabled:opacity-75`,`transition-colors`],leading:`absolute inset-y-0 start-0 flex items-center`,leadingIcon:`shrink-0 text-dimmed`,leadingAvatar:`shrink-0`,leadingAvatarSize:``,trailing:`absolute inset-y-0 end-0 flex items-center`,trailingIcon:`shrink-0 text-dimmed`,value:`truncate pointer-events-none`,placeholder:`truncate text-dimmed`,arrow:`fill-default`,content:`max-h-60 w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-select-content-transform-origin) pointer-events-auto flex flex-col`,viewport:`relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1`,group:`p-1 isolate`,empty:`text-center text-muted`,label:`font-semibold text-highlighted`,separator:`-mx-1 my-1 h-px bg-border`,item:[`group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50`,`transition-colors before:transition-colors`],itemLeadingIcon:[`shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default`,`transition-colors`],itemLeadingAvatar:`shrink-0`,itemLeadingAvatarSize:``,itemLeadingChip:`shrink-0`,itemLeadingChipSize:``,itemTrailing:`ms-auto inline-flex gap-1.5 items-center`,itemTrailingIcon:`shrink-0`,itemWrapper:`flex-1 flex flex-col min-w-0`,itemLabel:`truncate`,itemDescription:`truncate text-muted`},variants:{fieldGroup:{horizontal:`not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]`,vertical:`not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]`},size:{xs:{base:`px-2 py-1 text-xs gap-1`,leading:`ps-2`,trailing:`pe-2`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`,label:`p-1 text-[10px]/3 gap-1`,item:`p-1 text-xs gap-1`,itemLeadingIcon:`size-4`,itemLeadingAvatarSize:`3xs`,itemLeadingChip:`size-4`,itemLeadingChipSize:`sm`,itemTrailingIcon:`size-4`,empty:`p-1 text-xs`},sm:{base:`px-2.5 py-1.5 text-xs gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`,label:`p-1.5 text-[10px]/3 gap-1.5`,item:`p-1.5 text-xs gap-1.5`,itemLeadingIcon:`size-4`,itemLeadingAvatarSize:`3xs`,itemLeadingChip:`size-4`,itemLeadingChipSize:`sm`,itemTrailingIcon:`size-4`,empty:`p-1.5 text-xs`},md:{base:`px-2.5 py-1.5 text-sm gap-1.5`,leading:`ps-2.5`,trailing:`pe-2.5`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`,label:`p-1.5 text-xs gap-1.5`,item:`p-1.5 text-sm gap-1.5`,itemLeadingIcon:`size-5`,itemLeadingAvatarSize:`2xs`,itemLeadingChip:`size-5`,itemLeadingChipSize:`md`,itemTrailingIcon:`size-5`,empty:`p-1.5 text-sm`},lg:{base:`px-3 py-2 text-sm gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`,label:`p-2 text-xs gap-2`,item:`p-2 text-sm gap-2`,itemLeadingIcon:`size-5`,itemLeadingAvatarSize:`2xs`,itemLeadingChip:`size-5`,itemLeadingChipSize:`md`,itemTrailingIcon:`size-5`,empty:`p-2 text-sm`},xl:{base:`px-3 py-2 text-base gap-2`,leading:`ps-3`,trailing:`pe-3`,leadingIcon:`size-6`,leadingAvatarSize:`xs`,trailingIcon:`size-6`,label:`p-2 text-sm gap-2`,item:`p-2 text-base gap-2`,itemLeadingIcon:`size-6`,itemLeadingAvatarSize:`xs`,itemLeadingChip:`size-6`,itemLeadingChipSize:`lg`,itemTrailingIcon:`size-6`,empty:`p-2 text-base`}},variant:{outline:`text-highlighted bg-default ring ring-inset ring-accented`,soft:`text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50`,subtle:`text-highlighted bg-elevated ring ring-inset ring-accented`,ghost:`text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent`,none:`text-highlighted bg-transparent`},color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},leading:{true:``},trailing:{true:``},loading:{true:``},highlight:{true:``},type:{file:`file:me-1.5 file:font-medium file:text-muted file:outline-none`}},compoundVariants:[{color:`primary`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-primary`},{color:`secondary`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-secondary`},{color:`success`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-success`},{color:`info`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-info`},{color:`warning`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-warning`},{color:`error`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-error`},{color:`primary`,highlight:!0,class:`ring ring-inset ring-primary`},{color:`secondary`,highlight:!0,class:`ring ring-inset ring-secondary`},{color:`success`,highlight:!0,class:`ring ring-inset ring-success`},{color:`info`,highlight:!0,class:`ring ring-inset ring-info`},{color:`warning`,highlight:!0,class:`ring ring-inset ring-warning`},{color:`error`,highlight:!0,class:`ring ring-inset ring-error`},{color:`neutral`,variant:[`outline`,`subtle`],class:`focus:ring-2 focus:ring-inset focus:ring-inverted`},{color:`neutral`,highlight:!0,class:`ring ring-inset ring-inverted`},{leading:!0,size:`xs`,class:`ps-7`},{leading:!0,size:`sm`,class:`ps-8`},{leading:!0,size:`md`,class:`ps-9`},{leading:!0,size:`lg`,class:`ps-10`},{leading:!0,size:`xl`,class:`ps-11`},{trailing:!0,size:`xs`,class:`pe-7`},{trailing:!0,size:`sm`,class:`pe-8`},{trailing:!0,size:`md`,class:`pe-9`},{trailing:!0,size:`lg`,class:`pe-10`},{trailing:!0,size:`xl`,class:`pe-11`},{loading:!0,leading:!0,class:{leadingIcon:`animate-spin`}},{loading:!0,leading:!1,trailing:!0,class:{trailingIcon:`animate-spin`}}],defaultVariants:{size:`md`,color:`primary`,variant:`outline`}},IW=Object.assign({inheritAttrs:!1},{__name:`Select`,props:{id:{type:String,required:!1},placeholder:{type:String,required:!1},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},trailingIcon:{type:null,required:!1},selectedIcon:{type:null,required:!1},content:{type:Object,required:!1},arrow:{type:[Boolean,Object],required:!1},portal:{type:[Boolean,String],required:!1,skipCheck:!0,default:!0},valueKey:{type:null,required:!1,default:`value`},labelKey:{type:null,required:!1,default:`label`},descriptionKey:{type:null,required:!1,default:`description`},items:{type:null,required:!1},defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1},modelModifiers:{type:Object,required:!1},multiple:{type:Boolean,required:!1},highlight:{type:Boolean,required:!1},autofocus:{type:Boolean,required:!1},autofocusDelay:{type:Number,required:!1,default:0},class:{type:null,required:!1},ui:{type:null,required:!1},open:{type:Boolean,required:!1},defaultOpen:{type:Boolean,required:!1},autocomplete:{type:String,required:!1},disabled:{type:Boolean,required:!1},name:{type:String,required:!1},required:{type:Boolean,required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},loading:{type:Boolean,required:!1},loadingIcon:{type:null,required:!1}},emits:[`update:open`,`change`,`blur`,`focus`,`update:modelValue`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=lo(),o=wf(),s=bg(jd(r,`open`,`defaultOpen`,`disabled`,`autocomplete`,`required`,`multiple`),i),c=RS(lr(()=>r.portal)),l=lr(()=>Af(r.content,{side:`bottom`,sideOffset:8,collisionPadding:8,position:`popper`})),u=lr(()=>r.arrow),{emitFormChange:d,emitFormInput:f,emitFormBlur:p,emitFormFocus:m,size:h,color:g,id:_,name:v,highlight:y,disabled:b,ariaAttrs:x}=LE(r),{orientation:ee,size:S}=OE(r),{isLeading:C,isTrailing:w,leadingIconName:te,trailingIconName:ne}=EE(lr(()=>Af(r,{trailingIcon:o.ui.icons.chevronDown}))),re=W(()=>S.value||h.value),T=W(()=>Mw({extend:Mw(FW),...o.ui?.select||{}})({color:g.value,variant:r.variant,size:re?.value,loading:r.loading,highlight:y.value,leading:C.value||!!r.avatar||!!a.leading,trailing:w.value||!!a.trailing,fieldGroup:ee.value})),ie=W(()=>r.items?.length?Kf(r.items)?r.items:[r.items]:[]),ae=W(()=>ie.value.flatMap(e=>e));function oe(e){if(r.multiple&&Array.isArray(e)){let t=e.map(e=>Gf(ae.value,e,{labelKey:r.labelKey,valueKey:r.valueKey})).filter(e=>e!=null&&e!==``);return t.length>0?t.join(`, `):void 0}return Gf(ae.value,e,{labelKey:r.labelKey,valueKey:r.valueKey})}let se=qi(`triggerRef`);function E(){r.autofocus&&se.value?.$el?.focus({focusVisible:!0})}Aa(()=>{setTimeout(()=>{E()},r.autofocusDelay)});function ce(e){r.modelModifiers?.trim&&(e=e?.trim()??null),r.modelModifiers?.number&&(e=Hf(e)),r.modelModifiers?.nullable&&(e??=null),r.modelModifiers?.optional&&(e??=void 0),i(`change`,new Event(`change`,{target:{value:e}})),d(),f()}function le(e){e?(i(`focus`,new FocusEvent(`focus`)),m()):(i(`blur`,new FocusEvent(`blur`)),p())}function ue(e){return typeof e==`object`&&!!e}return t({triggerRef:lr(()=>se.value?.$el)}),(t,n)=>(L(),z(M(Ax),U({name:M(v)},M(s),{autocomplete:e.autocomplete,disabled:M(b),"default-value":e.defaultValue,"model-value":e.modelValue,"onUpdate:modelValue":ce,"onUpdate:open":le}),{default:N(({modelValue:n,open:i})=>[V(M(tS),U({id:M(_),ref_key:`triggerRef`,ref:se,"data-slot":`base`,class:T.value.base({class:[r.ui?.base,r.class]})},{...t.$attrs,...M(x)}),{default:N(()=>[M(C)||e.avatar||a.leading?(L(),R(`span`,{key:0,"data-slot":`leading`,class:A(T.value.leading({class:r.ui?.leading}))},[F(t.$slots,`leading`,{modelValue:n,open:i,ui:T.value},()=>[M(C)&&M(te)?(L(),z(yE,{key:0,name:M(te),"data-slot":`leadingIcon`,class:A(T.value.leadingIcon({class:r.ui?.leadingIcon}))},null,8,[`name`,`class`])):e.avatar?(L(),z(TE,U({key:1,size:r.ui?.itemLeadingAvatarSize||T.value.itemLeadingAvatarSize()},e.avatar,{"data-slot":`itemLeadingAvatar`,class:T.value.itemLeadingAvatar({class:r.ui?.itemLeadingAvatar})}),null,16,[`size`,`class`])):H(``,!0)])],2)):H(``,!0),F(t.$slots,`default`,{modelValue:n,open:i,ui:T.value},()=>[(L(!0),R(I,null,qa([oe(n)],t=>(L(),R(I,{key:t},[t==null?(L(),R(`span`,{key:1,"data-slot":`placeholder`,class:A(T.value.placeholder({class:r.ui?.placeholder}))},ft(e.placeholder??`\xA0`),3)):(L(),R(`span`,{key:0,"data-slot":`value`,class:A(T.value.value({class:r.ui?.value}))},ft(t),3))],64))),128))]),M(w)||a.trailing?(L(),R(`span`,{key:1,"data-slot":`trailing`,class:A(T.value.trailing({class:r.ui?.trailing}))},[F(t.$slots,`trailing`,{modelValue:n,open:i,ui:T.value},()=>[M(ne)?(L(),z(yE,{key:0,name:M(ne),"data-slot":`trailingIcon`,class:A(T.value.trailingIcon({class:r.ui?.trailingIcon}))},null,8,[`name`,`class`])):H(``,!0)])],2)):H(``,!0)]),_:2},1040,[`id`,`class`]),V(M($x),it(ec(M(c))),{default:N(()=>[V(M(Ux),U({"data-slot":`content`,class:T.value.content({class:r.ui?.content})},l.value),{default:N(()=>[F(t.$slots,`content-top`),B(`div`,{role:`presentation`,"data-slot":`viewport`,class:A(T.value.viewport({class:r.ui?.viewport}))},[(L(!0),R(I,null,qa(ie.value,(n,i)=>(L(),z(M(Kx),{key:`group-${i}`,"data-slot":`group`,class:A(T.value.group({class:r.ui?.group}))},{default:N(()=>[(L(!0),R(I,null,qa(n,(n,s)=>(L(),R(I,{key:`group-${i}-${s}`},[ue(n)&&n.type===`label`?(L(),z(M(Qx),{key:0,"data-slot":`label`,class:A(T.value.label({class:[r.ui?.label,n.ui?.label,n.class]}))},{default:N(()=>[nc(ft(M(Vf)(n,r.labelKey)),1)]),_:2},1032,[`class`])):ue(n)&&n.type===`separator`?(L(),z(M(eS),{key:1,"data-slot":`separator`,class:A(T.value.separator({class:[r.ui?.separator,n.ui?.separator,n.class]}))},null,8,[`class`])):(L(),z(M(Yx),{key:2,"data-slot":`item`,class:A(T.value.item({class:[r.ui?.item,ue(n)&&n.ui?.item,ue(n)&&n.class]})),disabled:ue(n)&&n.disabled,value:ue(n)?M(Vf)(n,r.valueKey):n,onSelect:e=>ue(n)&&n.onSelect?.(e)},{default:N(()=>[F(t.$slots,`item`,{item:n,index:s,ui:T.value},()=>[F(t.$slots,`item-leading`,{item:n,index:s,ui:T.value},()=>[ue(n)&&n.icon?(L(),z(yE,{key:0,name:n.icon,"data-slot":`itemLeadingIcon`,class:A(T.value.itemLeadingIcon({class:[r.ui?.itemLeadingIcon,n.ui?.itemLeadingIcon]}))},null,8,[`name`,`class`])):ue(n)&&n.avatar?(L(),z(TE,U({key:1,size:n.ui?.itemLeadingAvatarSize||r.ui?.itemLeadingAvatarSize||T.value.itemLeadingAvatarSize()},{ref_for:!0},n.avatar,{"data-slot":`itemLeadingAvatar`,class:T.value.itemLeadingAvatar({class:[r.ui?.itemLeadingAvatar,n.ui?.itemLeadingAvatar]})}),null,16,[`size`,`class`])):ue(n)&&n.chip?(L(),z(CE,U({key:2,size:n.ui?.itemLeadingChipSize||r.ui?.itemLeadingChipSize||T.value.itemLeadingChipSize(),inset:``,standalone:``},{ref_for:!0},n.chip,{"data-slot":`itemLeadingChip`,class:T.value.itemLeadingChip({class:[r.ui?.itemLeadingChip,n.ui?.itemLeadingChip]})}),null,16,[`size`,`class`])):H(``,!0)]),B(`span`,{"data-slot":`itemWrapper`,class:A(T.value.itemWrapper({class:[r.ui?.itemWrapper,ue(n)&&n.ui?.itemWrapper]}))},[V(M(Zx),{"data-slot":`itemLabel`,class:A(T.value.itemLabel({class:[r.ui?.itemLabel,ue(n)&&n.ui?.itemLabel]}))},{default:N(()=>[F(t.$slots,`item-label`,{item:n,index:s},()=>[nc(ft(ue(n)?M(Vf)(n,r.labelKey):n),1)])]),_:2},1032,[`class`]),ue(n)&&(M(Vf)(n,r.descriptionKey)||a[`item-description`])?(L(),R(`span`,{key:0,"data-slot":`itemDescription`,class:A(T.value.itemDescription({class:[r.ui?.itemDescription,ue(n)&&n.ui?.itemDescription]}))},[F(t.$slots,`item-description`,{item:n,index:s},()=>[nc(ft(M(Vf)(n,r.descriptionKey)),1)])],2)):H(``,!0)],2),B(`span`,{"data-slot":`itemTrailing`,class:A(T.value.itemTrailing({class:[r.ui?.itemTrailing,ue(n)&&n.ui?.itemTrailing]}))},[F(t.$slots,`item-trailing`,{item:n,index:s,ui:T.value}),V(M(Xx),{"as-child":``},{default:N(()=>[V(yE,{name:e.selectedIcon||M(o).ui.icons.check,"data-slot":`itemTrailingIcon`,class:A(T.value.itemTrailingIcon({class:[r.ui?.itemTrailingIcon,ue(n)&&n.ui?.itemTrailingIcon]}))},null,8,[`name`,`class`])]),_:2},1024)],2)])]),_:2},1032,[`class`,`disabled`,`value`,`onSelect`]))],64))),128))]),_:2},1032,[`class`]))),128))],2),F(t.$slots,`content-bottom`),e.arrow?(L(),z(M(Bx),U({key:0},u.value,{"data-slot":`arrow`,class:T.value.arrow({class:r.ui?.arrow})}),null,16,[`class`])):H(``,!0)]),_:3},16,[`class`])]),_:3},16)]),_:3},16,[`name`,`autocomplete`,`disabled`,`default-value`,`model-value`]))}}),LW=P({__name:`dialog`,emits:[`close`],setup(e,{emit:t}){let n=t,r=qi(`form`),i=Ln({editor:BW.value.editor,openEditorTemplate:BW.value.openEditorTemplate});async function a(e){BW.value.editor=e.data.editor,BW.value.openEditorTemplate=e.data.openEditorTemplate,n(`close`)}return(e,t)=>{let n=IW,o=PW,s=TW,c=OW,l=yW,u=YE,d=lW;return L(),z(d,{close:!1,ui:{footer:`justify-end`}},{body:N(()=>[V(l,{state:i,schema:M(zW),onSubmit:a,ref_key:`form`,ref:r,class:`flex flex-col gap-6`,ui:{footer:`justify-end`}},{default:N(()=>[V(o,{label:`Editor`,description:`Select your preferred code editor for opening files.`,name:`editor`},{default:N(()=>[V(n,{items:[{label:`Code`,value:`vscode`,icon:`simple-icons:visualstudiocode`},{label:`Zed`,value:`zed`,icon:`simple-icons:zedindustries`},{label:`PhpStorm`,value:`phpstorm`,icon:`simple-icons:phpstorm`},{label:`Custom`,value:`custom`,icon:`tabler:settings`}],placeholder:`No editor selected`,icon:`tabler:code`,modelValue:i.editor,"onUpdate:modelValue":t[0]||=e=>i.editor=e,class:`mt-1 w-full`,required:``},null,8,[`modelValue`])]),_:1}),i.editor===`custom`?(L(),z(o,{key:0,label:`Editor command`,description:`The command used to open files in your selected editor. Use {file} as a placeholder for the file path, and {line} for the line number.`,name:`openEditorTemplate`},{default:N(()=>[V(s,{modelValue:i.openEditorTemplate,"onUpdate:modelValue":t[1]||=e=>i.openEditorTemplate=e,required:``,class:`w-full font-mono`,placeholder:`zed://file/{file}:{line}`},null,8,[`modelValue`])]),_:1})):H(``,!0),V(o,{label:`Color mode`},{default:N(()=>[V(c,{class:`w-full`})]),_:1})]),_:1},8,[`state`,`schema`])]),footer:N(({close:e})=>[V(u,{label:`Cancel`,variant:`secondary`,onClick:e},null,8,[`onClick`]),V(u,{type:`submit`,label:`Save`,onClick:t[2]||=e=>r.value?.submit()})]),_:1})}}});const RW=rD().create(LW),zW=sW({editor:sW(`"vscode" | "phpstorm" | "zed" | "custom" | undefined`).optional(),openEditorTemplate:sW(`string | undefined`).optional()}),BW=vf(`tempest:exceptions:settings`,{});async function VW(e,t){if(BW.value.editor||await RW.open(),!BW.value.editor){BS().add({title:`No editor configured in settings`,color:`warning`,progress:!1,icon:`tabler:exclamation-circle`});return}let n=encodeURIComponent(e),r=t??1,i={vscode:`vscode://file/{file}:{line}`,phpstorm:`phpstorm://open?file={file}&line={line}`,zed:`zed://file/{file}:{line}`,custom:BW.value.openEditorTemplate??``}[BW.value.editor]?.replace(`{file}`,n)?.replace(`{line}`,r.toString());window.open(i,`_self`)}var HW={class:`flex flex-col min-w-0 overflow-x-auto font-mono`},UW={class:`flex flex-col w-max min-w-full`},WW=[`onClick`],GW=[`textContent`],KW={class:`flex-1 pr-3 min-w-0`},qW=[`innerHTML`],JW={class:`opacity-0 group-hover:opacity-100 transition-all group-hover:-translate-x-3`},YW=P({__name:`code-snippet`,props:{snippet:{},file:{}},setup(e){let t=e,n=W(()=>Object.entries(t.snippet.lines).map(([e,n])=>({number:Number(e),code:n,highlighted:IN(n,`php`),isHighlighted:Number(e)===t.snippet.highlightedLine})));return(t,r)=>{let i=yE;return L(),R(`div`,HW,[B(`ul`,UW,[(L(!0),R(I,null,qa(n.value,t=>(L(),R(`li`,{key:t.number,class:A([`flex transition-colors items-center text-sm py-1 group cursor-pointer hover:bg-accented! grow w-full`,t.isHighlighted&&`bg-error-400/15`,!t.isHighlighted&&`even:bg-elevated dark:even:bg-accented/20`]),onClick:n=>M(VW)(e.file,t.number)},[B(`div`,{class:`px-3 w-12 text-dimmed text-right select-none shrink-0`,textContent:ft(t.number)},null,8,GW),B(`div`,KW,[B(`div`,{innerHTML:t.highlighted},null,8,qW)]),B(`div`,JW,[V(i,{name:`tabler:code`,class:`text-dimmed`})])],10,WW))),128))])])}}}),XW=[`textContent`],ZW=[`textContent`],QW=P({__name:`file-label`,props:{absoluteFile:{},relativeFile:{},line:{}},setup(e){let t=e,n=W(()=>!t.relativeFile||t.relativeFile.length>t.absoluteFile.length?t.absoluteFile:t.relativeFile);return(t,r)=>{let i=UN;return L(),z(i,{text:e.absoluteFile},{default:N(()=>[B(`code`,{class:`font-mono text-dimmed decoration-dashed decoration-transparent hover:decoration-neutral-500 underline underline-offset-4 truncate transition-colors cursor-pointer`,onClick:r[0]||=Mu(t=>M(VW)(e.absoluteFile,e.line),[`stop`])},[B(`span`,{textContent:ft(n.value)},null,8,XW),r[1]||=B(`span`,null,`:`,-1),B(`span`,{class:`text-muted`,textContent:ft(e.line)},null,8,ZW)])]),_:1},8,[`text`])}}}),$W={class:`font-mono`},eG={key:0,class:`whitespace-pre`},tG={key:0,textContent:` `},nG=[`innerHTML`],rG=[`innerHTML`],iG=P({__name:`symbol-call`,props:{frame:{},formatted:{type:Boolean}},setup(e){let t=e,n=W(()=>{if(!t.frame.class)return[{html:t.frame.function??``}];let e=[],n=`${t.frame.class}${t.frame.type??``}${t.frame.function??``}`,r=FN.getLastGrammarState(`${n}(`,{lang:`php`,theme:`tempest`}),i=FN.codeToHtml(n,{lang:`php`,theme:`tempest`}).match(/]*>(.*?)<\/code>/s);return e.push({html:i?.[1]??n}),e.push({html:`(`}),t.formatted&&t.frame.arguments.forEach((t,n)=>{n>0&&e.push({html:`, `}),e.push({html:`
`});let i=`${t.name}: ${t.compact}`,a=FN.codeToHtml(i,{lang:`php`,theme:`tempest`,grammarState:r}).match(/]*>(.*?)<\/code>/s);e.push({html:a?.[1]??i,argument:t})}),!t.formatted&&t.frame.arguments.length>0&&e.push({html:`...`}),t.formatted&&t.frame.arguments.length>0&&e.push({html:`
`}),e.push({html:`);`}),e});return(t,r)=>(L(),R(`span`,$W,[(L(!0),R(I,null,qa(n.value,(t,n)=>(L(),R(I,{key:n},[t.argument?(L(),R(`span`,eG,[e.formatted?(L(),R(`span`,tG)):H(``,!0),B(`span`,{innerHTML:t.html},null,8,nG)])):(L(),R(`span`,{key:1,innerHTML:t.html},null,8,rG))],64))),128))]))}}),aG={class:`mr-1 shrink-0`},oG={class:`flex justify-between items-center gap-x-4 grow`},sG={key:0,class:`bg-muted py-1 border-default border-t`},cG=P({__name:`application-frame`,props:{frame:{}},setup(e){return(t,n)=>{let r=$N;return L(),z(r,{class:`border border-default rounded-md overflow-hidden`,"default-open":``,disabled:!e.frame.snippet},{default:N(({open:t})=>[B(`div`,{class:A([`flex items-center gap-x-2 bg-accented/60 data-[state=open]:bg-accented/80 hover:bg-accented/80 px-4 py-3 text-dimmed text-sm transition-colors`,{"cursor-pointer":e.frame.snippet}])},[B(`div`,aG,[B(`div`,{class:A([`rounded-full size-2 shrink-0`,t?`bg-(--ui-text-muted)/80`:`bg-(--ui-text-dimmed)/60`])},null,2)]),B(`div`,oG,[V(iG,{frame:e.frame,class:`grow`},null,8,[`frame`]),e.frame.absoluteFile?(L(),z(QW,{key:0,class:`min-w-[20%] text-right shrink-0`,"relative-file":e.frame.relativeFile,"absolute-file":e.frame.absoluteFile,line:e.frame.line},null,8,[`relative-file`,`absolute-file`,`line`])):H(``,!0)])],2)]),content:N(()=>[e.frame.snippet?(L(),R(`div`,sG,[V(YW,{snippet:e.frame.snippet,file:e.frame.absoluteFile},null,8,[`snippet`,`file`])])):H(``,!0)]),_:1},8,[`disabled`])}}}),lG={class:`flex items-center gap-x-2 data-[state=open]:bg-accented/80 hover:bg-accented/80 px-4 py-3 text-dimmed text-sm transition-colors cursor-pointer`},uG={class:`flex justify-between items-center font-mono grow`},dG={class:`flex flex-col bg-muted border-default border-t overflow-x-scroll`},fG={class:`flex justify-between items-center`},pG={class:`font-mono text-xs`},mG=[`textContent`],hG=P({__name:`vendor-frames`,props:{frames:{}},setup(e){return(t,n)=>{let r=$N;return L(),z(r,{class:`group border border-default not-data-[state=open]:border-accented not-data-[state=open]:border-dashed rounded-md overflow-hidden`},{content:N(()=>[B(`div`,dG,[(L(!0),R(I,null,qa(e.frames,e=>(L(),R(`div`,{key:`vendor_${e.index}`,class:`flex flex-col gap-y-5 px-4 py-3 border-b border-b-default`},[V(iG,{frame:e,formatted:``,class:`text-sm`},null,8,[`frame`]),B(`div`,fG,[V(QW,{"relative-file":e.relativeFile,"absolute-file":e.absoluteFile,line:e.line,class:`text-xs`},null,8,[`relative-file`,`absolute-file`,`line`]),B(`span`,pG,[n[1]||=B(`span`,{class:`text-dimmed`},`#`,-1),B(`span`,{class:`text-muted`,textContent:ft(e.index)},null,8,mG)])])]))),128))])]),default:N(()=>[B(`div`,lG,[n[0]||=B(`div`,{class:`mr-1 shrink-0`},[B(`div`,{class:`rounded-full size-2 shrink-0 group-data-[state=open]:bg-(--ui-text-muted)/80 bg-(--ui-text-dimmed)/60`})],-1),B(`div`,uG,[B(`span`,null,ft(e.frames.length)+` vendor frames`,1)])])]),_:1})}}}),gG={class:`flex flex-col gap-y-2`},_G=P({__name:`stacktrace`,props:{exception:{}},setup(e){let t=e,n=W(()=>{let e=t.exception.stacktrace.frames,n=[];return e.forEach(e=>{let t=e.isVendor??!0?`vendor`:`application`,r=n[n.length-1];r&&r.type===t?r.frames.push(e):n.push({type:t,frames:[e]})}),n});return(e,t)=>(L(),z(_D,{title:`Stacktrace`,icon:`tabler:stack-2`},{default:N(()=>[B(`div`,gG,[(L(!0),R(I,null,qa(n.value,({type:e,frames:t},n)=>(L(),R(I,{key:`group_${n}_${e}`},[e===`application`?(L(!0),R(I,{key:0},qa(t,e=>(L(),z(cG,{key:`app_${e.index}`,frame:e},null,8,[`frame`]))),128)):H(``,!0),e===`vendor`?(L(),z(hG,{key:1,frames:t},null,8,[`frames`])):H(``,!0)],64))),128))])]),_:1}))}}),vG={slots:{base:`font-medium inline-flex items-center`,label:`truncate`,leadingIcon:`shrink-0`,leadingAvatar:`shrink-0`,leadingAvatarSize:``,trailingIcon:`shrink-0`},variants:{fieldGroup:{horizontal:`not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]`,vertical:`not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]`},color:{primary:``,secondary:``,success:``,info:``,warning:``,error:``,neutral:``},variant:{solid:``,outline:``,soft:``,subtle:``},size:{xs:{base:`text-[8px]/3 px-1 py-0.5 gap-1 rounded-sm`,leadingIcon:`size-3`,leadingAvatarSize:`3xs`,trailingIcon:`size-3`},sm:{base:`text-[10px]/3 px-1.5 py-1 gap-1 rounded-sm`,leadingIcon:`size-3`,leadingAvatarSize:`3xs`,trailingIcon:`size-3`},md:{base:`text-xs px-2 py-1 gap-1 rounded-md`,leadingIcon:`size-4`,leadingAvatarSize:`3xs`,trailingIcon:`size-4`},lg:{base:`text-sm px-2 py-1 gap-1.5 rounded-md`,leadingIcon:`size-5`,leadingAvatarSize:`2xs`,trailingIcon:`size-5`},xl:{base:`text-base px-2.5 py-1 gap-1.5 rounded-md`,leadingIcon:`size-6`,leadingAvatarSize:`2xs`,trailingIcon:`size-6`}},square:{true:``}},compoundVariants:[{color:`primary`,variant:`solid`,class:`bg-primary text-inverted`},{color:`secondary`,variant:`solid`,class:`bg-secondary text-inverted`},{color:`success`,variant:`solid`,class:`bg-success text-inverted`},{color:`info`,variant:`solid`,class:`bg-info text-inverted`},{color:`warning`,variant:`solid`,class:`bg-warning text-inverted`},{color:`error`,variant:`solid`,class:`bg-error text-inverted`},{color:`primary`,variant:`outline`,class:`text-primary ring ring-inset ring-primary/50`},{color:`secondary`,variant:`outline`,class:`text-secondary ring ring-inset ring-secondary/50`},{color:`success`,variant:`outline`,class:`text-success ring ring-inset ring-success/50`},{color:`info`,variant:`outline`,class:`text-info ring ring-inset ring-info/50`},{color:`warning`,variant:`outline`,class:`text-warning ring ring-inset ring-warning/50`},{color:`error`,variant:`outline`,class:`text-error ring ring-inset ring-error/50`},{color:`primary`,variant:`soft`,class:`bg-primary/10 text-primary`},{color:`secondary`,variant:`soft`,class:`bg-secondary/10 text-secondary`},{color:`success`,variant:`soft`,class:`bg-success/10 text-success`},{color:`info`,variant:`soft`,class:`bg-info/10 text-info`},{color:`warning`,variant:`soft`,class:`bg-warning/10 text-warning`},{color:`error`,variant:`soft`,class:`bg-error/10 text-error`},{color:`primary`,variant:`subtle`,class:`bg-primary/10 text-primary ring ring-inset ring-primary/25`},{color:`secondary`,variant:`subtle`,class:`bg-secondary/10 text-secondary ring ring-inset ring-secondary/25`},{color:`success`,variant:`subtle`,class:`bg-success/10 text-success ring ring-inset ring-success/25`},{color:`info`,variant:`subtle`,class:`bg-info/10 text-info ring ring-inset ring-info/25`},{color:`warning`,variant:`subtle`,class:`bg-warning/10 text-warning ring ring-inset ring-warning/25`},{color:`error`,variant:`subtle`,class:`bg-error/10 text-error ring ring-inset ring-error/25`},{color:`neutral`,variant:`solid`,class:`text-inverted bg-inverted`},{color:`neutral`,variant:`outline`,class:`ring ring-inset ring-accented text-default bg-default`},{color:`neutral`,variant:`soft`,class:`text-default bg-elevated`},{color:`neutral`,variant:`subtle`,class:`ring ring-inset ring-accented text-default bg-elevated`},{size:`xs`,square:!0,class:`p-0.5`},{size:`sm`,square:!0,class:`p-1`},{size:`md`,square:!0,class:`p-1`},{size:`lg`,square:!0,class:`p-1`},{size:`xl`,square:!0,class:`p-1`}],defaultVariants:{color:`primary`,variant:`solid`,size:`md`}},yG={__name:`Badge`,props:{as:{type:null,required:!1,default:`span`},label:{type:[String,Number],required:!1},color:{type:null,required:!1},variant:{type:null,required:!1},size:{type:null,required:!1},square:{type:Boolean,required:!1},class:{type:null,required:!1},ui:{type:null,required:!1},icon:{type:null,required:!1},avatar:{type:Object,required:!1},leading:{type:Boolean,required:!1},leadingIcon:{type:null,required:!1},trailing:{type:Boolean,required:!1},trailingIcon:{type:null,required:!1}},setup(e){let t=e,n=lo(),r=wf(),{orientation:i,size:a}=OE(t),{isLeading:o,isTrailing:s,leadingIconName:c,trailingIconName:l}=EE(t),u=W(()=>Mw({extend:Mw(vG),...r.ui?.badge||{}})({color:t.color,variant:t.variant,size:a.value||t.size,square:t.square||!n.default&&!t.label,fieldGroup:i.value}));return(n,r)=>(L(),z(M(K),{as:e.as,"data-slot":`base`,class:A(u.value.base({class:[t.ui?.base,t.class]}))},{default:N(()=>[F(n.$slots,`leading`,{ui:u.value},()=>[M(o)&&M(c)?(L(),z(yE,{key:0,name:M(c),"data-slot":`leadingIcon`,class:A(u.value.leadingIcon({class:t.ui?.leadingIcon}))},null,8,[`name`,`class`])):e.avatar?(L(),z(TE,U({key:1,size:t.ui?.leadingAvatarSize||u.value.leadingAvatarSize()},e.avatar,{"data-slot":`leadingAvatar`,class:u.value.leadingAvatar({class:t.ui?.leadingAvatar})}),null,16,[`size`,`class`])):H(``,!0)]),F(n.$slots,`default`,{ui:u.value},()=>[e.label!==void 0&&e.label!==null?(L(),R(`span`,{key:0,"data-slot":`label`,class:A(u.value.label({class:t.ui?.label}))},ft(e.label),3)):H(``,!0)]),F(n.$slots,`trailing`,{ui:u.value},()=>[M(s)&&M(l)?(L(),z(yE,{key:0,name:M(l),"data-slot":`trailingIcon`,class:A(u.value.trailingIcon({class:t.ui?.trailingIcon}))},null,8,[`name`,`class`])):H(``,!0)])]),_:3},8,[`as`,`class`]))}},bG={class:`flex flex-col gap-y-8`},xG={class:`flex justify-between items-center gap-x-6`},SG={class:`flex flex-col gap-y-2 min-w-0`},CG=[`textContent`],wG={class:`flex items-center gap-x-2`},TG={class:`flex flex-col items-end gap-2 font-mono shrink-0`},EG=[`textContent`],DG={class:`flex items-center gap-x-2`},OG=[`textContent`],kG=P({__name:`summary`,props:{exception:{},phpVersion:{},tempestVersion:{},uri:{},method:{},status:{},executionTime:{},memoryPeakUsage:{}},setup(e){let t=e;function n(e,t=0,n=!1){let r=n?1024:1e3,i=n?[`B`,`KiB`,`MiB`,`GiB`,`TiB`,`PiB`,`EiB`,`ZiB`,`YiB`,`RiB`,`QiB`]:[`B`,`KB`,`MB`,`GB`,`TB`,`PB`,`EB`,`ZB`,`YB`,`RB`,`QB`],a=0;for(;e/r>.9&&a{let a=UN,o=QW,s=yG;return L(),R(`div`,bG,[B(`div`,xG,[B(`div`,SG,[V(a,{text:e.exception.stacktrace.exceptionClass},{default:N(()=>[B(`span`,{class:`font-semibold text-3xl truncate`,textContent:ft(e.exception.stacktrace.exceptionClass)},null,8,CG)]),_:1},8,[`text`]),B(`div`,wG,[V(o,{"relative-file":e.exception.stacktrace.relativeFile,"absolute-file":e.exception.stacktrace.absoluteFile,line:e.exception.stacktrace.line},null,8,[`relative-file`,`absolute-file`,`line`])])]),B(`div`,TG,[V(M(WN),{source:e.uri},{default:N(({copy:t,copied:n})=>[V(a,{text:`Click to copy`},{default:N(()=>[V(s,{color:n?`success`:`neutral`,size:`md`,label:e.uri,onClick:t,class:`cursor-pointer select-none`},{leading:N(()=>[B(`span`,{class:`text-muted`,textContent:ft(e.method)},null,8,EG)]),_:1},8,[`color`,`label`,`onClick`])]),_:2},1024)]),_:1},8,[`source`]),B(`div`,DG,[V(s,{size:`md`,color:t.status<400?`neutral`:t.status>=400&&t.status<500?`warning`:`error`,label:t.status},{leading:N(()=>[...i[0]||=[B(`span`,{class:`text-muted`},`STATUS`,-1)]]),_:1},8,[`color`,`label`]),V(a,{text:`The total execution time from kernel boot to response was ${e.executionTime} milliseconds`},{default:N(()=>[V(s,{color:`neutral`,size:`md`,label:e.executionTime.toFixed()+` ms`},{leading:N(()=>[...i[1]||=[B(`span`,{class:`text-muted`},`TIME`,-1)]]),_:1},8,[`label`])]),_:1},8,[`text`]),V(a,{text:`The peak memory usage during execution was ${e.memoryPeakUsage} bytes`},{default:N(()=>[V(s,{color:`neutral`,size:`md`,label:n(e.memoryPeakUsage)},{leading:N(()=>[...i[2]||=[B(`span`,{class:`text-muted`},`MEM`,-1)]]),_:1},8,[`label`])]),_:1},8,[`text`]),V(s,{color:`neutral`,size:`md`,label:e.tempestVersion},{leading:N(()=>[...i[3]||=[B(`span`,{class:`text-muted`},`TEMPEST`,-1)]]),_:1},8,[`label`]),V(s,{color:`neutral`,size:`md`,label:e.phpVersion},{leading:N(()=>[...i[4]||=[B(`span`,{class:`text-muted`},`PHP`,-1)]]),_:1},8,[`label`])])])]),e.exception.stacktrace.message?(L(),R(`span`,{key:0,class:`font-light text-xl whitespace-pre-line`,textContent:ft(e.exception.stacktrace.message)},null,8,OG)):H(``,!0)])}}});const AG=Ln({step:`initializing`});function jG(e){AG.step=`ready`,AG.step===`ready`&&(AG.exception={...e,stacktrace:JSON.parse(e.stacktrace)},console.log(AG.exception))}var MG={key:0,class:`my-32`},NG=Hu(P({__name:`renderer`,setup(e){let t=qi(`background`);return _f(dD),bf(()=>AG.step===`initializing`?`Exception`:AG.exception.stacktrace.message),Aa(()=>t.value.style.backgroundImage=`url("${uD}")`),(e,n)=>{let r=YE,i=lD,a=sD,o=aD;return L(),z(o,null,{default:N(()=>[B(`div`,{ref_key:`background`,ref:t,class:`z-[-1] absolute inset-0 bg-repeat pointer-events-none`},null,512),V(a,null,{default:N(()=>[M(AG).step===`ready`?(L(),R(`main`,MG,[V(kG,{"tempest-version":M(AG).exception.versions.tempest,"php-version":M(AG).exception.versions.php,exception:M(AG).exception,uri:M(AG).exception.request.uri,method:M(AG).exception.request.method,status:M(AG).exception.response.status,"execution-time":M(AG).exception.resources.executionTimeMs,"memory-peak-usage":M(AG).exception.resources.memoryPeakUsage,class:`mb-4`},null,8,[`tempest-version`,`php-version`,`exception`,`uri`,`method`,`status`,`execution-time`,`memory-peak-usage`]),V(_G,{exception:M(AG).exception,class:`mt-12`},null,8,[`exception`]),V(zN,{context:M(AG).exception.context,class:`mt-12`},null,8,[`context`]),V(ZN,{body:M(AG).exception.request.body,class:`mt-12`},null,8,[`body`]),V(JN,{headers:M(AG).exception.request.headers,exception:M(AG).exception,class:`mt-12`},null,8,[`headers`,`exception`]),V(i,{class:`mt-12`},{default:N(()=>[V(r,{icon:`tabler:adjustments`,variant:`secondary`,class:`text-dimmed hover:text-highlighted`,onClick:n[0]||=e=>M(RW).open()})]),_:1})])):H(``,!0)]),_:1})]),_:1})}}})),PG=lh({routes:[],history:Om()}),FG=document.getElementById(`tempest-hydration`);if(!FG)throw Error(`Hydration element not found`);jG(JSON.parse(FG.textContent)),NG.use(PG),NG.use(_h),NG.mount(`#root`); \ No newline at end of file diff --git a/packages/router/src/Exceptions/local/dist/style.css b/packages/router/src/Exceptions/local/dist/style.css new file mode 100644 index 0000000000..eb3190bd0c --- /dev/null +++ b/packages/router/src/Exceptions/local/dist/style.css @@ -0,0 +1,3 @@ +/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-pan-x:initial;--tw-pan-y:initial;--tw-pinch-zoom:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Inter Variable",ui-sans-serif,system-ui;--font-mono:"JetBrains Mono Variable",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-neutral-50:var(--ui-color-neutral-50);--color-neutral-950:var(--ui-color-neutral-950);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--ease-out:cubic-bezier(0,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-old-neutral-50:oklch(98.5% 0 0);--color-old-neutral-100:oklch(97% 0 0);--color-old-neutral-200:oklch(92.2% 0 0);--color-old-neutral-300:oklch(87% 0 0);--color-old-neutral-400:oklch(70.8% 0 0);--color-old-neutral-500:oklch(55.6% 0 0);--color-old-neutral-600:oklch(43.9% 0 0);--color-old-neutral-700:oklch(37.1% 0 0);--color-old-neutral-800:oklch(26.9% 0 0);--color-old-neutral-900:oklch(20.5% 0 0);--color-old-neutral-950:oklch(14.5% 0 0);--code-background:transparent;--code-foreground:var(--ui-text-toned);--code-keyword:#4f95d1;--code-property:#37954e;--code-type:#d14f57;--code-value:#515248;--code-variable:#515248;--code-comment:#888;--code-highlight:#2dd3;--code-gutter:#555;--code-gutter-addition:#34a853;--code-gutter-deletion:#ea4334}:host,:root{--ui-header-height:calc(var(--spacing)*16)}.light,:host,:root{--ui-text-dimmed:var(--ui-color-neutral-400);--ui-text-muted:var(--ui-color-neutral-500);--ui-text-toned:var(--ui-color-neutral-600);--ui-text:var(--ui-color-neutral-700);--ui-text-highlighted:var(--ui-color-neutral-900);--ui-text-inverted:#fff;--ui-bg:#fff;--ui-bg-muted:var(--ui-color-neutral-50);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-200);--ui-bg-inverted:var(--ui-color-neutral-900);--ui-border:var(--ui-color-neutral-200);--ui-border-muted:var(--ui-color-neutral-200);--ui-border-accented:var(--ui-color-neutral-300);--ui-border-inverted:var(--ui-color-neutral-900);--ui-radius:.25rem;--ui-container:80rem}.dark{--ui-text-dimmed:var(--ui-color-neutral-500);--ui-text-muted:var(--ui-color-neutral-400);--ui-text-toned:var(--ui-color-neutral-300);--ui-text:var(--ui-color-neutral-200);--ui-text-highlighted:#fff;--ui-text-inverted:var(--ui-color-neutral-900);--ui-bg:var(--ui-color-neutral-900);--ui-bg-muted:var(--ui-color-neutral-800);--ui-bg-elevated:var(--ui-color-neutral-800);--ui-bg-accented:var(--ui-color-neutral-700);--ui-bg-inverted:#fff;--ui-border:var(--ui-color-neutral-800);--ui-border-muted:var(--ui-color-neutral-700);--ui-border-accented:var(--ui-color-neutral-700);--ui-border-inverted:#fff}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{background-color:var(--ui-bg);color:var(--ui-text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}body:where(.dark,.dark *){--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.not-sr-only{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-x-1{inset-inline:calc(var(--spacing)*1)}.inset-x-4{inset-inline:calc(var(--spacing)*4)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.inset-y-1{inset-block:calc(var(--spacing)*1)}.inset-y-1\.5{inset-block:calc(var(--spacing)*1.5)}.inset-y-2{inset-block:calc(var(--spacing)*2)}.inset-y-3{inset-block:calc(var(--spacing)*3)}.inset-y-4{inset-block:calc(var(--spacing)*4)}.-start-px{inset-inline-start:-1px}.start-0{inset-inline-start:calc(var(--spacing)*0)}.start-4{inset-inline-start:calc(var(--spacing)*4)}.start-32{inset-inline-start:calc(var(--spacing)*32)}.start-\[calc\(50\%\+16px\)\]{inset-inline-start:calc(50% + 16px)}.start-\[calc\(50\%\+20px\)\]{inset-inline-start:calc(50% + 20px)}.start-\[calc\(50\%\+28px\)\]{inset-inline-start:calc(50% + 28px)}.start-\[calc\(50\%\+32px\)\]{inset-inline-start:calc(50% + 32px)}.start-\[calc\(50\%\+36px\)\]{inset-inline-start:calc(50% + 36px)}.start-\[calc\(50\%-1px\)\]{inset-inline-start:calc(50% - 1px)}.-end-1\.5{inset-inline-end:calc(var(--spacing)*-1.5)}.end-0{inset-inline-end:calc(var(--spacing)*0)}.end-4{inset-inline-end:calc(var(--spacing)*4)}.end-\[calc\(-50\%\+16px\)\]{inset-inline-end:calc(16px - 50%)}.end-\[calc\(-50\%\+20px\)\]{inset-inline-end:calc(20px - 50%)}.end-\[calc\(-50\%\+28px\)\]{inset-inline-end:calc(28px - 50%)}.end-\[calc\(-50\%\+32px\)\]{inset-inline-end:calc(32px - 50%)}.end-\[calc\(-50\%\+36px\)\]{inset-inline-end:calc(36px - 50%)}.-top-1\.5{top:calc(var(--spacing)*-1.5)}.-top-8{top:calc(var(--spacing)*-8)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing)*4)}.top-\[30px\]{top:30px}.top-\[38px\]{top:38px}.top-\[46px\]{top:46px}.top-\[50\%\]{top:50%}.top-\[54px\]{top:54px}.top-\[62px\]{top:62px}.top-\[86\%\]{top:86%}.top-\[calc\(50\%-2px\)\]{top:calc(50% - 2px)}.top-full{top:100%}.right-0{right:calc(var(--spacing)*0)}.right-1\/2{right:50%}.right-4{right:calc(var(--spacing)*4)}.-bottom-7{bottom:calc(var(--spacing)*-7)}.-bottom-\[10px\]{bottom:-10px}.-bottom-px{bottom:-1px}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-4{bottom:calc(var(--spacing)*4)}.left-\(--reka-navigation-menu-viewport-left\){left:var(--reka-navigation-menu-viewport-left)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-4{left:calc(var(--spacing)*4)}.left-6\.5{left:calc(var(--spacing)*6.5)}.left-11{left:calc(var(--spacing)*11)}.isolate{isolation:isolate}.isolation-auto{isolation:auto}.z-\(--index\){z-index:var(--index)}.z-1{z-index:1}.z-50{z-index:50}.z-\[-1\]{z-index:-1}.z-\[1\]{z-index:1}.z-\[2\]{z-index:2}.z-\[100\]{z-index:100}.order-first{order:-9999}.order-last{order:9999}.col-start-1{grid-column-start:1}.row-start-1{grid-row-start:1}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-0\.5{margin:calc(var(--spacing)*.5)}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.-mx-4{margin-inline:calc(var(--spacing)*-4)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-auto{margin-inline:auto}.my-1{margin-block:calc(var(--spacing)*1)}.my-2{margin-block:calc(var(--spacing)*2)}.my-32{margin-block:calc(var(--spacing)*32)}.-ms-1\.5{margin-inline-start:calc(var(--spacing)*-1.5)}.-ms-4{margin-inline-start:calc(var(--spacing)*-4)}.-ms-\[8\.5px\]{margin-inline-start:-8.5px}.-ms-px{margin-inline-start:-1px}.ms-2{margin-inline-start:calc(var(--spacing)*2)}.ms-4{margin-inline-start:calc(var(--spacing)*4)}.ms-4\.5{margin-inline-start:calc(var(--spacing)*4.5)}.ms-5{margin-inline-start:calc(var(--spacing)*5)}.ms-5\.5{margin-inline-start:calc(var(--spacing)*5.5)}.ms-6{margin-inline-start:calc(var(--spacing)*6)}.ms-auto{margin-inline-start:auto}.-me-0\.5{margin-inline-end:calc(var(--spacing)*-.5)}.-me-1{margin-inline-end:calc(var(--spacing)*-1)}.-me-1\.5{margin-inline-end:calc(var(--spacing)*-1.5)}.-me-2{margin-inline-end:calc(var(--spacing)*-2)}.me-1\.5{margin-inline-end:calc(var(--spacing)*1.5)}.me-2{margin-inline-end:calc(var(--spacing)*2)}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.-mt-8{margin-top:calc(var(--spacing)*-8)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-2\.5{margin-top:calc(var(--spacing)*2.5)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-3\.5{margin-top:calc(var(--spacing)*3.5)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-24{margin-top:calc(var(--spacing)*24)}.mt-auto{margin-top:auto}.\!mr-4{margin-right:calc(var(--spacing)*4)!important}.mr-1{margin-right:calc(var(--spacing)*1)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-2\.5{margin-bottom:calc(var(--spacing)*2.5)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.mb-24{margin-bottom:calc(var(--spacing)*24)}.mb-auto{margin-bottom:auto}.\!ml-4{margin-left:calc(var(--spacing)*4)!important}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.inline-table{display:inline-table}.list-item{display:list-item}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row{display:table-row}.table-row-group{display:table-row-group}.aspect-\[16\/9\]{aspect-ratio:16/9}.aspect-square{aspect-ratio:1}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-2\.5{width:calc(var(--spacing)*2.5);height:calc(var(--spacing)*2.5)}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.size-10{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10)}.size-10\/12{width:83.3333%;height:83.3333%}.size-11{width:calc(var(--spacing)*11);height:calc(var(--spacing)*11)}.size-12{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12)}.size-14{width:calc(var(--spacing)*14);height:calc(var(--spacing)*14)}.size-full{width:100%;height:100%}.\!h-1\.5{height:calc(var(--spacing)*1.5)!important}.\!h-12{height:calc(var(--spacing)*12)!important}.h-\(--reka-navigation-menu-viewport-height\){height:var(--reka-navigation-menu-viewport-height)}.h-\(--reka-tabs-indicator-size\){height:var(--reka-tabs-indicator-size)}.h-\(--ui-header-height\){height:var(--ui-header-height)}.h-0\.5{height:calc(var(--spacing)*.5)}.h-1{height:calc(var(--spacing)*1)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-12{height:calc(var(--spacing)*12)}.h-38{height:calc(var(--spacing)*38)}.h-40{height:calc(var(--spacing)*40)}.h-42{height:calc(var(--spacing)*42)}.h-44{height:calc(var(--spacing)*44)}.h-46{height:calc(var(--spacing)*46)}.h-\[4px\]{height:4px}.h-\[5px\]{height:5px}.h-\[6px\]{height:6px}.h-\[7px\]{height:7px}.h-\[8px\]{height:8px}.h-\[9px\]{height:9px}.h-\[10px\]{height:10px}.h-\[11px\]{height:11px}.h-\[12px\]{height:12px}.h-\[fit-content\]{height:fit-content}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.max-h-60{max-height:calc(var(--spacing)*60)}.max-h-96{max-height:calc(var(--spacing)*96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[96\%\]{max-height:96%}.max-h-\[calc\(100\%-2rem\)\]{max-height:calc(100% - 2rem)}.max-h-\[calc\(100dvh-2rem\)\]{max-height:calc(100dvh - 2rem)}.max-h-full{max-height:100%}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-6{min-height:calc(var(--spacing)*6)}.min-h-8{min-height:calc(var(--spacing)*8)}.min-h-12{min-height:calc(var(--spacing)*12)}.min-h-16{min-height:calc(var(--spacing)*16)}.min-h-\[49px\]{min-height:49px}.min-h-\[calc\(100vh-var\(--ui-header-height\)\)\]{min-height:calc(100vh - var(--ui-header-height))}.min-h-fit{min-height:fit-content}.min-h-svh{min-height:100svh}.\!w-1\.5{width:calc(var(--spacing)*1.5)!important}.\!w-12{width:calc(var(--spacing)*12)!important}.w-\(--reka-combobox-trigger-width\){width:var(--reka-combobox-trigger-width)}.w-\(--reka-navigation-menu-indicator-size\){width:var(--reka-navigation-menu-indicator-size)}.w-\(--reka-select-trigger-width\){width:var(--reka-select-trigger-width)}.w-\(--reka-tabs-indicator-size\){width:var(--reka-tabs-indicator-size)}.w-\(--width\){width:var(--width)}.w-0{width:calc(var(--spacing)*0)}.w-0\.5{width:calc(var(--spacing)*.5)}.w-1{width:calc(var(--spacing)*1)}.w-2{width:calc(var(--spacing)*2)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-7{width:calc(var(--spacing)*7)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-11{width:calc(var(--spacing)*11)}.w-12{width:calc(var(--spacing)*12)}.w-32{width:calc(var(--spacing)*32)}.w-38{width:calc(var(--spacing)*38)}.w-40{width:calc(var(--spacing)*40)}.w-42{width:calc(var(--spacing)*42)}.w-44{width:calc(var(--spacing)*44)}.w-46{width:calc(var(--spacing)*46)}.w-60{width:calc(var(--spacing)*60)}.w-\[6px\]{width:6px}.w-\[7px\]{width:7px}.w-\[8px\]{width:8px}.w-\[9px\]{width:9px}.w-\[10px\]{width:10px}.w-\[calc\(100\%-2rem\)\]{width:calc(100% - 2rem)}.w-\[calc\(100vw-2rem\)\]{width:calc(100vw - 2rem)}.w-auto{width:auto}.w-full{width:100%}.w-max{width:max-content}.w-px{width:1px}.max-w-\(--ui-container\){max-width:var(--ui-container)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-60{max-width:calc(var(--spacing)*60)}.max-w-\[75\%\]{max-width:75%}.max-w-\[calc\(100\%-2rem\)\]{max-width:calc(100% - 2rem)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-5{min-width:calc(var(--spacing)*5)}.min-w-16{min-width:calc(var(--spacing)*16)}.min-w-32{min-width:calc(var(--spacing)*32)}.min-w-48{min-width:calc(var(--spacing)*48)}.min-w-\[4px\]{min-width:4px}.min-w-\[5px\]{min-width:5px}.min-w-\[6px\]{min-width:6px}.min-w-\[7px\]{min-width:7px}.min-w-\[8px\]{min-width:8px}.min-w-\[9px\]{min-width:9px}.min-w-\[10px\]{min-width:10px}.min-w-\[11px\]{min-width:11px}.min-w-\[12px\]{min-width:12px}.min-w-\[15\%\]{min-width:15%}.min-w-\[16px\]{min-width:16px}.min-w-\[20\%\]{min-width:20%}.min-w-\[20px\]{min-width:20px}.min-w-\[24px\]{min-width:24px}.min-w-fit{min-width:fit-content}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-full{flex-basis:100%}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-x-0{--tw-border-spacing-x:calc(var(--spacing)*0);border-spacing:var(--tw-border-spacing-x)var(--tw-border-spacing-y)}.origin-\(--reka-combobox-content-transform-origin\){transform-origin:var(--reka-combobox-content-transform-origin)}.origin-\(--reka-context-menu-content-transform-origin\){transform-origin:var(--reka-context-menu-content-transform-origin)}.origin-\(--reka-dropdown-menu-content-transform-origin\){transform-origin:var(--reka-dropdown-menu-content-transform-origin)}.origin-\(--reka-popover-content-transform-origin\){transform-origin:var(--reka-popover-content-transform-origin)}.origin-\(--reka-select-content-transform-origin\){transform-origin:var(--reka-select-content-transform-origin)}.origin-\(--reka-tooltip-content-transform-origin\){transform-origin:var(--reka-tooltip-content-transform-origin)}.origin-\[top_center\]{transform-origin:top}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-\[4px\]{--tw-translate-x:calc(4px*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\(--reka-navigation-menu-indicator-position\){--tw-translate-x:var(--reka-navigation-menu-indicator-position);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\(--reka-tabs-indicator-position\){--tw-translate-x:var(--reka-tabs-indicator-position);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-1\/2{--tw-translate-x:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\(--reka-tabs-indicator-position\){--tw-translate-y:var(--reka-tabs-indicator-position);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y:calc(1/2*100%);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-none{translate:none}.scale-80{--tw-scale-x:80%;--tw-scale-y:80%;--tw-scale-z:80%;scale:var(--tw-scale-x)var(--tw-scale-y)}.scale-3d{scale:var(--tw-scale-x)var(--tw-scale-y)var(--tw-scale-z)}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.transform-\(--transform\){transform:var(--transform)}.animate-\[marquee-vertical_var\(--duration\)_linear_infinite\]{animation:marquee-vertical var(--duration)linear infinite}.animate-\[marquee_var\(--duration\)_linear_infinite\]{animation:marquee var(--duration)linear infinite}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.touch-pinch-zoom{--tw-pinch-zoom:pinch-zoom;touch-action:var(--tw-pan-x,)var(--tw-pan-y,)var(--tw-pinch-zoom,)}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.scroll-mt-3{scroll-margin-top:calc(var(--spacing)*3)}.scroll-mt-4{scroll-margin-top:calc(var(--spacing)*4)}.scroll-py-1{scroll-padding-block:calc(var(--spacing)*1)}.appearance-none{appearance:none}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.grid-flow-col{grid-auto-flow:column}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.place-items-baseline{place-items:baseline}.place-items-center{place-items:center}.content-center{align-content:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-\(--gap\){gap:var(--gap)}.gap-0{gap:calc(var(--spacing)*0)}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-0\.25{gap:calc(var(--spacing)*.25)}.gap-0\.75{gap:calc(var(--spacing)*.75)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-3\.5{gap:calc(var(--spacing)*3.5)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}.gap-16{gap:calc(var(--spacing)*16)}:where(.-space-y-px>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(-1px*var(--tw-space-y-reverse));margin-block-end:calc(-1px*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-12>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*12)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*12)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-reverse>:not(:last-child)){--tw-space-y-reverse:1}.gap-x-1\.5{column-gap:calc(var(--spacing)*1.5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}.gap-x-2\.5{column-gap:calc(var(--spacing)*2.5)}.gap-x-3{column-gap:calc(var(--spacing)*3)}.gap-x-4{column-gap:calc(var(--spacing)*4)}.gap-x-6{column-gap:calc(var(--spacing)*6)}.gap-x-8{column-gap:calc(var(--spacing)*8)}:where(.-space-x-px>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(-1px*var(--tw-space-x-reverse));margin-inline-end:calc(-1px*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-reverse>:not(:last-child)){--tw-space-x-reverse:1}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.gap-y-1{row-gap:calc(var(--spacing)*1)}.gap-y-1\.5{row-gap:calc(var(--spacing)*1.5)}.gap-y-2{row-gap:calc(var(--spacing)*2)}.gap-y-2\.5{row-gap:calc(var(--spacing)*2.5)}.gap-y-3{row-gap:calc(var(--spacing)*3)}.gap-y-4{row-gap:calc(var(--spacing)*4)}.gap-y-5{row-gap:calc(var(--spacing)*5)}.gap-y-6{row-gap:calc(var(--spacing)*6)}.gap-y-8{row-gap:calc(var(--spacing)*8)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-reverse>:not(:last-child)){--tw-divide-y-reverse:1}:where(.divide-accented>:not(:last-child)){border-color:var(--ui-border-accented)}:where(.divide-default>:not(:last-child)){border-color:var(--ui-border)}.self-center{align-self:center}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.rounded{border-radius:.25rem}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:calc(var(--ui-radius)*2)}.rounded-md{border-radius:calc(var(--ui-radius)*1.5)}.rounded-sm{border-radius:var(--ui-radius)}.rounded-xl{border-radius:calc(var(--ui-radius)*3)}.rounded-xs{border-radius:calc(var(--ui-radius)*.5)}.rounded-s{border-start-start-radius:.25rem;border-end-start-radius:.25rem}.rounded-ss{border-start-start-radius:.25rem}.rounded-e{border-start-end-radius:.25rem;border-end-end-radius:.25rem}.rounded-se{border-start-end-radius:.25rem}.rounded-ee{border-end-end-radius:.25rem}.rounded-es{border-end-start-radius:.25rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-lg{border-top-left-radius:calc(var(--ui-radius)*2);border-top-right-radius:calc(var(--ui-radius)*2)}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-lg{border-top-left-radius:calc(var(--ui-radius)*2);border-bottom-left-radius:calc(var(--ui-radius)*2)}.rounded-tl{border-top-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-lg{border-top-right-radius:calc(var(--ui-radius)*2);border-bottom-right-radius:calc(var(--ui-radius)*2)}.rounded-tr{border-top-right-radius:.25rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-b-lg{border-bottom-right-radius:calc(var(--ui-radius)*2);border-bottom-left-radius:calc(var(--ui-radius)*2)}.rounded-br{border-bottom-right-radius:.25rem}.rounded-bl{border-bottom-left-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.border-s-\[2px\]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.border-s-\[3px\]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:3px}.border-s-\[4px\]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:4px}.border-s-\[5px\]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:5px}.border-e{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-\[2px\]{border-top-style:var(--tw-border-style);border-top-width:2px}.border-t-\[3px\]{border-top-style:var(--tw-border-style);border-top-width:3px}.border-t-\[4px\]{border-top-style:var(--tw-border-style);border-top-width:4px}.border-t-\[5px\]{border-top-style:var(--tw-border-style);border-top-width:5px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-solid{--tw-border-style:solid;border-style:solid}.border-accented{border-color:var(--ui-border-accented)}.border-bg{border-color:var(--ui-bg)}.border-default{border-color:var(--ui-border)}.border-error{border-color:var(--ui-error)}.border-info{border-color:var(--ui-info)}.border-inverted{border-color:var(--ui-border-inverted)}.border-muted{border-color:var(--ui-border-muted)}.border-primary{border-color:var(--ui-primary)}.border-secondary{border-color:var(--ui-secondary)}.border-success{border-color:var(--ui-success)}.border-transparent{border-color:#0000}.border-warning{border-color:var(--ui-warning)}.border-b-default{border-bottom-color:var(--ui-border)}.\!bg-accented{background-color:var(--ui-bg-accented)!important}.bg-\(--ui-border-accented\){background-color:var(--ui-border-accented)}.bg-\(--ui-text-dimmed\)\/60{background-color:var(--ui-text-dimmed)}@supports (color:color-mix(in lab, red, red)){.bg-\(--ui-text-dimmed\)\/60{background-color:color-mix(in oklab,var(--ui-text-dimmed)60%,transparent)}}.bg-\(--ui-text-muted\)\/80{background-color:var(--ui-text-muted)}@supports (color:color-mix(in lab, red, red)){.bg-\(--ui-text-muted\)\/80{background-color:color-mix(in oklab,var(--ui-text-muted)80%,transparent)}}.bg-accented,.bg-accented\/60{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.bg-accented\/60{background-color:color-mix(in oklab,var(--ui-bg-accented)60%,transparent)}}.bg-border{background-color:var(--ui-border)}.bg-default,.bg-default\/75{background-color:var(--ui-bg)}@supports (color:color-mix(in lab, red, red)){.bg-default\/75{background-color:color-mix(in oklab,var(--ui-bg)75%,transparent)}}.bg-default\/90{background-color:var(--ui-bg)}@supports (color:color-mix(in lab, red, red)){.bg-default\/90{background-color:color-mix(in oklab,var(--ui-bg)90%,transparent)}}.bg-elevated,.bg-elevated\/50{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.bg-elevated\/50{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.bg-elevated\/75{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.bg-elevated\/75{background-color:color-mix(in oklab,var(--ui-bg-elevated)75%,transparent)}}.bg-error{background-color:var(--ui-error)}.bg-error-400\/15{background-color:var(--ui-color-error-400)}@supports (color:color-mix(in lab, red, red)){.bg-error-400\/15{background-color:color-mix(in oklab,var(--ui-color-error-400)15%,transparent)}}.bg-error\/10{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.bg-error\/10{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.bg-info,.bg-info\/10{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.bg-info\/10{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.bg-inverted{background-color:var(--ui-bg-inverted)}.bg-muted{background-color:var(--ui-bg-muted)}.bg-primary,.bg-primary\/10{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.bg-secondary,.bg-secondary\/10{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.bg-secondary\/10{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.bg-success,.bg-success\/10{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.bg-success\/10{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.bg-transparent{background-color:#0000}.bg-warning,.bg-warning\/10{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.bg-warning\/10{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-default{--tw-gradient-from:var(--ui-bg);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.bg-repeat{background-repeat:repeat}.mask-no-clip{-webkit-mask-clip:no-clip;mask-clip:no-clip}.mask-repeat{-webkit-mask-repeat:repeat;mask-repeat:repeat}.fill-default{fill:var(--ui-border)}.object-cover{object-fit:cover}.object-top{object-position:top}.p-0{padding:calc(var(--spacing)*0)}.p-0\.5{padding:calc(var(--spacing)*.5)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-2\.5{padding:calc(var(--spacing)*2.5)}.p-3{padding:calc(var(--spacing)*3)}.p-3\.5{padding:calc(var(--spacing)*3.5)}.p-4{padding:calc(var(--spacing)*4)}.p-4\.5{padding:calc(var(--spacing)*4.5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-0{padding-inline:calc(var(--spacing)*0)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-3\.5{padding-block:calc(var(--spacing)*3.5)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.py-24{padding-block:calc(var(--spacing)*24)}.ps-1{padding-inline-start:calc(var(--spacing)*1)}.ps-1\.5{padding-inline-start:calc(var(--spacing)*1.5)}.ps-2{padding-inline-start:calc(var(--spacing)*2)}.ps-2\.5{padding-inline-start:calc(var(--spacing)*2.5)}.ps-3{padding-inline-start:calc(var(--spacing)*3)}.ps-4{padding-inline-start:calc(var(--spacing)*4)}.ps-7{padding-inline-start:calc(var(--spacing)*7)}.ps-8{padding-inline-start:calc(var(--spacing)*8)}.ps-9{padding-inline-start:calc(var(--spacing)*9)}.ps-10{padding-inline-start:calc(var(--spacing)*10)}.ps-11{padding-inline-start:calc(var(--spacing)*11)}.pe-1{padding-inline-end:calc(var(--spacing)*1)}.pe-2{padding-inline-end:calc(var(--spacing)*2)}.pe-2\.5{padding-inline-end:calc(var(--spacing)*2.5)}.pe-3{padding-inline-end:calc(var(--spacing)*3)}.pe-4\.5{padding-inline-end:calc(var(--spacing)*4.5)}.pe-5{padding-inline-end:calc(var(--spacing)*5)}.pe-5\.5{padding-inline-end:calc(var(--spacing)*5.5)}.pe-6{padding-inline-end:calc(var(--spacing)*6)}.pe-6\.5{padding-inline-end:calc(var(--spacing)*6.5)}.pe-7{padding-inline-end:calc(var(--spacing)*7)}.pe-7\.5{padding-inline-end:calc(var(--spacing)*7.5)}.pe-8{padding-inline-end:calc(var(--spacing)*8)}.pe-8\.5{padding-inline-end:calc(var(--spacing)*8.5)}.pe-9{padding-inline-end:calc(var(--spacing)*9)}.pe-10{padding-inline-end:calc(var(--spacing)*10)}.pe-11{padding-inline-end:calc(var(--spacing)*11)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pr-3{padding-right:calc(var(--spacing)*3)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-3\.5{padding-bottom:calc(var(--spacing)*3.5)}.pb-4\.5{padding-bottom:calc(var(--spacing)*4.5)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pb-5\.5{padding-bottom:calc(var(--spacing)*5.5)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pb-6\.5{padding-bottom:calc(var(--spacing)*6.5)}.pb-7{padding-bottom:calc(var(--spacing)*7)}.pb-7\.5{padding-bottom:calc(var(--spacing)*7.5)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-8\.5{padding-bottom:calc(var(--spacing)*8.5)}.pb-24{padding-bottom:calc(var(--spacing)*24)}.text-center{text-align:center}.text-end{text-align:end}.text-left{text-align:left}.text-right{text-align:right}.text-start{text-align:start}.align-middle{vertical-align:middle}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-\[8px\]\/3{font-size:8px;line-height:calc(var(--spacing)*3)}.text-\[10px\]\/3{font-size:10px;line-height:calc(var(--spacing)*3)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-base\/5{font-size:var(--text-base);line-height:calc(var(--spacing)*5)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-sm\/6{font-size:var(--text-sm);line-height:calc(var(--spacing)*6)}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-xs\/5{font-size:var(--text-xs);line-height:calc(var(--spacing)*5)}.text-\[4px\]{font-size:4px}.text-\[5px\]{font-size:5px}.text-\[6px\]{font-size:6px}.text-\[7px\]{font-size:7px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[15px\]{font-size:15px}.text-\[22px\]{font-size:22px}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.text-balance{text-wrap:balance}.text-pretty{text-wrap:pretty}.text-wrap{text-wrap:wrap}.break-words{overflow-wrap:break-word}.wrap-anywhere{overflow-wrap:anywhere}.text-clip{text-overflow:clip}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-line{white-space:pre-line}.text-default{color:var(--ui-text)}.text-dimmed{color:var(--ui-text-dimmed)}.text-error,.text-error\/75{color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.text-error\/75{color:color-mix(in oklab,var(--ui-error)75%,transparent)}}.text-highlighted{color:var(--ui-text-highlighted)}.text-info,.text-info\/75{color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.text-info\/75{color:color-mix(in oklab,var(--ui-info)75%,transparent)}}.text-inherit{color:inherit}.text-inverted{color:var(--ui-text-inverted)}.text-muted{color:var(--ui-text-muted)}.text-primary,.text-primary\/75{color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.text-primary\/75{color:color-mix(in oklab,var(--ui-primary)75%,transparent)}}.text-secondary,.text-secondary\/75{color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.text-secondary\/75{color:color-mix(in oklab,var(--ui-secondary)75%,transparent)}}.text-success,.text-success\/75{color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.text-success\/75{color:color-mix(in oklab,var(--ui-success)75%,transparent)}}.text-toned{color:var(--ui-text-toned)}.text-warning,.text-warning\/75{color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.text-warning\/75{color:color-mix(in oklab,var(--ui-warning)75%,transparent)}}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.diagonal-fractions{--tw-numeric-fraction:diagonal-fractions;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.lining-nums{--tw-numeric-figure:lining-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.oldstyle-nums{--tw-numeric-figure:oldstyle-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.ordinal{--tw-ordinal:ordinal;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.proportional-nums{--tw-numeric-spacing:proportional-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.slashed-zero{--tw-slashed-zero:slashed-zero;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.stacked-fractions{--tw-numeric-fraction:stacked-fractions;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.normal-nums{font-variant-numeric:normal}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.decoration-transparent{text-decoration-color:#0000}.decoration-dashed{text-decoration-style:dashed}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subpixel-antialiased{-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.opacity-0{opacity:0}.opacity-75{opacity:.75}.opacity-90{opacity:.9}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-3{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.inset-ring{--tw-inset-ring-shadow:inset 0 0 0 1px var(--tw-inset-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-accented{--tw-ring-color:var(--ui-border-accented)}.ring-bg{--tw-ring-color:var(--ui-bg)}.ring-default{--tw-ring-color:var(--ui-border)}.ring-error,.ring-error\/25{--tw-ring-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.ring-error\/25{--tw-ring-color:color-mix(in oklab,var(--ui-error)25%,transparent)}}.ring-error\/50{--tw-ring-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.ring-error\/50{--tw-ring-color:color-mix(in oklab,var(--ui-error)50%,transparent)}}.ring-info,.ring-info\/25{--tw-ring-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.ring-info\/25{--tw-ring-color:color-mix(in oklab,var(--ui-info)25%,transparent)}}.ring-info\/50{--tw-ring-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.ring-info\/50{--tw-ring-color:color-mix(in oklab,var(--ui-info)50%,transparent)}}.ring-inverted{--tw-ring-color:var(--ui-border-inverted)}.ring-primary,.ring-primary\/25{--tw-ring-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.ring-primary\/25{--tw-ring-color:color-mix(in oklab,var(--ui-primary)25%,transparent)}}.ring-primary\/50{--tw-ring-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.ring-primary\/50{--tw-ring-color:color-mix(in oklab,var(--ui-primary)50%,transparent)}}.ring-secondary,.ring-secondary\/25{--tw-ring-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.ring-secondary\/25{--tw-ring-color:color-mix(in oklab,var(--ui-secondary)25%,transparent)}}.ring-secondary\/50{--tw-ring-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.ring-secondary\/50{--tw-ring-color:color-mix(in oklab,var(--ui-secondary)50%,transparent)}}.ring-success,.ring-success\/25{--tw-ring-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.ring-success\/25{--tw-ring-color:color-mix(in oklab,var(--ui-success)25%,transparent)}}.ring-success\/50{--tw-ring-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.ring-success\/50{--tw-ring-color:color-mix(in oklab,var(--ui-success)50%,transparent)}}.ring-warning,.ring-warning\/25{--tw-ring-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.ring-warning\/25{--tw-ring-color:color-mix(in oklab,var(--ui-warning)25%,transparent)}}.ring-warning\/50{--tw-ring-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.ring-warning\/50{--tw-ring-color:color-mix(in oklab,var(--ui-warning)50%,transparent)}}.ring-white{--tw-ring-color:var(--color-white)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a))drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a)drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter\!{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)!important}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-grayscale{--tw-backdrop-grayscale:grayscale(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-invert{--tw-backdrop-invert:invert(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-sepia{--tw-backdrop-sepia:sepia(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[background\]{transition-property:background;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,opacity\]{transition-property:color,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,translate\,height\]{transition-property:transform,translate,height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[translate\,width\]{transition-property:translate,width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\,height\,left\]{transition-property:width,height,left;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.will-change-\[height\]{will-change:height}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\!\[animation-direction\:reverse\]{animation-direction:reverse!important}.\[--duration\:20s\]{--duration:20s}.\[--gap\:--spacing\(16\)\]{--gap:calc(var(--spacing)*16)}.\[--initial-transform\:calc\(100\%\+1\.5rem\)\]{--initial-transform:calc(100% + 1.5rem)}.\[--spotlight-color\:var\(--ui-bg-inverted\)\]{--spotlight-color:var(--ui-bg-inverted)}.\[--spotlight-color\:var\(--ui-error\)\]{--spotlight-color:var(--ui-error)}.\[--spotlight-color\:var\(--ui-info\)\]{--spotlight-color:var(--ui-info)}.\[--spotlight-color\:var\(--ui-primary\)\]{--spotlight-color:var(--ui-primary)}.\[--spotlight-color\:var\(--ui-secondary\)\]{--spotlight-color:var(--ui-secondary)}.\[--spotlight-color\:var\(--ui-success\)\]{--spotlight-color:var(--ui-success)}.\[--spotlight-color\:var\(--ui-warning\)\]{--spotlight-color:var(--ui-warning)}.\[--spotlight-size\:400px\]{--spotlight-size:400px}.backface-hidden{backface-visibility:hidden}:where(.divide-x-reverse>:not(:last-child)){--tw-divide-x-reverse:1}.ring-inset{--tw-ring-inset:inset}:is(.\*\:my-5>*){margin-block:calc(var(--spacing)*5)}:is(.\*\:size-2>*){width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}:is(.\*\:break-inside-avoid-column>*){break-inside:avoid-column}:is(.\*\:rounded-full>*){border-radius:3.40282e38px}:is(.\*\:bg-elevated>*){background-color:var(--ui-bg-elevated)}:is(.\*\:pt-8>*){padding-top:calc(var(--spacing)*8)}:is(.\*\:will-change-transform>*){will-change:transform}.not-last\:not-first\:rounded-none:not(:last-child):not(:first-child){border-radius:0}.not-disabled\:cursor-pointer:not(:disabled){cursor:pointer}.not-data-\[segment\=literal\]\:w-6:not([data-segment=literal]){width:calc(var(--spacing)*6)}.not-data-\[segment\=literal\]\:w-7:not([data-segment=literal]){width:calc(var(--spacing)*7)}.not-data-\[segment\=literal\]\:w-8:not([data-segment=literal]){width:calc(var(--spacing)*8)}.not-data-\[state\=open\]\:border-dashed:not([data-state=open]){--tw-border-style:dashed;border-style:dashed}.not-data-\[state\=open\]\:border-accented:not([data-state=open]){border-color:var(--ui-border-accented)}.group-not-last\:group-not-first\:rounded-none:is(:where(.group):not(:last-child) *):is(:where(.group):not(:first-child) *){border-radius:0}.group-not-only\:group-first\:rounded-e-none:is(:where(.group):not(:only-child) *):is(:where(.group):first-child *){border-start-end-radius:0;border-end-end-radius:0}.group-not-only\:group-first\:rounded-b-none:is(:where(.group):not(:only-child) *):is(:where(.group):first-child *){border-bottom-right-radius:0;border-bottom-left-radius:0}.group-not-only\:group-last\:rounded-s-none:is(:where(.group):not(:only-child) *):is(:where(.group):last-child *){border-start-start-radius:0;border-end-start-radius:0}.group-not-only\:group-last\:rounded-t-none:is(:where(.group):not(:only-child) *):is(:where(.group):last-child *){border-top-left-radius:0;border-top-right-radius:0}@media (hover:hover){.group-hover\:-translate-x-3:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing)*-3);translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:bg-primary:is(:where(.group):hover *){background-color:var(--ui-primary)}.group-hover\:text-default:is(:where(.group):hover *){color:var(--ui-text)}.group-hover\:text-inverted:is(:where(.group):hover *){color:var(--ui-text-inverted)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.group-hover\:ring-primary:is(:where(.group):hover *){--tw-ring-color:var(--ui-primary)}.group-hover\:\[animation-play-state\:paused\]:is(:where(.group):hover *){animation-play-state:paused}.group-hover\/blog-post\:scale-110:is(:where(.group\/blog-post):hover *){--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-hover\/blog-post\:rounded-r-none:is(:where(.group\/blog-post):hover *){border-top-right-radius:0;border-bottom-right-radius:0}.group-hover\/blog-post\:rounded-b-none:is(:where(.group\/blog-post):hover *){border-bottom-right-radius:0;border-bottom-left-radius:0}.group-hover\/blog-post\:shadow-none:is(:where(.group\/blog-post):hover *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-hover\/changelog-version-image\:scale-105:is(:where(.group\/changelog-version-image):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-hover\/message\:opacity-100:is(:where(.group\/message):hover *){opacity:1}.group-hover\/user\:scale-115:is(:where(.group\/user):hover *){--tw-scale-x:115%;--tw-scale-y:115%;--tw-scale-z:115%;scale:var(--tw-scale-x)var(--tw-scale-y)}}.group-has-focus-visible\/changelog-version-image\:scale-105:is(:where(.group\/changelog-version-image):has(:focus-visible) *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-has-focus-visible\/user\:scale-115:is(:where(.group\/user):has(:focus-visible) *){--tw-scale-x:115%;--tw-scale-y:115%;--tw-scale-z:115%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-data-expanded\:rotate-180:is(:where(.group)[data-expanded] *){rotate:180deg}.group-data-highlighted\:inline-flex:is(:where(.group)[data-highlighted] *){display:inline-flex}.group-data-highlighted\:text-default:is(:where(.group)[data-highlighted] *){color:var(--ui-text)}.group-data-highlighted\:text-error:is(:where(.group)[data-highlighted] *){color:var(--ui-error)}.group-data-highlighted\:text-info:is(:where(.group)[data-highlighted] *){color:var(--ui-info)}.group-data-highlighted\:text-primary:is(:where(.group)[data-highlighted] *){color:var(--ui-primary)}.group-data-highlighted\:text-secondary:is(:where(.group)[data-highlighted] *){color:var(--ui-secondary)}.group-data-highlighted\:text-success:is(:where(.group)[data-highlighted] *){color:var(--ui-success)}.group-data-highlighted\:text-warning:is(:where(.group)[data-highlighted] *){color:var(--ui-warning)}.group-data-highlighted\:not-group-data-disabled\:text-default:is(:where(.group)[data-highlighted] *):not(:is(:where(.group)[data-disabled] *)){color:var(--ui-text)}.group-data-\[disabled\]\:opacity-75:is(:where(.group)[data-disabled] *){opacity:.75}.group-data-\[state\=active\]\:bg-error:is(:where(.group)[data-state=active] *){background-color:var(--ui-error)}.group-data-\[state\=active\]\:bg-info:is(:where(.group)[data-state=active] *){background-color:var(--ui-info)}.group-data-\[state\=active\]\:bg-inverted:is(:where(.group)[data-state=active] *){background-color:var(--ui-bg-inverted)}.group-data-\[state\=active\]\:bg-primary:is(:where(.group)[data-state=active] *){background-color:var(--ui-primary)}.group-data-\[state\=active\]\:bg-secondary:is(:where(.group)[data-state=active] *){background-color:var(--ui-secondary)}.group-data-\[state\=active\]\:bg-success:is(:where(.group)[data-state=active] *){background-color:var(--ui-success)}.group-data-\[state\=active\]\:bg-warning:is(:where(.group)[data-state=active] *){background-color:var(--ui-warning)}.group-data-\[state\=active\]\:text-inverted:is(:where(.group)[data-state=active] *){color:var(--ui-text-inverted)}.group-data-\[state\=checked\]\:text-error:is(:where(.group)[data-state=checked] *){color:var(--ui-error)}.group-data-\[state\=checked\]\:text-highlighted:is(:where(.group)[data-state=checked] *){color:var(--ui-text-highlighted)}.group-data-\[state\=checked\]\:text-info:is(:where(.group)[data-state=checked] *){color:var(--ui-info)}.group-data-\[state\=checked\]\:text-primary:is(:where(.group)[data-state=checked] *){color:var(--ui-primary)}.group-data-\[state\=checked\]\:text-secondary:is(:where(.group)[data-state=checked] *){color:var(--ui-secondary)}.group-data-\[state\=checked\]\:text-success:is(:where(.group)[data-state=checked] *){color:var(--ui-success)}.group-data-\[state\=checked\]\:text-warning:is(:where(.group)[data-state=checked] *){color:var(--ui-warning)}.group-data-\[state\=checked\]\:opacity-100:is(:where(.group)[data-state=checked] *){opacity:1}.group-data-\[state\=completed\]\:bg-error:is(:where(.group)[data-state=completed] *){background-color:var(--ui-error)}.group-data-\[state\=completed\]\:bg-info:is(:where(.group)[data-state=completed] *){background-color:var(--ui-info)}.group-data-\[state\=completed\]\:bg-inverted:is(:where(.group)[data-state=completed] *){background-color:var(--ui-bg-inverted)}.group-data-\[state\=completed\]\:bg-primary:is(:where(.group)[data-state=completed] *){background-color:var(--ui-primary)}.group-data-\[state\=completed\]\:bg-secondary:is(:where(.group)[data-state=completed] *){background-color:var(--ui-secondary)}.group-data-\[state\=completed\]\:bg-success:is(:where(.group)[data-state=completed] *){background-color:var(--ui-success)}.group-data-\[state\=completed\]\:bg-warning:is(:where(.group)[data-state=completed] *){background-color:var(--ui-warning)}.group-data-\[state\=completed\]\:text-inverted:is(:where(.group)[data-state=completed] *){color:var(--ui-text-inverted)}.group-data-\[state\=open\]\:rotate-180:is(:where(.group)[data-state=open] *){rotate:180deg}.group-data-\[state\=open\]\:bg-\(--ui-text-muted\)\/80:is(:where(.group)[data-state=open] *){background-color:var(--ui-text-muted)}@supports (color:color-mix(in lab, red, red)){.group-data-\[state\=open\]\:bg-\(--ui-text-muted\)\/80:is(:where(.group)[data-state=open] *){background-color:color-mix(in oklab,var(--ui-text-muted)80%,transparent)}}.group-data-\[state\=open\]\:text-default:is(:where(.group)[data-state=open] *){color:var(--ui-text)}.group-data-\[state\=open\]\:text-error:is(:where(.group)[data-state=open] *){color:var(--ui-error)}.group-data-\[state\=open\]\:text-highlighted:is(:where(.group)[data-state=open] *){color:var(--ui-text-highlighted)}.group-data-\[state\=open\]\:text-info:is(:where(.group)[data-state=open] *){color:var(--ui-info)}.group-data-\[state\=open\]\:text-primary:is(:where(.group)[data-state=open] *){color:var(--ui-primary)}.group-data-\[state\=open\]\:text-secondary:is(:where(.group)[data-state=open] *){color:var(--ui-secondary)}.group-data-\[state\=open\]\:text-success:is(:where(.group)[data-state=open] *){color:var(--ui-success)}.group-data-\[state\=open\]\:text-warning:is(:where(.group)[data-state=open] *){color:var(--ui-warning)}.group-data-\[state\=unchecked\]\:text-dimmed:is(:where(.group)[data-state=unchecked] *){color:var(--ui-text-dimmed)}.group-data-\[state\=unchecked\]\:opacity-100:is(:where(.group)[data-state=unchecked] *){opacity:1}@media (hover:hover){.peer-hover\:text-highlighted:is(:where(.peer):hover~*){color:var(--ui-text-highlighted)}.peer-hover\:text-toned:is(:where(.peer):hover~*){color:var(--ui-text-toned)}}.peer-focus-visible\:text-highlighted:is(:where(.peer):focus-visible~*){color:var(--ui-text-highlighted)}.peer-focus-visible\:text-toned:is(:where(.peer):focus-visible~*){color:var(--ui-text-toned)}.selection\:bg-primary\/20 ::selection{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-primary\/20 ::selection{background-color:color-mix(in oklab,var(--ui-primary)20%,transparent)}}.selection\:bg-primary\/20::selection{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-primary\/20::selection{background-color:color-mix(in oklab,var(--ui-primary)20%,transparent)}}.file\:me-1\.5::file-selector-button{margin-inline-end:calc(var(--spacing)*1.5)}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-muted::file-selector-button{color:var(--ui-text-muted)}.file\:outline-none::file-selector-button{--tw-outline-style:none;outline-style:none}.placeholder\:text-dimmed::placeholder{color:var(--ui-text-dimmed)}.before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:-inset-px:before{content:var(--tw-content);inset:-1px}.before\:inset-px:before{content:var(--tw-content);inset:1px}.before\:inset-x-0:before{content:var(--tw-content);inset-inline:calc(var(--spacing)*0)}.before\:inset-x-px:before{content:var(--tw-content);inset-inline:1px}.before\:inset-y-0:before{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.before\:inset-y-px:before{content:var(--tw-content);inset-block:1px}.before\:top-0:before{content:var(--tw-content);top:calc(var(--spacing)*0)}.before\:-right-1\.5:before{content:var(--tw-content);right:calc(var(--spacing)*-1.5)}.before\:-left-1\.5:before{content:var(--tw-content);left:calc(var(--spacing)*-1.5)}.before\:left-0:before{content:var(--tw-content);left:calc(var(--spacing)*0)}.before\:z-1:before{content:var(--tw-content);z-index:1}.before\:z-2:before{content:var(--tw-content);z-index:2}.before\:z-\[-1\]:before{content:var(--tw-content);z-index:-1}.before\:h-1\/3:before{content:var(--tw-content);height:33.3333%}.before\:h-full:before{content:var(--tw-content);height:100%}.before\:w-1\/3:before{content:var(--tw-content);width:33.3333%}.before\:w-full:before{content:var(--tw-content);width:100%}.before\:rounded-\[inherit\]:before{content:var(--tw-content);border-radius:inherit}.before\:rounded-md:before{content:var(--tw-content);border-radius:calc(var(--ui-radius)*1.5)}.before\:bg-elevated:before,.before\:bg-elevated\/75:before{content:var(--tw-content);background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.before\:bg-elevated\/75:before{background-color:color-mix(in oklab,var(--ui-bg-elevated)75%,transparent)}}.before\:bg-error\/10:before{content:var(--tw-content);background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.before\:bg-error\/10:before{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.before\:bg-info\/10:before{content:var(--tw-content);background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.before\:bg-info\/10:before{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.before\:bg-primary\/10:before{content:var(--tw-content);background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.before\:bg-primary\/10:before{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.before\:bg-secondary\/10:before{content:var(--tw-content);background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.before\:bg-secondary\/10:before{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.before\:bg-success\/10:before{content:var(--tw-content);background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.before\:bg-success\/10:before{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.before\:bg-warning\/10:before{content:var(--tw-content);background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.before\:bg-warning\/10:before{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.before\:bg-gradient-to-b:before{content:var(--tw-content);--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.before\:bg-gradient-to-r:before{content:var(--tw-content);--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.before\:bg-\[radial-gradient\(var\(--spotlight-size\)_var\(--spotlight-size\)_at_calc\(var\(--spotlight-x\,0px\)\)_calc\(var\(--spotlight-y\,0px\)\)\,var\(--spotlight-color\)\,transparent_70\%\)\]:before{content:var(--tw-content);background-image:radial-gradient(var(--spotlight-size)var(--spotlight-size)at calc(var(--spotlight-x,0px))calc(var(--spotlight-y,0px)),var(--spotlight-color),transparent 70%)}.before\:from-default:before{content:var(--tw-content);--tw-gradient-from:var(--ui-bg);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.before\:to-transparent:before{content:var(--tw-content);--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.before\:transition-colors:before{content:var(--tw-content);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.not-first-of-type\:before\:me-0\.5:not(:first-of-type):before{content:var(--tw-content);margin-inline-end:calc(var(--spacing)*.5)}.not-first-of-type\:before\:content-\[\'·\'\]:not(:first-of-type):before{--tw-content:"·";content:var(--tw-content)}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-x-0:after{content:var(--tw-content);inset-inline:calc(var(--spacing)*0)}.after\:inset-x-2\.5:after{content:var(--tw-content);inset-inline:calc(var(--spacing)*2.5)}.after\:inset-y-0:after{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.after\:inset-y-0\.5:after{content:var(--tw-content);inset-block:calc(var(--spacing)*.5)}.after\:-start-1\.5:after{content:var(--tw-content);inset-inline-start:calc(var(--spacing)*-1.5)}.after\:right-0:after{content:var(--tw-content);right:calc(var(--spacing)*0)}.after\:-bottom-2:after{content:var(--tw-content);bottom:calc(var(--spacing)*-2)}.after\:bottom-0:after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}.after\:z-1:after{content:var(--tw-content);z-index:1}.after\:z-2:after{content:var(--tw-content);z-index:2}.after\:ms-0\.5:after{content:var(--tw-content);margin-inline-start:calc(var(--spacing)*.5)}.after\:block:after{content:var(--tw-content);display:block}.after\:hidden:after{content:var(--tw-content);display:none}.after\:size-1:after{content:var(--tw-content);width:calc(var(--spacing)*1);height:calc(var(--spacing)*1)}.after\:size-1\.5:after{content:var(--tw-content);width:calc(var(--spacing)*1.5);height:calc(var(--spacing)*1.5)}.after\:size-2:after{content:var(--tw-content);width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.after\:h-1\/3:after{content:var(--tw-content);height:33.3333%}.after\:h-full:after{content:var(--tw-content);height:100%}.after\:h-px:after{content:var(--tw-content);height:1px}.after\:w-1\/3:after{content:var(--tw-content);width:33.3333%}.after\:w-full:after{content:var(--tw-content);width:100%}.after\:w-px:after{content:var(--tw-content);width:1px}.after\:animate-\[carousel-inverse_2s_ease-in-out_infinite\]:after{content:var(--tw-content);animation:2s ease-in-out infinite carousel-inverse}.after\:animate-\[carousel_2s_ease-in-out_infinite\]:after{content:var(--tw-content);animation:2s ease-in-out infinite carousel}.after\:animate-\[elastic_2s_ease-in-out_infinite\]:after{content:var(--tw-content);animation:2s ease-in-out infinite elastic}.after\:animate-\[swing_2s_ease-in-out_infinite\]:after{content:var(--tw-content);animation:2s ease-in-out infinite swing}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:bg-default:after{content:var(--tw-content);background-color:var(--ui-bg)}.after\:bg-error:after{content:var(--tw-content);background-color:var(--ui-error)}.after\:bg-info:after{content:var(--tw-content);background-color:var(--ui-info)}.after\:bg-inverted:after{content:var(--tw-content);background-color:var(--ui-bg-inverted)}.after\:bg-primary:after{content:var(--tw-content);background-color:var(--ui-primary)}.after\:bg-secondary:after{content:var(--tw-content);background-color:var(--ui-secondary)}.after\:bg-success:after{content:var(--tw-content);background-color:var(--ui-success)}.after\:bg-warning:after{content:var(--tw-content);background-color:var(--ui-warning)}.after\:bg-gradient-to-l:after{content:var(--tw-content);--tw-gradient-position:to left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.after\:bg-gradient-to-t:after{content:var(--tw-content);--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.after\:from-default:after{content:var(--tw-content);--tw-gradient-from:var(--ui-bg);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.after\:to-transparent:after{content:var(--tw-content);--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.after\:text-error:after{content:var(--tw-content);color:var(--ui-error)}.after\:transition-colors:after{content:var(--tw-content);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\'\*\'\]:after{--tw-content:"*";content:var(--tw-content)}.after\:content-\[\\\"\\\"\]:after{--tw-content:\"\";content:var(--tw-content)}:is(.\*\:not-last\:after\:absolute>*):not(:last-child):after{content:var(--tw-content);position:absolute}:is(.\*\:not-last\:after\:inset-x-1>*):not(:last-child):after{content:var(--tw-content);inset-inline:calc(var(--spacing)*1)}:is(.\*\:not-last\:after\:bottom-0>*):not(:last-child):after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}:is(.\*\:not-last\:after\:h-px>*):not(:last-child):after{content:var(--tw-content);height:1px}:is(.\*\:not-last\:after\:bg-border>*):not(:last-child):after{content:var(--tw-content);background-color:var(--ui-border)}.first\:me-0:first-child{margin-inline-end:calc(var(--spacing)*0)}:is(.\*\:first\:mt-0>*):first-child{margin-top:calc(var(--spacing)*0)}.not-only\:first\:rounded-e-none:not(:only-child):first-child{border-start-end-radius:0;border-end-end-radius:0}.not-only\:first\:rounded-b-none:not(:only-child):first-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}:is(.\*\:last\:mb-0>*):last-child{margin-bottom:calc(var(--spacing)*0)}.not-only\:last\:rounded-s-none:not(:only-child):last-child{border-start-start-radius:0;border-end-start-radius:0}.not-only\:last\:rounded-t-none:not(:only-child):last-child{border-top-left-radius:0;border-top-right-radius:0}.even\:bg-elevated:nth-child(2n){background-color:var(--ui-bg-elevated)}.first-of-type\:rounded-s-lg:first-of-type{border-start-start-radius:calc(var(--ui-radius)*2);border-end-start-radius:calc(var(--ui-radius)*2)}.first-of-type\:rounded-t-lg:first-of-type{border-top-left-radius:calc(var(--ui-radius)*2);border-top-right-radius:calc(var(--ui-radius)*2)}.last-of-type\:rounded-e-lg:last-of-type{border-start-end-radius:calc(var(--ui-radius)*2);border-end-end-radius:calc(var(--ui-radius)*2)}.last-of-type\:rounded-b-lg:last-of-type{border-bottom-right-radius:calc(var(--ui-radius)*2);border-bottom-left-radius:calc(var(--ui-radius)*2)}@media (hover:hover){.hover\:scale-115:hover{--tw-scale-x:115%;--tw-scale-y:115%;--tw-scale-z:115%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-accented\!:hover{background-color:var(--ui-bg-accented)!important}.hover\:bg-accented\/75:hover{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accented\/75:hover{background-color:color-mix(in oklab,var(--ui-bg-accented)75%,transparent)}}.hover\:bg-accented\/80:hover{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accented\/80:hover{background-color:color-mix(in oklab,var(--ui-bg-accented)80%,transparent)}}.hover\:bg-default\/10:hover{background-color:var(--ui-bg)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-default\/10:hover{background-color:color-mix(in oklab,var(--ui-bg)10%,transparent)}}.hover\:bg-elevated:hover,.hover\:bg-elevated\/25:hover{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-elevated\/25:hover{background-color:color-mix(in oklab,var(--ui-bg-elevated)25%,transparent)}}.hover\:bg-elevated\/50:hover{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-elevated\/50:hover{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.hover\:bg-error\/10:hover{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/10:hover{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.hover\:bg-error\/15:hover{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/15:hover{background-color:color-mix(in oklab,var(--ui-error)15%,transparent)}}.hover\:bg-error\/75:hover{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/75:hover{background-color:color-mix(in oklab,var(--ui-error)75%,transparent)}}.hover\:bg-error\/90:hover{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/90:hover{background-color:color-mix(in oklab,var(--ui-error)90%,transparent)}}.hover\:bg-info\/10:hover{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-info\/10:hover{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.hover\:bg-info\/15:hover{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-info\/15:hover{background-color:color-mix(in oklab,var(--ui-info)15%,transparent)}}.hover\:bg-info\/75:hover{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-info\/75:hover{background-color:color-mix(in oklab,var(--ui-info)75%,transparent)}}.hover\:bg-info\/90:hover{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-info\/90:hover{background-color:color-mix(in oklab,var(--ui-info)90%,transparent)}}.hover\:bg-inverted\/90:hover{background-color:var(--ui-bg-inverted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-inverted\/90:hover{background-color:color-mix(in oklab,var(--ui-bg-inverted)90%,transparent)}}.hover\:bg-primary\/10:hover{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.hover\:bg-primary\/15:hover{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/15:hover{background-color:color-mix(in oklab,var(--ui-primary)15%,transparent)}}.hover\:bg-primary\/75:hover{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/75:hover{background-color:color-mix(in oklab,var(--ui-primary)75%,transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--ui-primary)90%,transparent)}}.hover\:bg-secondary\/10:hover{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/10:hover{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.hover\:bg-secondary\/15:hover{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/15:hover{background-color:color-mix(in oklab,var(--ui-secondary)15%,transparent)}}.hover\:bg-secondary\/75:hover{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/75:hover{background-color:color-mix(in oklab,var(--ui-secondary)75%,transparent)}}.hover\:bg-secondary\/90:hover{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/90:hover{background-color:color-mix(in oklab,var(--ui-secondary)90%,transparent)}}.hover\:bg-success\/10:hover{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-success\/10:hover{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.hover\:bg-success\/15:hover{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-success\/15:hover{background-color:color-mix(in oklab,var(--ui-success)15%,transparent)}}.hover\:bg-success\/75:hover{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-success\/75:hover{background-color:color-mix(in oklab,var(--ui-success)75%,transparent)}}.hover\:bg-success\/90:hover{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-success\/90:hover{background-color:color-mix(in oklab,var(--ui-success)90%,transparent)}}.hover\:bg-warning\/10:hover{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-warning\/10:hover{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.hover\:bg-warning\/15:hover{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-warning\/15:hover{background-color:color-mix(in oklab,var(--ui-warning)15%,transparent)}}.hover\:bg-warning\/75:hover{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-warning\/75:hover{background-color:color-mix(in oklab,var(--ui-warning)75%,transparent)}}.hover\:bg-warning\/90:hover{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-warning\/90:hover{background-color:color-mix(in oklab,var(--ui-warning)90%,transparent)}}.hover\:text-default:hover{color:var(--ui-text)}.hover\:text-error\/75:hover{color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:text-error\/75:hover{color:color-mix(in oklab,var(--ui-error)75%,transparent)}}.hover\:text-highlighted:hover{color:var(--ui-text-highlighted)}.hover\:text-info\/75:hover{color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:text-info\/75:hover{color:color-mix(in oklab,var(--ui-info)75%,transparent)}}.hover\:text-primary\/75:hover{color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:text-primary\/75:hover{color:color-mix(in oklab,var(--ui-primary)75%,transparent)}}.hover\:text-secondary\/75:hover{color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:text-secondary\/75:hover{color:color-mix(in oklab,var(--ui-secondary)75%,transparent)}}.hover\:text-success\/75:hover{color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:text-success\/75:hover{color:color-mix(in oklab,var(--ui-success)75%,transparent)}}.hover\:text-warning\/75:hover{color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:text-warning\/75:hover{color:color-mix(in oklab,var(--ui-warning)75%,transparent)}}.hover\:decoration-neutral-500:hover{-webkit-text-decoration-color:var(--ui-color-neutral-500);-webkit-text-decoration-color:var(--ui-color-neutral-500);-webkit-text-decoration-color:var(--ui-color-neutral-500);text-decoration-color:var(--ui-color-neutral-500)}.hover\:ring-accented:hover{--tw-ring-color:var(--ui-border-accented)}.hover\:not-data-\[selected\]\:bg-error\/10:hover:not([data-selected]){background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-error\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-error\/20:hover:not([data-selected]){background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-error\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-error)20%,transparent)}}.hover\:not-data-\[selected\]\:bg-info\/10:hover:not([data-selected]){background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-info\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-info\/20:hover:not([data-selected]){background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-info\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-info)20%,transparent)}}.hover\:not-data-\[selected\]\:bg-inverted\/10:hover:not([data-selected]){background-color:var(--ui-bg-inverted)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-inverted\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-bg-inverted)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-primary\/10:hover:not([data-selected]){background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-primary\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-primary\/20:hover:not([data-selected]){background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-primary\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-primary)20%,transparent)}}.hover\:not-data-\[selected\]\:bg-secondary\/10:hover:not([data-selected]){background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-secondary\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-secondary\/20:hover:not([data-selected]){background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-secondary\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-secondary)20%,transparent)}}.hover\:not-data-\[selected\]\:bg-success\/10:hover:not([data-selected]){background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-success\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-success\/20:hover:not([data-selected]){background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-success\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-success)20%,transparent)}}.hover\:not-data-\[selected\]\:bg-warning\/10:hover:not([data-selected]){background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-warning\/10:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.hover\:not-data-\[selected\]\:bg-warning\/20:hover:not([data-selected]){background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.hover\:not-data-\[selected\]\:bg-warning\/20:hover:not([data-selected]){background-color:color-mix(in oklab,var(--ui-warning)20%,transparent)}}.hover\:before\:bg-elevated\/50:hover:before{content:var(--tw-content);background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.hover\:before\:bg-elevated\/50:hover:before{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}}.focus\:bg-accented:focus,.focus\:bg-accented\/50:focus{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.focus\:bg-accented\/50:focus{background-color:color-mix(in oklab,var(--ui-bg-accented)50%,transparent)}}.focus\:bg-elevated:focus{background-color:var(--ui-bg-elevated)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-error:focus{--tw-ring-color:var(--ui-error)}.focus\:ring-info:focus{--tw-ring-color:var(--ui-info)}.focus\:ring-inverted:focus{--tw-ring-color:var(--ui-border-inverted)}.focus\:ring-primary:focus{--tw-ring-color:var(--ui-primary)}.focus\:ring-secondary:focus{--tw-ring-color:var(--ui-secondary)}.focus\:ring-success:focus{--tw-ring-color:var(--ui-success)}.focus\:ring-warning:focus{--tw-ring-color:var(--ui-warning)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}@media (hover:hover){.group-hover\:focus\:bg-accented:is(:where(.group):hover *):focus{background-color:var(--ui-bg-accented)}}.focus-visible\:z-\[1\]:focus-visible{z-index:1}.focus-visible\:bg-accented\/75:focus-visible{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-accented\/75:focus-visible{background-color:color-mix(in oklab,var(--ui-bg-accented)75%,transparent)}}.focus-visible\:bg-default\/10:focus-visible{background-color:var(--ui-bg)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-default\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-bg)10%,transparent)}}.focus-visible\:bg-elevated:focus-visible{background-color:var(--ui-bg-elevated)}.focus-visible\:bg-error\/10:focus-visible{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-error\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.focus-visible\:bg-error\/15:focus-visible{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-error\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-error)15%,transparent)}}.focus-visible\:bg-info\/10:focus-visible{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-info\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.focus-visible\:bg-info\/15:focus-visible{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-info\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-info)15%,transparent)}}.focus-visible\:bg-primary\/10:focus-visible{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-primary\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.focus-visible\:bg-primary\/15:focus-visible{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-primary\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-primary)15%,transparent)}}.focus-visible\:bg-secondary\/10:focus-visible{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-secondary\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.focus-visible\:bg-secondary\/15:focus-visible{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-secondary\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-secondary)15%,transparent)}}.focus-visible\:bg-success\/10:focus-visible{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-success\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.focus-visible\:bg-success\/15:focus-visible{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-success\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-success)15%,transparent)}}.focus-visible\:bg-warning\/10:focus-visible{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-warning\/10:focus-visible{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.focus-visible\:bg-warning\/15:focus-visible{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-warning\/15:focus-visible{background-color:color-mix(in oklab,var(--ui-warning)15%,transparent)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-error:focus-visible{--tw-ring-color:var(--ui-error)}.focus-visible\:ring-info:focus-visible{--tw-ring-color:var(--ui-info)}.focus-visible\:ring-inverted:focus-visible{--tw-ring-color:var(--ui-border-inverted)}.focus-visible\:ring-primary:focus-visible{--tw-ring-color:var(--ui-primary)}.focus-visible\:ring-secondary:focus-visible{--tw-ring-color:var(--ui-secondary)}.focus-visible\:ring-success:focus-visible{--tw-ring-color:var(--ui-success)}.focus-visible\:ring-warning:focus-visible{--tw-ring-color:var(--ui-warning)}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-error:focus-visible,.focus-visible\:outline-error\/50:focus-visible{outline-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-error\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-error)50%,transparent)}}.focus-visible\:outline-info:focus-visible,.focus-visible\:outline-info\/50:focus-visible{outline-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-info\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-info)50%,transparent)}}.focus-visible\:outline-inverted:focus-visible,.focus-visible\:outline-inverted\/50:focus-visible{outline-color:var(--ui-border-inverted)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-inverted\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-border-inverted)50%,transparent)}}.focus-visible\:outline-primary:focus-visible,.focus-visible\:outline-primary\/50:focus-visible{outline-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-primary\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-primary)50%,transparent)}}.focus-visible\:outline-secondary:focus-visible,.focus-visible\:outline-secondary\/50:focus-visible{outline-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-secondary\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-secondary)50%,transparent)}}.focus-visible\:outline-success:focus-visible,.focus-visible\:outline-success\/50:focus-visible{outline-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-success\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-success)50%,transparent)}}.focus-visible\:outline-warning:focus-visible,.focus-visible\:outline-warning\/50:focus-visible{outline-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:outline-warning\/50:focus-visible{outline-color:color-mix(in oklab,var(--ui-warning)50%,transparent)}}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.focus-visible\:before\:ring-2:focus-visible:before{content:var(--tw-content);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:before\:ring-error:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-error)}.focus-visible\:before\:ring-info:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-info)}.focus-visible\:before\:ring-inverted:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-border-inverted)}.focus-visible\:before\:ring-primary:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-primary)}.focus-visible\:before\:ring-secondary:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-secondary)}.focus-visible\:before\:ring-success:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-success)}.focus-visible\:before\:ring-warning:focus-visible:before{content:var(--tw-content);--tw-ring-color:var(--ui-warning)}.focus-visible\:before\:ring-inset:focus-visible:before{content:var(--tw-content);--tw-ring-inset:inset}.active\:bg-accented\/75:active{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.active\:bg-accented\/75:active{background-color:color-mix(in oklab,var(--ui-bg-accented)75%,transparent)}}.active\:bg-elevated:active{background-color:var(--ui-bg-elevated)}.active\:bg-error\/10:active{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.active\:bg-error\/10:active{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.active\:bg-error\/15:active{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.active\:bg-error\/15:active{background-color:color-mix(in oklab,var(--ui-error)15%,transparent)}}.active\:bg-error\/75:active{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.active\:bg-error\/75:active{background-color:color-mix(in oklab,var(--ui-error)75%,transparent)}}.active\:bg-info\/10:active{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.active\:bg-info\/10:active{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.active\:bg-info\/15:active{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.active\:bg-info\/15:active{background-color:color-mix(in oklab,var(--ui-info)15%,transparent)}}.active\:bg-info\/75:active{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.active\:bg-info\/75:active{background-color:color-mix(in oklab,var(--ui-info)75%,transparent)}}.active\:bg-inverted\/90:active{background-color:var(--ui-bg-inverted)}@supports (color:color-mix(in lab, red, red)){.active\:bg-inverted\/90:active{background-color:color-mix(in oklab,var(--ui-bg-inverted)90%,transparent)}}.active\:bg-primary\/10:active{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-primary\/10:active{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.active\:bg-primary\/15:active{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-primary\/15:active{background-color:color-mix(in oklab,var(--ui-primary)15%,transparent)}}.active\:bg-primary\/75:active{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-primary\/75:active{background-color:color-mix(in oklab,var(--ui-primary)75%,transparent)}}.active\:bg-secondary\/10:active{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-secondary\/10:active{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.active\:bg-secondary\/15:active{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-secondary\/15:active{background-color:color-mix(in oklab,var(--ui-secondary)15%,transparent)}}.active\:bg-secondary\/75:active{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.active\:bg-secondary\/75:active{background-color:color-mix(in oklab,var(--ui-secondary)75%,transparent)}}.active\:bg-success\/10:active{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.active\:bg-success\/10:active{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.active\:bg-success\/15:active{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.active\:bg-success\/15:active{background-color:color-mix(in oklab,var(--ui-success)15%,transparent)}}.active\:bg-success\/75:active{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.active\:bg-success\/75:active{background-color:color-mix(in oklab,var(--ui-success)75%,transparent)}}.active\:bg-warning\/10:active{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.active\:bg-warning\/10:active{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.active\:bg-warning\/15:active{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.active\:bg-warning\/15:active{background-color:color-mix(in oklab,var(--ui-warning)15%,transparent)}}.active\:bg-warning\/75:active{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.active\:bg-warning\/75:active{background-color:color-mix(in oklab,var(--ui-warning)75%,transparent)}}.active\:text-default:active{color:var(--ui-text)}.active\:text-error\/75:active{color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.active\:text-error\/75:active{color:color-mix(in oklab,var(--ui-error)75%,transparent)}}.active\:text-info\/75:active{color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.active\:text-info\/75:active{color:color-mix(in oklab,var(--ui-info)75%,transparent)}}.active\:text-primary\/75:active{color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.active\:text-primary\/75:active{color:color-mix(in oklab,var(--ui-primary)75%,transparent)}}.active\:text-secondary\/75:active{color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.active\:text-secondary\/75:active{color:color-mix(in oklab,var(--ui-secondary)75%,transparent)}}.active\:text-success\/75:active{color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.active\:text-success\/75:active{color:color-mix(in oklab,var(--ui-success)75%,transparent)}}.active\:text-warning\/75:active{color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.active\:text-warning\/75:active{color:color-mix(in oklab,var(--ui-warning)75%,transparent)}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-default:disabled{background-color:var(--ui-bg)}.disabled\:bg-elevated:disabled,.disabled\:bg-elevated\/50:disabled{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-elevated\/50:disabled{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.disabled\:bg-error:disabled,.disabled\:bg-error\/10:disabled{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-error\/10:disabled{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.disabled\:bg-info:disabled,.disabled\:bg-info\/10:disabled{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-info\/10:disabled{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.disabled\:bg-inverted:disabled{background-color:var(--ui-bg-inverted)}.disabled\:bg-primary:disabled,.disabled\:bg-primary\/10:disabled{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-primary\/10:disabled{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.disabled\:bg-secondary:disabled,.disabled\:bg-secondary\/10:disabled{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-secondary\/10:disabled{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.disabled\:bg-success:disabled,.disabled\:bg-success\/10:disabled{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-success\/10:disabled{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.disabled\:bg-transparent:disabled{background-color:#0000}.disabled\:bg-warning:disabled,.disabled\:bg-warning\/10:disabled{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-warning\/10:disabled{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.disabled\:text-error:disabled{color:var(--ui-error)}.disabled\:text-info:disabled{color:var(--ui-info)}.disabled\:text-muted:disabled{color:var(--ui-text-muted)}.disabled\:text-primary:disabled{color:var(--ui-primary)}.disabled\:text-secondary:disabled{color:var(--ui-secondary)}.disabled\:text-success:disabled{color:var(--ui-success)}.disabled\:text-warning:disabled{color:var(--ui-warning)}.disabled\:opacity-75:disabled{opacity:.75}@media (hover:hover){.hover\:disabled\:bg-transparent:hover:disabled{background-color:#0000}}.has-focus\:bg-elevated:has(:focus){background-color:var(--ui-bg-elevated)}.has-focus-visible\:z-\[1\]:has(:focus-visible){z-index:1}.has-focus-visible\:ring-2:has(:focus-visible){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.has-focus-visible\:ring-error:has(:focus-visible){--tw-ring-color:var(--ui-error)}.has-focus-visible\:ring-info:has(:focus-visible){--tw-ring-color:var(--ui-info)}.has-focus-visible\:ring-inverted:has(:focus-visible){--tw-ring-color:var(--ui-border-inverted)}.has-focus-visible\:ring-primary:has(:focus-visible){--tw-ring-color:var(--ui-primary)}.has-focus-visible\:ring-secondary:has(:focus-visible){--tw-ring-color:var(--ui-secondary)}.has-focus-visible\:ring-success:has(:focus-visible){--tw-ring-color:var(--ui-success)}.has-focus-visible\:ring-warning:has(:focus-visible){--tw-ring-color:var(--ui-warning)}.has-focus-visible\:ring-inset:has(:focus-visible){--tw-ring-inset:inset}.has-data-\[state\=checked\]\:z-\[1\]:has([data-state=checked]){z-index:1}:is(.has-data-\[state\=checked\]\:border-error:has([data-state=checked]),.has-data-\[state\=checked\]\:border-error\/50:has([data-state=checked])){border-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-error\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-error)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-info:has([data-state=checked]),.has-data-\[state\=checked\]\:border-info\/50:has([data-state=checked])){border-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-info\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-info)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-inverted:has([data-state=checked]),.has-data-\[state\=checked\]\:border-inverted\/50:has([data-state=checked])){border-color:var(--ui-border-inverted)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-inverted\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-border-inverted)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-primary:has([data-state=checked]),.has-data-\[state\=checked\]\:border-primary\/50:has([data-state=checked])){border-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-primary\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-primary)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-secondary:has([data-state=checked]),.has-data-\[state\=checked\]\:border-secondary\/50:has([data-state=checked])){border-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-secondary\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-secondary)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-success:has([data-state=checked]),.has-data-\[state\=checked\]\:border-success\/50:has([data-state=checked])){border-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-success\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-success)50%,transparent)}}:is(.has-data-\[state\=checked\]\:border-warning:has([data-state=checked]),.has-data-\[state\=checked\]\:border-warning\/50:has([data-state=checked])){border-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:border-warning\/50:has([data-state=checked]){border-color:color-mix(in oklab,var(--ui-warning)50%,transparent)}}.has-data-\[state\=checked\]\:bg-elevated:has([data-state=checked]){background-color:var(--ui-bg-elevated)}.has-data-\[state\=checked\]\:bg-error\/10:has([data-state=checked]){background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-error\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.has-data-\[state\=checked\]\:bg-info\/10:has([data-state=checked]){background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-info\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.has-data-\[state\=checked\]\:bg-primary\/10:has([data-state=checked]){background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-primary\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.has-data-\[state\=checked\]\:bg-secondary\/10:has([data-state=checked]){background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-secondary\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.has-data-\[state\=checked\]\:bg-success\/10:has([data-state=checked]){background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-success\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.has-data-\[state\=checked\]\:bg-warning\/10:has([data-state=checked]){background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.has-data-\[state\=checked\]\:bg-warning\/10:has([data-state=checked]){background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.aria-disabled\:cursor-not-allowed[aria-disabled=true]{cursor:not-allowed}.aria-disabled\:bg-default[aria-disabled=true]{background-color:var(--ui-bg)}.aria-disabled\:bg-elevated[aria-disabled=true]{background-color:var(--ui-bg-elevated)}.aria-disabled\:bg-error[aria-disabled=true],.aria-disabled\:bg-error\/10[aria-disabled=true]{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-error\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.aria-disabled\:bg-info[aria-disabled=true],.aria-disabled\:bg-info\/10[aria-disabled=true]{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-info\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.aria-disabled\:bg-inverted[aria-disabled=true]{background-color:var(--ui-bg-inverted)}.aria-disabled\:bg-primary[aria-disabled=true],.aria-disabled\:bg-primary\/10[aria-disabled=true]{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-primary\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.aria-disabled\:bg-secondary[aria-disabled=true],.aria-disabled\:bg-secondary\/10[aria-disabled=true]{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-secondary\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.aria-disabled\:bg-success[aria-disabled=true],.aria-disabled\:bg-success\/10[aria-disabled=true]{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-success\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.aria-disabled\:bg-transparent[aria-disabled=true]{background-color:#0000}.aria-disabled\:bg-warning[aria-disabled=true],.aria-disabled\:bg-warning\/10[aria-disabled=true]{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.aria-disabled\:bg-warning\/10[aria-disabled=true]{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.aria-disabled\:text-error[aria-disabled=true]{color:var(--ui-error)}.aria-disabled\:text-info[aria-disabled=true]{color:var(--ui-info)}.aria-disabled\:text-muted[aria-disabled=true]{color:var(--ui-text-muted)}.aria-disabled\:text-primary[aria-disabled=true]{color:var(--ui-primary)}.aria-disabled\:text-secondary[aria-disabled=true]{color:var(--ui-secondary)}.aria-disabled\:text-success[aria-disabled=true]{color:var(--ui-success)}.aria-disabled\:text-warning[aria-disabled=true]{color:var(--ui-warning)}.aria-disabled\:opacity-75[aria-disabled=true]{opacity:.75}@media (hover:hover){.hover\:aria-disabled\:bg-transparent:hover[aria-disabled=true]{background-color:#0000}}.data-disabled\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.data-disabled\:text-muted[data-disabled]{color:var(--ui-text-muted)}.data-disabled\:opacity-75[data-disabled]{opacity:.75}.data-highlighted\:text-error[data-highlighted]{color:var(--ui-error)}.data-highlighted\:text-highlighted[data-highlighted]{color:var(--ui-text-highlighted)}.data-highlighted\:text-info[data-highlighted]{color:var(--ui-info)}.data-highlighted\:text-primary[data-highlighted]{color:var(--ui-primary)}.data-highlighted\:text-secondary[data-highlighted]{color:var(--ui-secondary)}.data-highlighted\:text-success[data-highlighted]{color:var(--ui-success)}.data-highlighted\:text-warning[data-highlighted]{color:var(--ui-warning)}.data-highlighted\:not-data-disabled\:text-highlighted[data-highlighted]:not([data-disabled]){color:var(--ui-text-highlighted)}.data-highlighted\:before\:bg-elevated\/50[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-elevated\/50[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.data-highlighted\:before\:bg-error\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-error\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.data-highlighted\:before\:bg-info\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-info\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.data-highlighted\:before\:bg-primary\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-primary\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.data-highlighted\:before\:bg-secondary\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-secondary\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.data-highlighted\:before\:bg-success\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-success\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.data-highlighted\:before\:bg-warning\/10[data-highlighted]:before{content:var(--tw-content);background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:before\:bg-warning\/10[data-highlighted]:before{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.data-highlighted\:not-data-disabled\:before\:bg-elevated\/50[data-highlighted]:not([data-disabled]):before{content:var(--tw-content);background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.data-highlighted\:not-data-disabled\:before\:bg-elevated\/50[data-highlighted]:not([data-disabled]):before{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.data-invalid\:text-error[data-invalid]{color:var(--ui-error)}.data-placeholder\:text-dimmed[data-placeholder]{color:var(--ui-text-dimmed)}.data-today\:font-semibold[data-today]{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.data-today\:not-data-\[selected\]\:text-error[data-today]:not([data-selected]){color:var(--ui-error)}.data-today\:not-data-\[selected\]\:text-highlighted[data-today]:not([data-selected]){color:var(--ui-text-highlighted)}.data-today\:not-data-\[selected\]\:text-info[data-today]:not([data-selected]){color:var(--ui-info)}.data-today\:not-data-\[selected\]\:text-primary[data-today]:not([data-selected]){color:var(--ui-primary)}.data-today\:not-data-\[selected\]\:text-secondary[data-today]:not([data-selected]){color:var(--ui-secondary)}.data-today\:not-data-\[selected\]\:text-success[data-today]:not([data-selected]){color:var(--ui-success)}.data-today\:not-data-\[selected\]\:text-warning[data-today]:not([data-selected]){color:var(--ui-warning)}.data-unavailable\:pointer-events-none[data-unavailable]{pointer-events:none}.data-unavailable\:text-muted[data-unavailable]{color:var(--ui-text-muted)}.data-unavailable\:line-through[data-unavailable]{text-decoration-line:line-through}.data-\[disabled\]\:cursor-not-allowed[data-disabled]{cursor:not-allowed}.data-\[disabled\]\:opacity-75[data-disabled]{opacity:.75}.data-\[dragging\=true\]\:bg-elevated\/25[data-dragging=true]{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.data-\[dragging\=true\]\:bg-elevated\/25[data-dragging=true]{background-color:color-mix(in oklab,var(--ui-bg-elevated)25%,transparent)}}.data-\[expanded\=true\]\:h-\(--height\)[data-expanded=true]{height:var(--height)}:is(.data-\[front\=false\]\:\*\:transition-opacity[data-front=false]>*){transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}:is(.data-\[front\=false\]\:\*\:duration-100[data-front=false]>*){--tw-duration:.1s;transition-duration:.1s}.data-\[expanded\=false\]\:data-\[front\=false\]\:h-\(--front-height\)[data-expanded=false][data-front=false]{height:var(--front-height)}:is(.data-\[expanded\=false\]\:data-\[front\=false\]\:\*\:opacity-0[data-expanded=false][data-front=false]>*){opacity:0}.data-\[highlighted\]\:bg-error\/10[data-highlighted]{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-error\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.data-\[highlighted\]\:bg-error\/20[data-highlighted]{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-error\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-error)20%,transparent)}}.data-\[highlighted\]\:bg-info\/10[data-highlighted]{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-info\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.data-\[highlighted\]\:bg-info\/20[data-highlighted]{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-info\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-info)20%,transparent)}}.data-\[highlighted\]\:bg-inverted\/10[data-highlighted]{background-color:var(--ui-bg-inverted)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-inverted\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-bg-inverted)10%,transparent)}}.data-\[highlighted\]\:bg-inverted\/20[data-highlighted]{background-color:var(--ui-bg-inverted)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-inverted\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-bg-inverted)20%,transparent)}}.data-\[highlighted\]\:bg-primary\/10[data-highlighted]{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-primary\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.data-\[highlighted\]\:bg-primary\/20[data-highlighted]{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-primary\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-primary)20%,transparent)}}.data-\[highlighted\]\:bg-secondary\/10[data-highlighted]{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-secondary\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.data-\[highlighted\]\:bg-secondary\/20[data-highlighted]{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-secondary\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-secondary)20%,transparent)}}.data-\[highlighted\]\:bg-success\/10[data-highlighted]{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-success\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.data-\[highlighted\]\:bg-success\/20[data-highlighted]{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-success\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-success)20%,transparent)}}.data-\[highlighted\]\:bg-warning\/10[data-highlighted]{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-warning\/10[data-highlighted]{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.data-\[highlighted\]\:bg-warning\/20[data-highlighted]{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[highlighted\]\:bg-warning\/20[data-highlighted]{background-color:color-mix(in oklab,var(--ui-warning)20%,transparent)}}.data-\[motion\=from-end\]\:animate-\[enter-from-right_200ms_ease\][data-motion=from-end]{animation:.2s enter-from-right}.data-\[motion\=from-start\]\:animate-\[enter-from-left_200ms_ease\][data-motion=from-start]{animation:.2s enter-from-left}.data-\[motion\=to-end\]\:animate-\[exit-to-right_200ms_ease\][data-motion=to-end]{animation:.2s exit-to-right}.data-\[motion\=to-start\]\:animate-\[exit-to-left_200ms_ease\][data-motion=to-start]{animation:.2s exit-to-left}.data-\[outside-view\]\:text-muted[data-outside-view]{color:var(--ui-text-muted)}.data-\[segment\=day\]\:w-6[data-segment=day]{width:calc(var(--spacing)*6)}.data-\[segment\=day\]\:w-7[data-segment=day]{width:calc(var(--spacing)*7)}.data-\[segment\=day\]\:w-8[data-segment=day]{width:calc(var(--spacing)*8)}.data-\[segment\=literal\]\:text-muted[data-segment=literal]{color:var(--ui-text-muted)}.data-\[segment\=month\]\:w-6[data-segment=month]{width:calc(var(--spacing)*6)}.data-\[segment\=month\]\:w-7[data-segment=month]{width:calc(var(--spacing)*7)}.data-\[segment\=month\]\:w-8[data-segment=month]{width:calc(var(--spacing)*8)}.data-\[segment\=year\]\:w-9[data-segment=year]{width:calc(var(--spacing)*9)}.data-\[segment\=year\]\:w-11[data-segment=year]{width:calc(var(--spacing)*11)}.data-\[segment\=year\]\:w-13[data-segment=year]{width:calc(var(--spacing)*13)}.data-\[selected\]\:bg-default[data-selected]{background-color:var(--ui-bg)}.data-\[selected\]\:bg-elevated[data-selected]{background-color:var(--ui-bg-elevated)}.data-\[selected\]\:bg-error[data-selected],.data-\[selected\]\:bg-error\/10[data-selected]{background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-error\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.data-\[selected\]\:bg-info[data-selected],.data-\[selected\]\:bg-info\/10[data-selected]{background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-info\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.data-\[selected\]\:bg-inverted[data-selected]{background-color:var(--ui-bg-inverted)}.data-\[selected\]\:bg-primary[data-selected],.data-\[selected\]\:bg-primary\/10[data-selected]{background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-primary\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.data-\[selected\]\:bg-secondary[data-selected],.data-\[selected\]\:bg-secondary\/10[data-selected]{background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-secondary\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.data-\[selected\]\:bg-success[data-selected],.data-\[selected\]\:bg-success\/10[data-selected]{background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-success\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.data-\[selected\]\:bg-warning[data-selected],.data-\[selected\]\:bg-warning\/10[data-selected]{background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:bg-warning\/10[data-selected]{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.data-\[selected\]\:text-default[data-selected]{color:var(--ui-text)}.data-\[selected\]\:text-error[data-selected]{color:var(--ui-error)}.data-\[selected\]\:text-info[data-selected]{color:var(--ui-info)}.data-\[selected\]\:text-inverted[data-selected]{color:var(--ui-text-inverted)}.data-\[selected\]\:text-primary[data-selected]{color:var(--ui-primary)}.data-\[selected\]\:text-secondary[data-selected]{color:var(--ui-secondary)}.data-\[selected\]\:text-success[data-selected]{color:var(--ui-success)}.data-\[selected\]\:text-warning[data-selected]{color:var(--ui-warning)}.data-\[selected\]\:ring[data-selected]{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[selected\]\:ring-accented[data-selected]{--tw-ring-color:var(--ui-border-accented)}.data-\[selected\]\:ring-error\/25[data-selected]{--tw-ring-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-error\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-error)25%,transparent)}}.data-\[selected\]\:ring-error\/50[data-selected]{--tw-ring-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-error\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-error)50%,transparent)}}.data-\[selected\]\:ring-info\/25[data-selected]{--tw-ring-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-info\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-info)25%,transparent)}}.data-\[selected\]\:ring-info\/50[data-selected]{--tw-ring-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-info\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-info)50%,transparent)}}.data-\[selected\]\:ring-primary\/25[data-selected]{--tw-ring-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-primary\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-primary)25%,transparent)}}.data-\[selected\]\:ring-primary\/50[data-selected]{--tw-ring-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-primary\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-primary)50%,transparent)}}.data-\[selected\]\:ring-secondary\/25[data-selected]{--tw-ring-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-secondary\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-secondary)25%,transparent)}}.data-\[selected\]\:ring-secondary\/50[data-selected]{--tw-ring-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-secondary\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-secondary)50%,transparent)}}.data-\[selected\]\:ring-success\/25[data-selected]{--tw-ring-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-success\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-success)25%,transparent)}}.data-\[selected\]\:ring-success\/50[data-selected]{--tw-ring-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-success\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-success)50%,transparent)}}.data-\[selected\]\:ring-warning\/25[data-selected]{--tw-ring-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-warning\/25[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-warning)25%,transparent)}}.data-\[selected\]\:ring-warning\/50[data-selected]{--tw-ring-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\]\:ring-warning\/50[data-selected]{--tw-ring-color:color-mix(in oklab,var(--ui-warning)50%,transparent)}}.data-\[selected\]\:ring-inset[data-selected]{--tw-ring-inset:inset}.data-\[selected\=true\]\:bg-elevated\/50[data-selected=true]{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\=true\]\:bg-elevated\/50[data-selected=true]{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.data-\[state\=\\\"active\\\"\]\:bg-accented[data-state=\"active\"]{background-color:var(--ui-bg-accented)}.data-\[state\=active\]\:bg-inverted[data-state=active]{background-color:var(--ui-bg-inverted)}.data-\[state\=active\]\:text-error[data-state=active]{color:var(--ui-error)}.data-\[state\=active\]\:text-highlighted[data-state=active]{color:var(--ui-text-highlighted)}.data-\[state\=active\]\:text-info[data-state=active]{color:var(--ui-info)}.data-\[state\=active\]\:text-inverted[data-state=active]{color:var(--ui-text-inverted)}.data-\[state\=active\]\:text-primary[data-state=active]{color:var(--ui-primary)}.data-\[state\=active\]\:text-secondary[data-state=active]{color:var(--ui-secondary)}.data-\[state\=active\]\:text-success[data-state=active]{color:var(--ui-success)}.data-\[state\=active\]\:text-warning[data-state=active]{color:var(--ui-warning)}.data-\[state\=checked\]\:translate-x-3[data-state=checked]{--tw-translate-x:calc(var(--spacing)*3);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:translate-x-3\.5[data-state=checked]{--tw-translate-x:calc(var(--spacing)*3.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:translate-x-4[data-state=checked]{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:translate-x-4\.5[data-state=checked]{--tw-translate-x:calc(var(--spacing)*4.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:translate-x-5[data-state=checked]{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:bg-error[data-state=checked]{background-color:var(--ui-error)}.data-\[state\=checked\]\:bg-info[data-state=checked]{background-color:var(--ui-info)}.data-\[state\=checked\]\:bg-inverted[data-state=checked]{background-color:var(--ui-bg-inverted)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--ui-primary)}.data-\[state\=checked\]\:bg-secondary[data-state=checked]{background-color:var(--ui-secondary)}.data-\[state\=checked\]\:bg-success[data-state=checked]{background-color:var(--ui-success)}.data-\[state\=checked\]\:bg-warning[data-state=checked]{background-color:var(--ui-warning)}.data-\[state\=closed\]\:animate-\[accordion-up_200ms_ease-out\][data-state=closed]{animation:.2s ease-out accordion-up}.data-\[state\=closed\]\:animate-\[collapsible-up_200ms_ease-out\][data-state=closed]{animation:.2s ease-out collapsible-up}.data-\[state\=closed\]\:animate-\[fade-out_200ms_ease-in\][data-state=closed]{animation:.2s ease-in fade-out}.data-\[state\=closed\]\:animate-\[scale-out_100ms_ease-in\][data-state=closed]{animation:.1s ease-in scale-out}.data-\[state\=closed\]\:animate-\[scale-out_200ms_ease-in\][data-state=closed]{animation:.2s ease-in scale-out}.data-\[state\=closed\]\:animate-\[slide-out-to-bottom_200ms_ease-in-out\][data-state=closed]{animation:.2s ease-in-out slide-out-to-bottom}.data-\[state\=closed\]\:animate-\[slide-out-to-left_200ms_ease-in-out\][data-state=closed]{animation:.2s ease-in-out slide-out-to-left}.data-\[state\=closed\]\:animate-\[slide-out-to-right_200ms_ease-in-out\][data-state=closed]{animation:.2s ease-in-out slide-out-to-right}.data-\[state\=closed\]\:animate-\[slide-out-to-top_200ms_ease-in-out\][data-state=closed]{animation:.2s ease-in-out slide-out-to-top}.data-\[state\=closed\]\:animate-\[toast-closed_200ms_ease-in-out\][data-state=closed]{animation:.2s ease-in-out toast-closed}.data-\[state\=closed\]\:data-\[expanded\=false\]\:data-\[front\=false\]\:animate-\[toast-collapsed-closed_200ms_ease-in-out\][data-state=closed][data-expanded=false][data-front=false]{animation:.2s ease-in-out toast-collapsed-closed}.data-\[state\=delayed-open\]\:animate-\[scale-in_100ms_ease-out\][data-state=delayed-open]{animation:.1s ease-out scale-in}.data-\[state\=hidden\]\:animate-\[fade-out_100ms_ease-in\][data-state=hidden]{animation:.1s ease-in fade-out}.data-\[state\=hidden\]\:opacity-0[data-state=hidden]{opacity:0}.data-\[state\=inactive\]\:text-muted[data-state=inactive]{color:var(--ui-text-muted)}@media (hover:hover){.hover\:data-\[state\=inactive\]\:not-disabled\:text-default:hover[data-state=inactive]:not(:disabled){color:var(--ui-text)}}.data-\[state\=indeterminate\]\:animate-\[carousel-inverse-vertical_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite carousel-inverse-vertical}.data-\[state\=indeterminate\]\:animate-\[carousel-inverse_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite carousel-inverse}.data-\[state\=indeterminate\]\:animate-\[carousel-vertical_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite carousel-vertical}.data-\[state\=indeterminate\]\:animate-\[carousel_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite carousel}.data-\[state\=indeterminate\]\:animate-\[elastic-vertical_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite elastic-vertical}.data-\[state\=indeterminate\]\:animate-\[elastic_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite elastic}.data-\[state\=indeterminate\]\:animate-\[swing-vertical_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite swing-vertical}.data-\[state\=indeterminate\]\:animate-\[swing_2s_ease-in-out_infinite\][data-state=indeterminate]{animation:2s ease-in-out infinite swing}.data-\[state\=open\]\:animate-\[accordion-down_200ms_ease-out\][data-state=open]{animation:.2s ease-out accordion-down}.data-\[state\=open\]\:animate-\[collapsible-down_200ms_ease-out\][data-state=open]{animation:.2s ease-out collapsible-down}.data-\[state\=open\]\:animate-\[fade-in_200ms_ease-out\][data-state=open]{animation:.2s ease-out fade-in}.data-\[state\=open\]\:animate-\[scale-in_100ms_ease-out\][data-state=open]{animation:.1s ease-out scale-in}.data-\[state\=open\]\:animate-\[scale-in_200ms_ease-out\][data-state=open]{animation:.2s ease-out scale-in}.data-\[state\=open\]\:animate-\[slide-in-from-bottom_200ms_ease-in-out\][data-state=open]{animation:.2s ease-in-out slide-in-from-bottom}.data-\[state\=open\]\:animate-\[slide-in-from-left_200ms_ease-in-out\][data-state=open]{animation:.2s ease-in-out slide-in-from-left}.data-\[state\=open\]\:animate-\[slide-in-from-right_200ms_ease-in-out\][data-state=open]{animation:.2s ease-in-out slide-in-from-right}.data-\[state\=open\]\:animate-\[slide-in-from-top_200ms_ease-in-out\][data-state=open]{animation:.2s ease-in-out slide-in-from-top}.data-\[state\=open\]\:bg-accented\/80[data-state=open]{background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:bg-accented\/80[data-state=open]{background-color:color-mix(in oklab,var(--ui-bg-accented)80%,transparent)}}.data-\[state\=open\]\:text-highlighted[data-state=open]{color:var(--ui-text-highlighted)}.data-\[state\=open\]\:before\:bg-elevated\/50[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-elevated\/50[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}.data-\[state\=open\]\:before\:bg-error\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-error)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-error\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-error)10%,transparent)}}.data-\[state\=open\]\:before\:bg-info\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-info)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-info\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-info)10%,transparent)}}.data-\[state\=open\]\:before\:bg-primary\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-primary\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-primary)10%,transparent)}}.data-\[state\=open\]\:before\:bg-secondary\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-secondary)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-secondary\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-secondary)10%,transparent)}}.data-\[state\=open\]\:before\:bg-success\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-success)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-success\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-success)10%,transparent)}}.data-\[state\=open\]\:before\:bg-warning\/10[data-state=open]:before{content:var(--tw-content);background-color:var(--ui-warning)}@supports (color:color-mix(in lab, red, red)){.data-\[state\=open\]\:before\:bg-warning\/10[data-state=open]:before{background-color:color-mix(in oklab,var(--ui-warning)10%,transparent)}}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=unchecked\]\:bg-accented[data-state=unchecked]{background-color:var(--ui-bg-accented)}.data-\[state\=visible\]\:animate-\[fade-in_100ms_ease-out\][data-state=visible]{animation:.1s ease-out fade-in}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=cancel\]\:translate-y-0[data-swipe=cancel]{--tw-translate-y:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=end\]\:translate-x-\(--reka-toast-swipe-end-x\)[data-swipe=end]{--tw-translate-x:var(--reka-toast-swipe-end-x);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=end\]\:translate-y-\(--reka-toast-swipe-end-y\)[data-swipe=end]{--tw-translate-y:var(--reka-toast-swipe-end-y);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=end\]\:animate-\[toast-slide-down_200ms_ease-out\][data-swipe=end]{animation:.2s ease-out toast-slide-down}.data-\[swipe\=end\]\:animate-\[toast-slide-left_200ms_ease-out\][data-swipe=end]{animation:.2s ease-out toast-slide-left}.data-\[swipe\=end\]\:animate-\[toast-slide-right_200ms_ease-out\][data-swipe=end]{animation:.2s ease-out toast-slide-right}.data-\[swipe\=end\]\:animate-\[toast-slide-up_200ms_ease-out\][data-swipe=end]{animation:.2s ease-out toast-slide-up}.data-\[swipe\=move\]\:translate-x-\(--reka-toast-swipe-move-x\)[data-swipe=move]{--tw-translate-x:var(--reka-toast-swipe-move-x);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=move\]\:translate-y-\(--reka-toast-swipe-move-y\)[data-swipe=move]{--tw-translate-y:var(--reka-toast-swipe-move-y);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}@media (width>=40rem){.sm\:-start-12{inset-inline-start:calc(var(--spacing)*-12)}.sm\:-end-12{inset-inline-end:calc(var(--spacing)*-12)}.sm\:-top-12{top:calc(var(--spacing)*-12)}.sm\:-bottom-12{bottom:calc(var(--spacing)*-12)}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:h-\[28rem\]{height:28rem}.sm\:max-h-\[calc\(100dvh-4rem\)\]{max-height:calc(100dvh - 4rem)}.sm\:w-\(--reka-navigation-menu-viewport-width\){width:var(--reka-navigation-menu-viewport-width)}.sm\:w-96{width:calc(var(--spacing)*96)}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:scroll-mt-6{scroll-margin-top:calc(var(--spacing)*6)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:gap-6{gap:calc(var(--spacing)*6)}.sm\:gap-16{gap:calc(var(--spacing)*16)}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*0)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*0)*calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.sm\:gap-y-12{row-gap:calc(var(--spacing)*12)}.sm\:gap-y-24{row-gap:calc(var(--spacing)*24)}.sm\:p-0{padding:calc(var(--spacing)*0)}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:px-12{padding-inline:calc(var(--spacing)*12)}.sm\:py-8{padding-block:calc(var(--spacing)*8)}.sm\:py-24{padding-block:calc(var(--spacing)*24)}.sm\:py-32{padding-block:calc(var(--spacing)*32)}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-xl\/8{font-size:var(--text-xl);line-height:calc(var(--spacing)*8)}.sm\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.sm\:ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media (width>=48rem){.md\:hidden{display:none}.md\:table{display:table}.md\:columns-2{columns:2}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (width>=64rem){.lg\:sticky{position:sticky}.lg\:top-\(--ui-header-height\){top:var(--ui-header-height)}.lg\:z-\[1\]{z-index:1}.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:order-3{order:3}.lg\:order-last{order:9999}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-6{grid-column:span 6/span 6}.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-span-10{grid-column:span 10/span 10}.lg\:mx-auto{margin-inline:auto}.lg\:-ms-4{margin-inline-start:calc(var(--spacing)*-4)}.lg\:me-0{margin-inline-end:calc(var(--spacing)*0)}.lg\:mt-0{margin-top:calc(var(--spacing)*0)}.lg\:mt-12{margin-top:calc(var(--spacing)*12)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:inline-flex{display:inline-flex}.lg\:max-h-\[calc\(100vh-var\(--ui-header-height\)\)\]{max-height:calc(100vh - var(--ui-header-height))}.lg\:w-\(--width\){width:var(--width)}.lg\:w-full{width:100%}.lg\:max-w-xs{max-width:var(--container-xs)}.lg\:flex-1{flex:1}.lg\:scale-\[1\.1\]{scale:1.1}.lg\:columns-3{columns:3}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:grid-cols-\[repeat\(var\(--count\)\,minmax\(0\,1fr\)\)\]{grid-template-columns:repeat(var(--count),minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}.lg\:justify-center{justify-content:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-start{justify-content:flex-start}.lg\:gap-10{gap:calc(var(--spacing)*10)}.lg\:gap-x-3{column-gap:calc(var(--spacing)*3)}.lg\:gap-x-13{column-gap:calc(var(--spacing)*13)}.lg\:gap-y-16{row-gap:calc(var(--spacing)*16)}:where(.lg\:divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.lg\:divide-y-0>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*calc(1 - var(--tw-divide-y-reverse)))}.lg\:p-6{padding:calc(var(--spacing)*6)}.lg\:p-8{padding:calc(var(--spacing)*8)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:px-16{padding-inline:calc(var(--spacing)*16)}.lg\:py-4{padding-block:calc(var(--spacing)*4)}.lg\:py-12{padding-block:calc(var(--spacing)*12)}.lg\:py-24{padding-block:calc(var(--spacing)*24)}.lg\:py-32{padding-block:calc(var(--spacing)*32)}.lg\:py-40{padding-block:calc(var(--spacing)*40)}.lg\:ps-4{padding-inline-start:calc(var(--spacing)*4)}.lg\:pe-6\.5{padding-inline-end:calc(var(--spacing)*6.5)}.lg\:pr-6{padding-right:calc(var(--spacing)*6)}.lg\:pb-0{padding-bottom:calc(var(--spacing)*0)}.lg\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.lg\:not-last\:border-e:not(:last-child){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.lg\:not-last\:border-default:not(:last-child){border-color:var(--ui-border)}}@media (width>=80rem){.xl\:col-span-2{grid-column:span 2/span 2}.xl\:mt-0{margin-top:calc(var(--spacing)*0)}.xl\:mb-0{margin-bottom:calc(var(--spacing)*0)}.xl\:grid{display:grid}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:gap-8{gap:calc(var(--spacing)*8)}.xl\:p-10{padding:calc(var(--spacing)*10)}}.ltr\:justify-end:where(:not(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=ltr],[dir=ltr] *){justify-content:flex-end}.rtl\:translate-x-\[4px\]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:4px;translate:var(--tw-translate-x)var(--tw-translate-y)}.rtl\:-rotate-90:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){rotate:-90deg}.rtl\:animate-\[marquee-rtl_var\(--duration\)_linear_infinite\]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){animation:marquee-rtl var(--duration)linear infinite}.rtl\:animate-\[marquee-vertical-rtl_var\(--duration\)_linear_infinite\]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){animation:marquee-vertical-rtl var(--duration)linear infinite}.rtl\:justify-end:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){justify-content:flex-end}.rtl\:text-right:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){text-align:right}.rtl\:after\:animate-\[carousel-inverse-rtl_2s_ease-in-out_infinite\]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *):after{content:var(--tw-content);animation:2s ease-in-out infinite carousel-inverse-rtl}.rtl\:after\:animate-\[carousel-rtl_2s_ease-in-out_infinite\]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *):after{content:var(--tw-content);animation:2s ease-in-out infinite carousel-rtl}.data-\[state\=checked\]\:rtl\:-translate-x-3[data-state=checked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-3);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:rtl\:-translate-x-3\.5[data-state=checked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-3.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:rtl\:-translate-x-4[data-state=checked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-4);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:rtl\:-translate-x-4\.5[data-state=checked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-4.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=checked\]\:rtl\:-translate-x-5[data-state=checked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-5);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[state\=indeterminate\]\:rtl\:animate-\[carousel-inverse-rtl_2s_ease-in-out_infinite\][data-state=indeterminate]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){animation:2s ease-in-out infinite carousel-inverse-rtl}.data-\[state\=indeterminate\]\:rtl\:animate-\[carousel-rtl_2s_ease-in-out_infinite\][data-state=indeterminate]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){animation:2s ease-in-out infinite carousel-rtl}.data-\[state\=unchecked\]\:rtl\:-translate-x-0[data-state=unchecked]:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.dark\:block:where(.dark,.dark *){display:block}.dark\:hidden:where(.dark,.dark *){display:none}.dark\:inline-block:where(.dark,.dark *){display:inline-block}.dark\:even\:bg-accented\/20:where(.dark,.dark *):nth-child(2n){background-color:var(--ui-bg-accented)}@supports (color:color-mix(in lab, red, red)){.dark\:even\:bg-accented\/20:where(.dark,.dark *):nth-child(2n){background-color:color-mix(in oklab,var(--ui-bg-accented)20%,transparent)}}.dark\:focus-visible\:outline-none:where(.dark,.dark *):focus-visible{--tw-outline-style:none;outline-style:none}.dark\:disabled\:bg-transparent:where(.dark,.dark *):disabled{background-color:#0000}@media (hover:hover){.dark\:hover\:disabled\:bg-transparent:where(.dark,.dark *):hover:disabled{background-color:#0000}}.dark\:aria-disabled\:bg-transparent:where(.dark,.dark *)[aria-disabled=true]{background-color:#0000}@media (hover:hover){.dark\:hover\:aria-disabled\:bg-transparent:where(.dark,.dark *):hover[aria-disabled=true]{background-color:#0000}}.\[\&_\.ProseMirror-selectednode\:not\(img\)\:not\(pre\)\:not\(\[data-node-view-wrapper\]\)\]\:bg-primary\/20 .ProseMirror-selectednode:not(img):not(pre):not([data-node-view-wrapper]){background-color:var(--ui-primary)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.ProseMirror-selectednode\:not\(img\)\:not\(pre\)\:not\(\[data-node-view-wrapper\]\)\]\:bg-primary\/20 .ProseMirror-selectednode:not(img):not(pre):not([data-node-view-wrapper]){background-color:color-mix(in oklab,var(--ui-primary)20%,transparent)}}.\[\&_\.mention\]\:font-medium .mention{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&_\.mention\]\:text-primary .mention{color:var(--ui-primary)}.\[\&_\:is\(h1\,h2\,h3\,h4\)\]\:font-bold :is(h1,h2,h3,h4){--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_\:is\(h1\,h2\,h3\,h4\)\]\:text-highlighted :is(h1,h2,h3,h4){color:var(--ui-text-highlighted)}.\[\&_\:is\(h1\,h2\,h3\,h4\)\>code\]\:border-dashed :is(h1,h2,h3,h4)>code{--tw-border-style:dashed;border-style:dashed}.\[\&_\:is\(h1\,h2\,h3\,h4\)\>code\]\:font-bold :is(h1,h2,h3,h4)>code{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_\:is\(p\,h1\,h2\,h3\,h4\)\.is-empty\]\:before\:pointer-events-none :is(p,h1,h2,h3,h4).is-empty:before{content:var(--tw-content);pointer-events:none}.\[\&_\:is\(p\,h1\,h2\,h3\,h4\)\.is-empty\]\:before\:float-left :is(p,h1,h2,h3,h4).is-empty:before{content:var(--tw-content);float:left}.\[\&_\:is\(p\,h1\,h2\,h3\,h4\)\.is-empty\]\:before\:h-0 :is(p,h1,h2,h3,h4).is-empty:before{content:var(--tw-content);height:calc(var(--spacing)*0)}.\[\&_\:is\(p\,h1\,h2\,h3\,h4\)\.is-empty\]\:before\:text-dimmed :is(p,h1,h2,h3,h4).is-empty:before{content:var(--tw-content);color:var(--ui-text-dimmed)}.\[\&_\:is\(p\,h1\,h2\,h3\,h4\)\.is-empty\]\:before\:content-\[attr\(data-placeholder\)\] :is(p,h1,h2,h3,h4).is-empty:before{--tw-content:attr(data-placeholder);content:var(--tw-content)}.\[\&_\:is\(ul\,ol\)\]\:ps-6 :is(ul,ol){padding-inline-start:calc(var(--spacing)*6)}.\[\&_\[data-type\=horizontalRule\]\]\:my-8 [data-type=horizontalRule]{margin-block:calc(var(--spacing)*8)}.\[\&_\[data-type\=horizontalRule\]\]\:py-2 [data-type=horizontalRule]{padding-block:calc(var(--spacing)*2)}.\[\&_a\]\:border-b a{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_a\]\:border-transparent a{border-color:#0000}.\[\&_a\]\:font-medium a{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&_a\]\:text-primary a{color:var(--ui-primary)}.\[\&_a\]\:transition-colors a{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.\[\&_a\]\:hover\:border-primary a:hover{border-color:var(--ui-primary)}}.\[\&_blockquote\]\:border-s-4 blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:4px}.\[\&_blockquote\]\:border-accented blockquote{border-color:var(--ui-border-accented)}.\[\&_blockquote\]\:ps-4 blockquote{padding-inline-start:calc(var(--spacing)*4)}.\[\&_blockquote\]\:italic blockquote{font-style:italic}.\[\&_code\]\:inline-block code{display:inline-block}.\[\&_code\]\:rounded-md code{border-radius:calc(var(--ui-radius)*1.5)}.\[\&_code\]\:border code{border-style:var(--tw-border-style);border-width:1px}.\[\&_code\]\:border-muted code{border-color:var(--ui-border-muted)}.\[\&_code\]\:bg-muted code{background-color:var(--ui-bg-muted)}.\[\&_code\]\:px-1\.5 code{padding-inline:calc(var(--spacing)*1.5)}.\[\&_code\]\:py-0\.5 code{padding-block:calc(var(--spacing)*.5)}.\[\&_code\]\:font-mono code{font-family:var(--font-mono)}.\[\&_code\]\:text-sm code{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\[\&_code\]\:font-medium code{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&_code\]\:text-highlighted code{color:var(--ui-text-highlighted)}.\[\&_h1\]\:text-3xl h1{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.\[\&_h2\]\:text-2xl h2{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.\[\&_h2\>code\]\:text-xl\/6 h2>code{font-size:var(--text-xl);line-height:calc(var(--spacing)*6)}.\[\&_h3\]\:text-xl h3{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.\[\&_h3\>code\]\:text-lg\/5 h3>code{font-size:var(--text-lg);line-height:calc(var(--spacing)*5)}.\[\&_h4\]\:text-lg h4{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.\[\&_hr\]\:border-t hr{border-top-style:var(--tw-border-style);border-top-width:1px}.\[\&_hr\]\:border-default hr{border-color:var(--ui-border)}.\[\&_img\]\:block img{display:block}.\[\&_img\]\:max-w-full img{max-width:100%}.\[\&_img\]\:rounded-md img{border-radius:calc(var(--ui-radius)*1.5)}.\[\&_img\.ProseMirror-selectednode\]\:outline-2 img.ProseMirror-selectednode{outline-style:var(--tw-outline-style);outline-width:2px}.\[\&_img\.ProseMirror-selectednode\]\:outline-primary img.ProseMirror-selectednode{outline-color:var(--ui-primary)}.\[\&_li\]\:my-1\.5 li{margin-block:calc(var(--spacing)*1.5)}.\[\&_li\]\:ps-1\.5 li{padding-inline-start:calc(var(--spacing)*1.5)}.\[\&_li_\.is-empty\]\:before\:content-none li .is-empty:before{content:var(--tw-content);--tw-content:none;content:none}.\[\&_ol\]\:list-decimal ol{list-style-type:decimal}.\[\&_ol\]\:marker\:text-muted ol ::marker{color:var(--ui-text-muted)}.\[\&_ol\]\:marker\:text-muted ol::marker{color:var(--ui-text-muted)}.\[\&_ol\]\:marker\:text-muted ol ::-webkit-details-marker{color:var(--ui-text-muted)}.\[\&_ol\]\:marker\:text-muted ol::-webkit-details-marker{color:var(--ui-text-muted)}.\[\&_p\]\:leading-7 p{--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.\[\&_pre\]\:overflow-x-auto pre{overflow-x:auto}.\[\&_pre\]\:rounded-md pre{border-radius:calc(var(--ui-radius)*1.5)}.\[\&_pre\]\:border pre{border-style:var(--tw-border-style);border-width:1px}.\[\&_pre\]\:border-muted pre{border-color:var(--ui-border-muted)}.\[\&_pre\]\:bg-muted pre{background-color:var(--ui-bg-muted)}.\[\&_pre\]\:px-4 pre{padding-inline:calc(var(--spacing)*4)}.\[\&_pre\]\:py-3 pre{padding-block:calc(var(--spacing)*3)}.\[\&_pre\]\:text-sm\/6 pre{font-size:var(--text-sm);line-height:calc(var(--spacing)*6)}.\[\&_pre\]\:break-words pre{overflow-wrap:break-word}.\[\&_pre\]\:whitespace-pre-wrap pre{white-space:pre-wrap}.\[\&_pre_code\]\:inline pre code{display:inline}.\[\&_pre_code\]\:rounded-none pre code{border-radius:0}.\[\&_pre_code\]\:border-0 pre code{border-style:var(--tw-border-style);border-width:0}.\[\&_pre_code\]\:bg-transparent pre code{background-color:#0000}.\[\&_pre_code\]\:p-0 pre code{padding:calc(var(--spacing)*0)}.\[\&_pre_code\]\:text-inherit pre code{color:inherit}.\[\&_ul\]\:list-disc ul{list-style-type:disc}.\[\&_ul\]\:marker\:text-\(--ui-border-accented\) ul ::marker{color:var(--ui-border-accented)}.\[\&_ul\]\:marker\:text-\(--ui-border-accented\) ul::marker{color:var(--ui-border-accented)}.\[\&_ul\]\:marker\:text-\(--ui-border-accented\) ul ::-webkit-details-marker{color:var(--ui-border-accented)}.\[\&_ul\]\:marker\:text-\(--ui-border-accented\) ul::-webkit-details-marker{color:var(--ui-border-accented)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pe-0:has([role=checkbox]){padding-inline-end:calc(var(--spacing)*0)}.\[\&\>\*\:nth-child\(1\)\]\:animate-\[bounce_1s_infinite\]>:first-child{animation:1s infinite bounce}.\[\&\>\*\:nth-child\(2\)\]\:animate-\[bounce_1s_0\.15s_infinite\]>:nth-child(2){animation:1s .15s infinite bounce}.\[\&\>\*\:nth-child\(3\)\]\:animate-\[bounce_1s_0\.3s_infinite\]>:nth-child(3){animation:1s .3s infinite bounce}.\[\&\>article\]\:last-of-type\:min-h-\(--last-message-height\)>article:last-of-type{min-height:var(--last-message-height)}.\[\&\>button\]\:py-0>button{padding-block:calc(var(--spacing)*0)}.\[\&\>div\]\:min-w-0>div{min-width:calc(var(--spacing)*0)}.\[\&\>input\]\:h-12>input{height:calc(var(--spacing)*12)}.\[\&\>input\]\:text-base\/5>input{font-size:var(--text-base);line-height:calc(var(--spacing)*5)}.\[\&\>mark\]\:bg-primary>mark{background-color:var(--ui-primary)}.\[\&\>mark\]\:text-inverted>mark{color:var(--ui-text-inverted)}@media (hover:hover){.\[\&\>tr\]\:data-\[selectable\=true\]\:hover\:bg-elevated\/50>tr[data-selectable=true]:hover{background-color:var(--ui-bg-elevated)}@supports (color:color-mix(in lab, red, red)){.\[\&\>tr\]\:data-\[selectable\=true\]\:hover\:bg-elevated\/50>tr[data-selectable=true]:hover{background-color:color-mix(in oklab,var(--ui-bg-elevated)50%,transparent)}}}.\[\&\>tr\]\:data-\[selectable\=true\]\:focus-visible\:outline-primary>tr[data-selectable=true]:focus-visible{outline-color:var(--ui-primary)}}@keyframes accordion-up{0%{height:var(--reka-accordion-content-height)}to{height:0}}@keyframes accordion-down{0%{height:0}to{height:var(--reka-accordion-content-height)}}@keyframes collapsible-up{0%{height:var(--reka-collapsible-content-height)}to{height:0}}@keyframes collapsible-down{0%{height:0}to{height:var(--reka-collapsible-content-height)}}@keyframes toast-collapsed-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--before) - var(--height))*var(--gap)))scale(var(--scale))}}@keyframes toast-closed{0%{transform:var(--transform)}to{transform:translateY(calc((var(--offset) - var(--height))*var(--translate-factor)))}}@keyframes toast-slide-left{0%{transform:translateX(0)translateY(var(--translate))}to{transform:translateX(-100%)translateY(var(--translate))}}@keyframes toast-slide-right{0%{transform:translateX(0)translateY(var(--translate))}to{transform:translateX(100%)translateY(var(--translate))}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes slide-in-from-top{0%{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes slide-out-to-top{0%{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes slide-in-from-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes slide-out-to-right{0%{transform:translate(0)}to{transform:translate(100%)}}@keyframes slide-in-from-bottom{0%{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes slide-out-to-bottom{0%{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes slide-in-from-left{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes slide-out-to-left{0%{transform:translate(0)}to{transform:translate(-100%)}}@keyframes slide-in-from-top-and-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-top-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}@keyframes slide-in-from-right-and-fade{0%{opacity:0;transform:translate(4px)}to{opacity:1;transform:translate(0)}}@keyframes slide-out-to-right-and-fade{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(4px)}}@keyframes slide-in-from-bottom-and-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slide-out-to-bottom-and-fade{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(4px)}}@keyframes slide-in-from-left-and-fade{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:translate(0)}}@keyframes slide-out-to-left-and-fade{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-4px)}}@keyframes enter-from-right{0%{opacity:0;transform:translate(200px)}to{opacity:1;transform:translate(0)}}@keyframes enter-from-left{0%{opacity:0;transform:translate(-200px)}to{opacity:1;transform:translate(0)}}@keyframes exit-to-right{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(200px)}}@keyframes exit-to-left{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(-200px)}}@keyframes carousel{0%,to{width:50%}0%{transform:translate(-100%)}to{transform:translate(200%)}}@keyframes carousel-rtl{0%,to{width:50%}0%{transform:translate(100%)}to{transform:translate(-200%)}}@keyframes carousel-vertical{0%,to{height:50%}0%{transform:translateY(-100%)}to{transform:translateY(200%)}}@keyframes carousel-inverse{0%,to{width:50%}0%{transform:translate(200%)}to{transform:translate(-100%)}}@keyframes carousel-inverse-rtl{0%,to{width:50%}0%{transform:translate(-200%)}to{transform:translate(100%)}}@keyframes carousel-inverse-vertical{0%,to{height:50%}0%{transform:translateY(200%)}to{transform:translateY(-100%)}}@keyframes swing{0%,to{width:50%}0%,to{transform:translate(-25%)}50%{transform:translate(125%)}}@keyframes swing-vertical{0%,to{height:50%}0%,to{transform:translateY(-25%)}50%{transform:translateY(125%)}}@keyframes elastic{0%,to{width:50%;margin-left:25%}50%{width:90%;margin-left:5%}}@keyframes elastic-vertical{0%,to{height:50%;margin-top:25%}50%{height:90%;margin-top:5%}}@keyframes marquee{0%{transform:translateZ(0)}to{transform:translate3d(calc(-100% - var(--gap)),0,0)}}@keyframes marquee-rtl{0%{transform:translateZ(0)}to{transform:translate3d(calc(100% + var(--gap)),0,0)}}@keyframes marquee-vertical{0%{transform:translateZ(0)}to{transform:translate3d(0,calc(-100% - var(--gap)),0)}}@keyframes marquee-vertical-rtl{0%{transform:translate3d(0,calc(-100% - var(--gap)),0)}to{transform:translate3d(0,calc(-100%*var(--gap)),0)}}:root{--ui-radius:.5rem;--ui-bg:var(--color-neutral-50);--ui-bg-muted:var(--ui-color-neutral-50);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-100);--ui-bg-inverted:var(--ui-color-neutral-900)}.dark{--ui-bg:var(--color-neutral-950);--ui-bg-muted:var(--ui-color-neutral-900);--ui-bg-elevated:var(--ui-color-neutral-900);--ui-bg-accented:var(--ui-color-neutral-800);--ui-bg-inverted:var(--color-neutral-50);--code-border:var(--ui-border);--code-foreground:var(--ui-text);--code-keyword:#e95678;--code-property:#fab795;--code-type:#25b0bc;--code-generic:#b877db;--code-value:#f09483;--code-variable:#f09483;--code-comment:#6c6f93;--code-highlight:#2e303e;--code-addition:#09f7a052;--code-deletion:#f43e5c52;--code-gutter:#6c6f93;--code-gutter-addition:#27d796;--code-gutter-deletion:#f43e5c;--code-moderate:#fab38e33;--code-complex:#e9567833;--code-adverb:#21bfc280;--code-passive:#b877db80;--code-complex-phrase:#25b0bc33;--code-qualified:#6c6f9333}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0;border-radius:4px}::-webkit-scrollbar-thumb{background:var(--ui-border-accented);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--ui-text-dimmed)}*{scrollbar-width:thin;scrollbar-color:var(--ui-border-accented)transparent}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url(https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono:vf@latest/latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-pan-x{syntax:"*";inherits:false}@property --tw-pan-y{syntax:"*";inherits:false}@property --tw-pinch-zoom{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}} +/*$vite$:1*/ \ No newline at end of file diff --git a/packages/router/src/Exceptions/local/exception.view.php b/packages/router/src/Exceptions/local/exception.view.php new file mode 100644 index 0000000000..96f5653370 --- /dev/null +++ b/packages/router/src/Exceptions/local/exception.view.php @@ -0,0 +1,21 @@ + + + + + + + Debug + + + + +
+ + + diff --git a/packages/router/src/Exceptions/local/package.json b/packages/router/src/Exceptions/local/package.json new file mode 100644 index 0000000000..d577ece128 --- /dev/null +++ b/packages/router/src/Exceptions/local/package.json @@ -0,0 +1,32 @@ +{ + "name": "tempest-exception-ui", + "type": "module", + "private": true, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc -b && vite build" + }, + "devDependencies": { + "@nuxt/ui": "^4.3.0", + "@shikijs/langs-precompiled": "^3.21.0", + "@tailwindcss/vite": "^4.1.18", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "@vueuse/components": "^14.1.0", + "@vueuse/core": "^14.1.0", + "arktype": "^2.1.29", + "shiki": "^3.21.0", + "sql-formatter": "^15.7.0", + "tailwindcss": "^4.1.18", + "temporal-polyfill": "^0.3.0", + "typescript": "~5.9.3", + "vite": "8.0.0-beta.2", + "vite-plugin-singlefile": "^2.3.0", + "vue": "^3.5.27", + "vue-router": "^4.6.4", + "vue-tsc": "^3.2.2" + }, + "trustedDependencies": [ + "@tailwindcss/oxide" + ] +} diff --git a/packages/router/src/Exceptions/local/src/assets/noise.svg b/packages/router/src/Exceptions/local/src/assets/noise.svg new file mode 100644 index 0000000000..94f208c6c7 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/assets/noise.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/router/src/Exceptions/local/src/assets/tempest-logo.svg b/packages/router/src/Exceptions/local/src/assets/tempest-logo.svg new file mode 100644 index 0000000000..236ff08245 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/assets/tempest-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/router/src/Exceptions/local/src/components/card.vue b/packages/router/src/Exceptions/local/src/components/card.vue new file mode 100644 index 0000000000..4b7c93e3a9 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/card.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/application-frame.vue b/packages/router/src/Exceptions/local/src/components/stacktrace/application-frame.vue new file mode 100644 index 0000000000..1650002c31 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/application-frame.vue @@ -0,0 +1,52 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/code-snippet.vue b/packages/router/src/Exceptions/local/src/components/stacktrace/code-snippet.vue new file mode 100644 index 0000000000..c55da33fca --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/code-snippet.vue @@ -0,0 +1,47 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/file-label.vue b/packages/router/src/Exceptions/local/src/components/stacktrace/file-label.vue new file mode 100644 index 0000000000..1e92356965 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/file-label.vue @@ -0,0 +1,31 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/stacktrace.ts b/packages/router/src/Exceptions/local/src/components/stacktrace/stacktrace.ts new file mode 100644 index 0000000000..e460b13158 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/stacktrace.ts @@ -0,0 +1,55 @@ +import { highlight } from '../../highlight' + +export interface CodeSnippet { + lines: Record + highlightedLine: number +} + +export interface Argument { + name: string | number + compact: string +} + +export interface StacktraceFrame { + line: number + class?: string + function?: string + type: '::' | '->' + isVendor: boolean + snippet?: CodeSnippet + absoluteFile: string + relativeFile: string + arguments: Argument[] + index: number +} + +export interface Stacktrace { + message?: string + exceptionClass: string + frames: StacktraceFrame[] + line: number + absoluteFile: string + relativeFile: string +} + +interface SymbolOptions { + formatted?: boolean + highlighted?: boolean +} + +export function getSymbolCall(frame: StacktraceFrame, options: SymbolOptions = {}): string { + if (!frame.class) { + return frame.function ?? '' + } + + const args = frame.arguments.map((argument) => `${argument.name}: ${argument.compact}`) + const formattedArgs = args.length > 0 + ? (options?.formatted ? `(\n ${args.join(',\n ')}\n);` : `(${args.join(',')});`) + : '();' + + const symbol = `${frame.class}${frame.type ?? ''}${frame.function ?? ''}${formattedArgs}` + + return options.highlighted + ? highlight(symbol, 'php') + : symbol +} diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/symbol-call.vue b/packages/router/src/Exceptions/local/src/components/stacktrace/symbol-call.vue new file mode 100644 index 0000000000..39908f5c89 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/symbol-call.vue @@ -0,0 +1,101 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/components/stacktrace/vendor-frames.vue b/packages/router/src/Exceptions/local/src/components/stacktrace/vendor-frames.vue new file mode 100644 index 0000000000..6e8ea10fb3 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/components/stacktrace/vendor-frames.vue @@ -0,0 +1,50 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/entrypoint/main.ts b/packages/router/src/Exceptions/local/src/entrypoint/main.ts new file mode 100644 index 0000000000..8a5c19d77e --- /dev/null +++ b/packages/router/src/Exceptions/local/src/entrypoint/main.ts @@ -0,0 +1,23 @@ +import ui from '@nuxt/ui/vue-plugin' +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import renderer from '../renderer.vue' +import { initializeExceptionStore } from '../store' +import './style.css' + +const app = createApp(renderer) +const router = createRouter({ + routes: [], + history: createWebHistory(), +}) + +const element = document.getElementById('tempest-hydration') +if (!element) { + throw new Error('Hydration element not found') +} + +initializeExceptionStore(JSON.parse(element.textContent!)) + +app.use(router) +app.use(ui) +app.mount('#root') diff --git a/packages/router/src/Exceptions/local/src/entrypoint/style.css b/packages/router/src/Exceptions/local/src/entrypoint/style.css new file mode 100644 index 0000000000..57a3a4d5d6 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/entrypoint/style.css @@ -0,0 +1,170 @@ +@import "tailwindcss"; +@import "@nuxt/ui"; + +@theme { + --font-sans: "Inter Variable", ui-sans-serif, system-ui; + --font-mono: + "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, + Consolas, "Liberation Mono", "Courier New", monospace; + + --code-border: var(--ui-border); + --code-background: transparent; + --code-foreground: var(--ui-text-toned); + --code-keyword: #4f95d1; + --code-property: #37954e; + --code-type: #d14f57; + --code-generic: #9d3af6; + --code-value: #515248; + --code-variable: #515248; + --code-comment: #888888; + --code-highlight: #22dddd33; + --code-addition: #00ff0022; + --code-deletion: #ff000011; + --code-gutter: #555555; + --code-gutter-addition: #34a853; + --code-gutter-deletion: #ea4334; + --code-moderate: #fef9c3; + --code-complex: #fee2e2; + --code-adverb: #e0f2fe; + --code-passive: #dcfce7; + --code-complex-phrase: #f3e8ff; + --code-qualified: #f1f5f9; +} + +:root { + --ui-radius: 0.5rem; + + --ui-bg: var(--color-neutral-50); + --ui-bg-muted: var(--ui-color-neutral-50); + --ui-bg-elevated: var(--ui-color-neutral-100); + --ui-bg-accented: var(--ui-color-neutral-100); + --ui-bg-inverted: var(--ui-color-neutral-900); +} + +.dark { + --ui-bg: var(--color-neutral-950); + --ui-bg-muted: var(--ui-color-neutral-900); + --ui-bg-elevated: var(--ui-color-neutral-900); + --ui-bg-accented: var(--ui-color-neutral-800); + --ui-bg-inverted: var(--color-neutral-50); + --code-border: var(--ui-border); + --code-foreground: var(--ui-text); + --code-keyword: #e95678; + --code-property: #fab795; + --code-type: #25b0bc; + --code-generic: #b877db; + --code-value: #f09483; + --code-variable: #f09483; + --code-comment: #6c6f93; + --code-highlight: #2e303e; + --code-addition: #09f7a052; + --code-deletion: #f43e5c52; + --code-gutter: #6c6f93; + --code-gutter-addition: #27d796; + --code-gutter-deletion: #f43e5c; + --code-moderate: #fab38e33; + --code-complex: #e9567833; + --code-adverb: #21bfc280; + --code-passive: #b877db80; + --code-complex-phrase: #25b0bc33; + --code-qualified: #6c6f9333; +} + +/* +|-------------------------------------------------------------------------- +| Scrollbars +|-------------------------------------------------------------------------- +*/ + +/* Scrollbar width */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +/* Scrollbar track */ +::-webkit-scrollbar-track { + background: transparent; + border-radius: 4px; +} + +/* Scrollbar thumb (the draggable part) */ +::-webkit-scrollbar-thumb { + background: var(--ui-border-accented); + border-radius: 4px; +} + +/* Scrollbar thumb on hover */ +::-webkit-scrollbar-thumb:hover { + background: var(--ui-text-dimmed); +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: var(--ui-border-accented) transparent; +} + +/* +|-------------------------------------------------------------------------- +| Fonts +|-------------------------------------------------------------------------- +*/ + +@font-face { + font-family: "JetBrains Mono Variable"; + font-style: normal; + font-display: swap; + font-weight: 100 800; + src: url(https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono:vf@latest/latin-wght-normal.woff2) + format("woff2-variations"); + unicode-range: + U+0000-00FF, + U+0131, + U+0152-0153, + U+02BB-02BC, + U+02C6, + U+02DA, + U+02DC, + U+0304, + U+0308, + U+0329, + U+2000-206F, + U+20AC, + U+2122, + U+2191, + U+2193, + U+2212, + U+2215, + U+FEFF, + U+FFFD; +} + +@font-face { + font-family: "Inter Variable"; + font-style: normal; + font-display: swap; + font-weight: 100 900; + src: url(https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2) + format("woff2-variations"); + unicode-range: + U+0000-00FF, + U+0131, + U+0152-0153, + U+02BB-02BC, + U+02C6, + U+02DA, + U+02DC, + U+0304, + U+0308, + U+0329, + U+2000-206F, + U+20AC, + U+2122, + U+2191, + U+2193, + U+2212, + U+2215, + U+FEFF, + U+FFFD; +} diff --git a/packages/router/src/Exceptions/local/src/highlight.ts b/packages/router/src/Exceptions/local/src/highlight.ts new file mode 100644 index 0000000000..013a2d0cf6 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/highlight.ts @@ -0,0 +1,159 @@ +import { createHighlighterCore, type ThemeInput } from 'shiki/core' +import { createJavaScriptRawEngine } from 'shiki/engine/javascript' + +const tempest: ThemeInput = { + name: 'tempest', + type: 'dark', + colors: { + 'editor.background': 'var(--code-background)', + 'editor.foreground': 'var(--code-foreground)', + 'editorLineNumber.foreground': 'var(--code-gutter)', + 'editorGutter.background': 'var(--code-background)', + 'editor.selectionBackground': 'var(--code-highlight)', + 'editor.lineHighlightBackground': 'var(--code-highlight)', + }, + tokenColors: [ + { + scope: [ + 'comment', + 'punctuation.definition.comment', + 'comment.block', + 'comment.line', + ], + settings: { + foreground: 'var(--code-comment)', + fontStyle: 'italic', + }, + }, + { + scope: [ + 'keyword', + 'storage.type', + 'storage.modifier', + 'keyword.control', + 'keyword.operator', + 'keyword.other', + ], + settings: { + foreground: 'var(--code-keyword)', + }, + }, + { + scope: [ + 'variable.parameter', + 'variable.other', + 'variable.language', + 'entity.name.variable', + ], + settings: { + foreground: 'var(--code-variable)', + }, + }, + { + scope: [ + 'entity.name.type', + 'entity.name.class', + 'support.type', + 'support.class', + ], + settings: { + foreground: 'var(--code-type)', + }, + }, + { + scope: [ + 'entity.name.function', + 'support.function', + 'meta.function-call', + ], + settings: { + foreground: 'var(--code-property)', + }, + }, + { + scope: [ + 'string', + 'string.quoted', + 'string.template', + 'constant.character', + 'constant.numeric', + 'constant.language', + 'constant.other', + ], + settings: { + foreground: 'var(--code-value)', + }, + }, + { + scope: [ + 'entity.other.attribute-name', + 'support.other.variable', + 'variable.other.property', + ], + settings: { + foreground: 'var(--code-property)', + }, + }, + { + scope: [ + 'punctuation', + 'meta.brace', + 'punctuation.definition.string', + 'punctuation.definition.variable', + 'punctuation.definition.parameters', + 'punctuation.definition.array', + ], + settings: { + foreground: 'var(--code-foreground)', + }, + }, + { + scope: [ + 'markup.bold', + ], + settings: { + fontStyle: 'bold', + }, + }, + { + scope: [ + 'markup.italic', + ], + settings: { + fontStyle: 'italic', + }, + }, + { + scope: [ + 'markup.inserted', + ], + settings: { + foreground: 'var(--code-gutter-addition)', + }, + }, + { + scope: [ + 'markup.deleted', + ], + settings: { + foreground: 'var(--code-gutter-deletion)', + }, + }, + ], +} + +export const highlighter = await createHighlighterCore({ + themes: [ + tempest, + ], + langs: [ + import('@shikijs/langs-precompiled/php'), + import('@shikijs/langs-precompiled/json'), + import('@shikijs/langs-precompiled/sql'), + ], + engine: createJavaScriptRawEngine(), +}) + +export function highlight(code: string, lang: string) { + return highlighter.codeToHtml(code, { lang, theme: 'tempest' }) +} diff --git a/packages/router/src/Exceptions/local/src/renderer.vue b/packages/router/src/Exceptions/local/src/renderer.vue new file mode 100644 index 0000000000..3fa9ff713f --- /dev/null +++ b/packages/router/src/Exceptions/local/src/renderer.vue @@ -0,0 +1,56 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/sections/context.vue b/packages/router/src/Exceptions/local/src/sections/context.vue new file mode 100644 index 0000000000..c11c62255b --- /dev/null +++ b/packages/router/src/Exceptions/local/src/sections/context.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/sections/headers.vue b/packages/router/src/Exceptions/local/src/sections/headers.vue new file mode 100644 index 0000000000..d40937f4cc --- /dev/null +++ b/packages/router/src/Exceptions/local/src/sections/headers.vue @@ -0,0 +1,44 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/sections/request-body.vue b/packages/router/src/Exceptions/local/src/sections/request-body.vue new file mode 100644 index 0000000000..4f283d50a6 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/sections/request-body.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/sections/stacktrace.vue b/packages/router/src/Exceptions/local/src/sections/stacktrace.vue new file mode 100644 index 0000000000..e35d68273f --- /dev/null +++ b/packages/router/src/Exceptions/local/src/sections/stacktrace.vue @@ -0,0 +1,46 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/sections/summary.vue b/packages/router/src/Exceptions/local/src/sections/summary.vue new file mode 100644 index 0000000000..a69ed7a701 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/sections/summary.vue @@ -0,0 +1,130 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/settings/dialog.vue b/packages/router/src/Exceptions/local/src/settings/dialog.vue new file mode 100644 index 0000000000..3070e0ca85 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/settings/dialog.vue @@ -0,0 +1,87 @@ + + + diff --git a/packages/router/src/Exceptions/local/src/settings/settings.ts b/packages/router/src/Exceptions/local/src/settings/settings.ts new file mode 100644 index 0000000000..59000623a6 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/settings/settings.ts @@ -0,0 +1,51 @@ +import { useLocalStorage } from '@vueuse/core' +import { type } from 'arktype' +import Dialog from './dialog.vue' +import { useOverlay, useToast } from '@nuxt/ui/composables'; + +const overlay = useOverlay() + +export const settingsDialog = overlay.create(Dialog) + +export const schema = type({ + editor: type('"vscode" | "phpstorm" | "zed" | "custom" | undefined').optional(), + openEditorTemplate: type('string | undefined').optional(), +}) + +export type Editor = typeof schema.infer['editor'] +export type Settings = typeof schema.inferIn + +export const settings = useLocalStorage('tempest:exceptions:settings', {}) + +export async function openFileInEditor(file: string, line?: number) { + if (!settings.value.editor) { + await settingsDialog.open() + } + + if (!settings.value.editor) { + const toast = useToast() + toast.add({ + title: 'No editor configured in settings', + color: 'warning', + progress: false, + icon: 'tabler:exclamation-circle', + }) + + return + } + + const filePath = encodeURIComponent(file) + const lineNumber = line ?? 1 + const template = { + vscode: 'vscode://file/{file}:{line}', + phpstorm: 'phpstorm://open?file={file}&line={line}', + zed: 'zed://file/{file}:{line}', + custom: settings.value.openEditorTemplate ?? '', + } + + const url = template[settings.value.editor] + ?.replace('{file}', filePath) + ?.replace('{line}', lineNumber.toString()) + + window.open(url, '_self') +} diff --git a/packages/router/src/Exceptions/local/src/store.ts b/packages/router/src/Exceptions/local/src/store.ts new file mode 100644 index 0000000000..32e0a9ec63 --- /dev/null +++ b/packages/router/src/Exceptions/local/src/store.ts @@ -0,0 +1,50 @@ +import { reactive } from 'vue' +import type { Stacktrace } from './components/stacktrace/stacktrace' + +interface InitializingStore { + step: 'initializing' +} +interface ReadyStore { + step: 'ready' + exception: ExceptionState +} + +export interface ExceptionState { + stacktrace: Stacktrace + context: Record + rootPath: string + request: { + uri: string + method: string + headers: Record + body?: string + } + response: { + status: number + } + resources: { + memoryPeakUsage: number + executionTimeMs: number + } + versions: { + php: string + tempest: string + } +} + +export const store = reactive({ + step: 'initializing', +}) + +export function initializeExceptionStore(data: ExceptionState) { + store.step = 'ready' + + if (store.step === 'ready') { + store.exception = { + ...data, + stacktrace: JSON.parse(data.stacktrace as any) as unknown as ExceptionState['stacktrace'], + } + + console.log(store.exception) + } +} diff --git a/packages/router/src/Exceptions/local/tsconfig.app.json b/packages/router/src/Exceptions/local/tsconfig.app.json new file mode 100644 index 0000000000..a67f8fb169 --- /dev/null +++ b/packages/router/src/Exceptions/local/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + /* Linting */ + "strict": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noUncheckedSideEffectImports": true + }, + "include": ["./*.ts", "./*.vue", "src/renderer.vue", "vite-env.d.ts", "src/entrypoint/main.ts"] +} diff --git a/packages/router/src/Exceptions/local/tsconfig.json b/packages/router/src/Exceptions/local/tsconfig.json new file mode 100644 index 0000000000..c93f755c16 --- /dev/null +++ b/packages/router/src/Exceptions/local/tsconfig.json @@ -0,0 +1,7 @@ +{ + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "files": [] +} diff --git a/packages/router/src/Exceptions/local/tsconfig.node.json b/packages/router/src/Exceptions/local/tsconfig.node.json new file mode 100644 index 0000000000..674001e843 --- /dev/null +++ b/packages/router/src/Exceptions/local/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "moduleDetection": "force", + "module": "ESNext", + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + + /* Linting */ + "strict": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/router/src/Exceptions/local/vite-env.d.ts b/packages/router/src/Exceptions/local/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/packages/router/src/Exceptions/local/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/router/src/Exceptions/local/vite.config.ts b/packages/router/src/Exceptions/local/vite.config.ts new file mode 100644 index 0000000000..fcff5027fd --- /dev/null +++ b/packages/router/src/Exceptions/local/vite.config.ts @@ -0,0 +1,57 @@ +import ui from '@nuxt/ui/vite' +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite' +import { viteSingleFile } from 'vite-plugin-singlefile' + +export default defineConfig({ + plugins: [ + vue(), + tailwindcss(), + ui({ + ui: { + colors: { + primary: 'blue', + neutral: 'zinc', + }, + card: { + defaultVariants: { + variant: 'soft', + }, + }, + badge: { + defaultVariants: { + variant: 'soft', + }, + }, + button: { + slots: { + base: 'not-disabled:cursor-pointer', + }, + defaultVariants: { + variant: 'soft', + }, + }, + tooltip: { + slots: { + content: 'px-4 py-2 h-auto bg-default/90 text-toned font-mono', + }, + }, + }, + }), + viteSingleFile(), + ], + build: { + watch: { + exclude: 'dist/**', // prevent infinite loops while using --watch + }, + rollupOptions: { + input: ['./src/entrypoint/main.ts'], + output: { + inlineDynamicImports: true, + assetFileNames: '[name][extname]', + entryFileNames: '[name].js', + }, + }, + }, +}) diff --git a/packages/router/src/Exceptions/localization.en.yml b/packages/router/src/Exceptions/localization.en.yml new file mode 100644 index 0000000000..f41da0a735 --- /dev/null +++ b/packages/router/src/Exceptions/localization.en.yml @@ -0,0 +1,6 @@ +http_status_error: + 500: "An unexpected server error occurred" + 404: "The requested resource was not found" + 403: "Access to the requested resource is forbidden" + 401: "Authentication is required to access the requested resource" + 422: "The request could not be processed due to validation errors" diff --git a/packages/router/src/Exceptions/HttpErrorResponse/error.view.php b/packages/router/src/Exceptions/production/error.view.php similarity index 88% rename from packages/router/src/Exceptions/HttpErrorResponse/error.view.php rename to packages/router/src/Exceptions/production/error.view.php index 944fc3707a..9a8a637ae5 100644 --- a/packages/router/src/Exceptions/HttpErrorResponse/error.view.php +++ b/packages/router/src/Exceptions/production/error.view.php @@ -2,16 +2,17 @@ {{ $title }} + {{-- https://play.tailwindcss.com/AtW4En9qH8 --}} -
+
-

+

- HTTP + HTTP {{ $status }}

-

{{ \Tempest\Support\Str\ensure_ends_with($message, '.') }}

+

{{ $message }}

diff --git a/packages/router/src/Exceptions/HttpErrorResponse/style.css b/packages/router/src/Exceptions/production/style.css similarity index 81% rename from packages/router/src/Exceptions/HttpErrorResponse/style.css rename to packages/router/src/Exceptions/production/style.css index 690f394fe2..c73cd4b900 100644 --- a/packages/router/src/Exceptions/HttpErrorResponse/style.css +++ b/packages/router/src/Exceptions/production/style.css @@ -1,4 +1,5 @@ -/*! tailwindcss v4.0.17 | MIT License | https://tailwindcss.com */ +/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ +@layer properties; @layer theme, base, components, utilities; @layer theme { :root, :host { @@ -8,12 +9,14 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --color-blue-200: oklch(88.2% 0.059 254.128); + --color-neutral-600: oklch(43.9% 0 0); + --color-neutral-900: oklch(20.5% 0 0); --spacing: 0.25rem; - --text-xl: 1.25rem; - --text-xl--line-height: calc(1.75 / 1.25); --text-8xl: 6rem; --text-8xl--line-height: 1; - --font-weight-thin: 100; + --font-weight-light: 300; + --font-weight-normal: 400; --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); } @@ -148,7 +151,10 @@ @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { ::placeholder { - color: color-mix(in oklab, currentColor 50%, transparent); + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } } } textarea { @@ -178,6 +184,9 @@ ::-webkit-datetime-edit-meridiem-field { padding-block: 0; } + ::-webkit-calendar-picker-indicator { + line-height: 1; + } :-moz-ui-invalid { box-shadow: none; } @@ -233,29 +242,32 @@ .gap-x-1 { column-gap: calc(var(--spacing) * 1); } - .bg-\[\#061324\] { - background-color: #061324; + .bg-neutral-900 { + background-color: var(--color-neutral-900); } .px-8 { padding-inline: calc(var(--spacing) * 8); } + .font-mono { + font-family: var(--font-mono); + } .text-8xl { font-size: var(--text-8xl); line-height: var(--tw-leading, var(--text-8xl--line-height)); } - .text-xl { - font-size: var(--text-xl); - line-height: var(--tw-leading, var(--text-xl--line-height)); + .font-light { + --tw-font-weight: var(--font-weight-light); + font-weight: var(--font-weight-light); } - .font-thin { - --tw-font-weight: var(--font-weight-thin); - font-weight: var(--font-weight-thin); + .font-normal { + --tw-font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-normal); } - .text-\[\#4c6586\] { - color: #4c6586; + .text-blue-200 { + color: var(--color-blue-200); } - .text-\[\#a8caf7\] { - color: #a8caf7; + .text-neutral-600 { + color: var(--color-neutral-600); } .uppercase { text-transform: uppercase; @@ -269,3 +281,11 @@ syntax: "*"; inherits: false; } +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or + ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-font-weight: initial; + } + } +} diff --git a/packages/router/src/GenericResponseSender.php b/packages/router/src/GenericResponseSender.php index a1b9c963b6..472f46051a 100644 --- a/packages/router/src/GenericResponseSender.php +++ b/packages/router/src/GenericResponseSender.php @@ -70,7 +70,7 @@ private function resolveHeaders(Response $response): Generator if (is_array($response->body)) { $headers[ContentType::HEADER] ??= new Header(ContentType::HEADER); - $headers[ContentType::HEADER]->add(ContentType::JSON->value); + $headers[ContentType::HEADER]->add(ContentType::JSON); } foreach ($headers as $key => $header) { diff --git a/packages/router/src/GenericRouter.php b/packages/router/src/GenericRouter.php index 4dd5199625..5c87319d2a 100644 --- a/packages/router/src/GenericRouter.php +++ b/packages/router/src/GenericRouter.php @@ -6,24 +6,20 @@ use Psr\Http\Message\ServerRequestInterface as PsrRequest; use Tempest\Container\Container; -use Tempest\Core\AppConfig; use Tempest\Http\Mappers\PsrRequestToGenericRequestMapper; use Tempest\Http\Request; use Tempest\Http\Response; use Tempest\Http\Responses\Ok; use Tempest\Router\Exceptions\ControllerActionHadNoReturn; use Tempest\Router\Exceptions\MatchedRouteCouldNotBeResolved; -use Tempest\Router\Routing\Matching\RouteMatcher; use Tempest\View\View; -use function Tempest\map; +use function Tempest\Mapper\map; final readonly class GenericRouter implements Router { public function __construct( private Container $container, - private RouteMatcher $routeMatcher, - private AppConfig $appConfig, private RouteConfig $routeConfig, ) {} diff --git a/packages/router/src/HandleRouteExceptionMiddleware.php b/packages/router/src/HandleRouteExceptionMiddleware.php index c9c5ec6f33..750b422b5c 100644 --- a/packages/router/src/HandleRouteExceptionMiddleware.php +++ b/packages/router/src/HandleRouteExceptionMiddleware.php @@ -6,48 +6,37 @@ use Tempest\Http\HttpRequestFailed; use Tempest\Http\Request; use Tempest\Http\Response; -use Tempest\Http\Responses\Invalid; use Tempest\Http\Responses\NotFound; -use Tempest\Router\Exceptions\ConvertsToResponse; use Tempest\Router\Exceptions\RouteBindingFailed; -use Tempest\Validation\Exceptions\ValidationFailed; #[Priority(Priority::FRAMEWORK - 10)] final readonly class HandleRouteExceptionMiddleware implements HttpMiddleware { - public function __construct( - private RouteConfig $routeConfig, - ) {} - public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { - if ($this->routeConfig->throwHttpExceptions === true) { - $response = $this->forward($request, $next); - - if ($response->status->isServerError() || $response->status->isClientError()) { - throw new HttpRequestFailed( - request: $request, - status: $response->status, - cause: $response, - ); - } + $response = $this->forward($request, $next); + if (! $response->status->isClientError() && ! $response->status->isServerError()) { return $response; } - return $this->forward($request, $next); + throw new HttpRequestFailed( + status: $response->status, + cause: $response, + request: $request, + ); } + /** + * Some exceptions are not necessary to be thrown as-is, so we catch them here and convert them to equivalent responses. + * - `RouteBindingFailed` would require to be handled manually in renderers, it's better DX to simply return a 404. + */ private function forward(Request $request, HttpMiddlewareCallable $next): Response { try { return $next($request); - } catch (ConvertsToResponse $convertsToResponse) { - return $convertsToResponse->toResponse(); } catch (RouteBindingFailed) { return new NotFound(); - } catch (ValidationFailed $validationException) { - return new Invalid($validationException->subject, $validationException->failingRules, $validationException->targetClass); } } } diff --git a/packages/router/src/HttpApplication.php b/packages/router/src/HttpApplication.php index 78b6e6961d..965655a34a 100644 --- a/packages/router/src/HttpApplication.php +++ b/packages/router/src/HttpApplication.php @@ -10,12 +10,6 @@ use Tempest\Core\Kernel; use Tempest\Core\Tempest; use Tempest\Http\RequestFactory; -use Tempest\Http\Session\SessionManager; -use Tempest\Log\Channels\AppendLogChannel; -use Tempest\Log\LogConfig; - -use function Tempest\env; -use function Tempest\Support\path; #[Singleton] final readonly class HttpApplication implements Application @@ -25,40 +19,21 @@ public function __construct( ) {} /** @param \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */ - public static function boot( - string $root, - array $discoveryLocations = [], - ): self { - $container = Tempest::boot($root, $discoveryLocations); - - $application = $container->get(HttpApplication::class); - - // Application-specific setup - $logConfig = $container->get(LogConfig::class); - - if ($logConfig->debugLogPath === null && $logConfig->serverLogPath === null && $logConfig->channels === []) { - $logConfig->debugLogPath = path($container->get(Kernel::class)->root, '/log/debug.log')->toString(); - $logConfig->serverLogPath = env('SERVER_LOG'); - $logConfig->channels[] = new AppendLogChannel(path($root, '/log/tempest.log')->toString()); - } - - return $application; + public static function boot(string $root, array $discoveryLocations = []): self + { + return Tempest::boot($root, $discoveryLocations)->get(HttpApplication::class); } - public function run(): void + public function run(): never { $router = $this->container->get(Router::class); - $psrRequest = $this->container->get(RequestFactory::class)->make(); - $responseSender = $this->container->get(ResponseSender::class); $responseSender->send( - $router->dispatch($psrRequest), + response: $router->dispatch($psrRequest), ); - $this->container->get(SessionManager::class)->cleanup(); - $this->container->get(Kernel::class)->shutdown(); } } diff --git a/packages/router/src/MatchRouteMiddleware.php b/packages/router/src/MatchRouteMiddleware.php index 05bc919690..ab8a44a329 100644 --- a/packages/router/src/MatchRouteMiddleware.php +++ b/packages/router/src/MatchRouteMiddleware.php @@ -12,7 +12,7 @@ use Tempest\Http\Responses\NotFound; use Tempest\Router\Routing\Matching\RouteMatcher; -use function Tempest\map; +use function Tempest\Mapper\map; #[Priority(Priority::FRAMEWORK - 9)] final readonly class MatchRouteMiddleware implements HttpMiddleware diff --git a/packages/router/src/PreventCrossSiteRequestsMiddleware.php b/packages/router/src/PreventCrossSiteRequestsMiddleware.php new file mode 100644 index 0000000000..b1bf73f4fc --- /dev/null +++ b/packages/router/src/PreventCrossSiteRequestsMiddleware.php @@ -0,0 +1,85 @@ +shouldValidate($request)) { + return $next($request); + } + + if ($this->isValidRequest($request)) { + return $next($request); + } + + return new Forbidden(); + } + + /** + * Determines if the request should be validated for CSRF. + */ + private function shouldValidate(Request $request): bool + { + if (in_array($request->method, self::SAFE_METHODS, strict: true)) { + return false; + } + + return true; + } + + /** + * Validates the request using `Sec-Fetch-*` headers. + */ + private function isValidRequest(Request $request): bool + { + $secFetchSite = SecFetchSite::tryFrom($request->headers->get('sec-fetch-site', default: '')); + $secFetchMode = SecFetchMode::tryFrom($request->headers->get('sec-fetch-mode', default: '')); + + // prevent the request if there is no `sec-fetch-site` header + if ($secFetchSite === null) { + return false; + } + + // allow cross-site only on navigation requests + if ($secFetchSite === SecFetchSite::CROSS_SITE && $secFetchMode === SecFetchMode::NAVIGATE) { + return true; + } + + // same origin, same site and user-originated requests are always allowed + if ($secFetchSite !== SecFetchSite::CROSS_SITE) { + return true; + } + + return false; + } +} diff --git a/packages/router/src/RateLimit.php b/packages/router/src/RateLimit.php new file mode 100644 index 0000000000..3090b6752a --- /dev/null +++ b/packages/router/src/RateLimit.php @@ -0,0 +1,71 @@ +middleware = [ + ...$route->middleware, + RateLimitMiddleware::class, + ]; + + return $route; + } +} diff --git a/packages/router/src/RateLimitMiddleware.php b/packages/router/src/RateLimitMiddleware.php new file mode 100644 index 0000000000..7f6c09651b --- /dev/null +++ b/packages/router/src/RateLimitMiddleware.php @@ -0,0 +1,188 @@ +getRateLimitAttribute(); + + if ($rateLimit === null) { + return $next($request); + } + + // Get the rate limiter from container at invocation time to support testing + $rateLimiter = $this->container->get(RateLimiter::class); + + $key = $this->resolveKey($rateLimit, $request); + $result = $rateLimiter->attempt($key, $rateLimit->maxAttempts, $rateLimit->decaySeconds); + + if (! $result->allowed) { + return $this->buildTooManyRequestsResponse($result); + } + + $response = $next($request); + + return $this->addRateLimitHeaders($response, $result); + } + + /** + * Get the RateLimit attribute from the current route handler. + */ + private function getRateLimitAttribute(): ?RateLimit + { + $handler = $this->matchedRoute->route->handler; + + // Check method first, then class + $rateLimit = $handler->getAttribute(RateLimit::class); + + if ($rateLimit === null) { + $rateLimit = $handler->getDeclaringClass()->getAttribute(RateLimit::class); + } + + return $rateLimit; + } + + /** + * Resolve the rate limit key based on the configuration. + */ + private function resolveKey(RateLimit $rateLimit, Request $request): string + { + $prefix = $rateLimit->key ?? $this->matchedRoute->route->uri; + $identifier = $this->resolveIdentifier($rateLimit->by, $request); + + return sprintf('%s:%s', $prefix, $identifier); + } + + /** + * Resolve the client identifier based on the rate limit strategy. + */ + private function resolveIdentifier(string $by, Request $request): string + { + return match ($by) { + 'user' => $this->resolveUserIdentifier(), + 'session' => $this->resolveSessionIdentifier(), + default => $this->resolveIpIdentifier($request), + }; + } + + /** + * Resolve the client IP address from the request. + */ + private function resolveIpIdentifier(Request $request): string + { + // Check for proxy headers first + $forwardedFor = $request->headers->get('X-Forwarded-For'); + + if ($forwardedFor !== null) { + // X-Forwarded-For can contain multiple IPs, the first one is the client + $ips = explode(',', $forwardedFor); + + return trim($ips[0]); + } + + $realIp = $request->headers->get('X-Real-IP'); + + if ($realIp !== null) { + return $realIp; + } + + // Fall back to REMOTE_ADDR + return (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown'); + } + + /** + * Resolve the authenticated user ID. + */ + private function resolveUserIdentifier(): string + { + if (! $this->container->has(Authenticator::class)) { + return 'anonymous'; + } + + /** @var Authenticator $authenticator */ + $authenticator = $this->container->get(Authenticator::class); + $user = $authenticator->current(); + + if ($user === null) { + return 'anonymous'; + } + + // Try to get an identifier from the authenticatable + // Use the object's hash as a fallback if no id property exists + if (property_exists($user, 'id')) { + return 'user:' . (string) $user->id; + } + + return 'user:' . spl_object_id($user); + } + + /** + * Resolve the session ID. + */ + private function resolveSessionIdentifier(): string + { + if (! $this->container->has(Session::class)) { + return 'no-session'; + } + + $session = $this->container->get(Session::class); + + return 'session:' . $session->id; + } + + /** + * Build a 429 Too Many Requests response. + */ + private function buildTooManyRequestsResponse(RateLimitResult $result): TooManyRequests + { + return new TooManyRequests( + retryAfter: $result->retryAfter, + limit: $result->limit, + remaining: $result->remaining, + resetAt: $result->resetAt, + ); + } + + /** + * Add rate limit headers to the response. + */ + private function addRateLimitHeaders(Response $response, RateLimitResult $result): Response + { + return $response + ->addHeader('X-RateLimit-Limit', (string) $result->limit) + ->addHeader('X-RateLimit-Remaining', (string) $result->remaining) + ->addHeader('X-RateLimit-Reset', (string) $result->resetAt); + } +} diff --git a/packages/router/src/RateLimiting/CacheRateLimiter.php b/packages/router/src/RateLimiting/CacheRateLimiter.php new file mode 100644 index 0000000000..0fb8ad50fc --- /dev/null +++ b/packages/router/src/RateLimiting/CacheRateLimiter.php @@ -0,0 +1,95 @@ +getCacheKey($key); + $timerKey = $this->getTimerKey($key); + + // Get or set the timer (when the window ends) + $resetAt = (int) $this->cache->get($timerKey); + + if ($resetAt === 0) { + $resetAt = time() + $decaySeconds; + $this->cache->put($timerKey, $resetAt, Duration::seconds($decaySeconds)); + } + + // Increment the counter + $attempts = $this->cache->increment($cacheKey); + + // Set expiration on first hit + if ($attempts === 1) { + $this->cache->put($cacheKey, 1, Duration::seconds($decaySeconds)); + } + + $remaining = max(0, $maxAttempts - $attempts); + + if ($attempts > $maxAttempts) { + return RateLimitResult::deny($maxAttempts, (int) $resetAt); + } + + return RateLimitResult::allow($maxAttempts, $remaining, $resetAt); + } + + public function attempts(string $key): int + { + return (int) ($this->cache->get($this->getCacheKey($key)) ?? 0); + } + + public function remaining(string $key, int $maxAttempts): int + { + return max(0, $maxAttempts - $this->attempts($key)); + } + + public function tooManyAttempts(string $key, int $maxAttempts): bool + { + return $this->attempts($key) >= $maxAttempts; + } + + public function clear(string $key): void + { + $this->cache->remove($this->getCacheKey($key)); + $this->cache->remove($this->getTimerKey($key)); + } + + public function availableAt(string $key): int + { + return (int) ($this->cache->get($this->getTimerKey($key)) ?? time()); + } + + private function getCacheKey(string $key): string + { + return self::PREFIX . $this->sanitizeKey($key); + } + + private function getTimerKey(string $key): string + { + return self::PREFIX . $this->sanitizeKey($key) . '_timer'; + } + + /** + * Sanitize a cache key to be compatible with all cache adapters. + * Replaces reserved characters with underscores. + */ + private function sanitizeKey(string $key): string + { + return preg_replace('/[{}()\/@:\\\\]/', '_', $key); + } +} diff --git a/packages/router/src/RateLimiting/RateLimitResult.php b/packages/router/src/RateLimiting/RateLimitResult.php new file mode 100644 index 0000000000..065a05d10c --- /dev/null +++ b/packages/router/src/RateLimiting/RateLimitResult.php @@ -0,0 +1,52 @@ +get(Cache::class), + ); + } +} diff --git a/packages/router/src/RateLimiting/Testing/TestingRateLimiter.php b/packages/router/src/RateLimiting/Testing/TestingRateLimiter.php new file mode 100644 index 0000000000..728201856a --- /dev/null +++ b/packages/router/src/RateLimiting/Testing/TestingRateLimiter.php @@ -0,0 +1,78 @@ + */ + private array $attempts = []; + + /** @var array */ + private array $timers = []; + + public function attempt(string $key, int $maxAttempts, int $decaySeconds): RateLimitResult + { + // Initialize timer if not set + if (! isset($this->timers[$key])) { + $this->timers[$key] = time() + $decaySeconds; + } + + // Increment attempts + if (! isset($this->attempts[$key])) { + $this->attempts[$key] = 0; + } + $this->attempts[$key]++; + + $attempts = $this->attempts[$key]; + $resetAt = $this->timers[$key]; + $remaining = max(0, $maxAttempts - $attempts); + + if ($attempts > $maxAttempts) { + return RateLimitResult::deny($maxAttempts, $resetAt); + } + + return RateLimitResult::allow($maxAttempts, $remaining, $resetAt); + } + + public function attempts(string $key): int + { + return $this->attempts[$key] ?? 0; + } + + public function remaining(string $key, int $maxAttempts): int + { + return max(0, $maxAttempts - $this->attempts($key)); + } + + public function tooManyAttempts(string $key, int $maxAttempts): bool + { + return $this->attempts($key) >= $maxAttempts; + } + + public function clear(string $key): void + { + unset($this->attempts[$key], $this->timers[$key]); + } + + public function availableAt(string $key): int + { + return $this->timers[$key] ?? time(); + } + + /** + * Clear all rate limiting state. + */ + public function clearAll(): void + { + $this->attempts = []; + $this->timers = []; + } +} diff --git a/packages/router/src/RouteConfig.php b/packages/router/src/RouteConfig.php index b5ce198358..d2bf3f2912 100644 --- a/packages/router/src/RouteConfig.php +++ b/packages/router/src/RouteConfig.php @@ -24,15 +24,16 @@ public function __construct( /** @var class-string<\Tempest\Router\ResponseProcessor>[] */ public array $responseProcessors = [], + /** @var array>> */ + public array $exceptionRenderers = [], + /** @var Middleware<\Tempest\Router\HttpMiddleware> */ public Middleware $middleware = new Middleware( HandleRouteExceptionMiddleware::class, MatchRouteMiddleware::class, - SetCookieMiddleware::class, + SetCookieHeadersMiddleware::class, HandleRouteSpecificMiddleware::class, ), - - public bool $throwHttpExceptions = true, ) {} public function apply(RouteConfig $newConfig): void @@ -47,4 +48,10 @@ public function addResponseProcessor(string $responseProcessor): void { $this->responseProcessors[] = $responseProcessor; } + + public function addExceptionRenderer(string $exceptionRenderer, int $priority): void + { + $this->exceptionRenderers[$priority] ??= []; + $this->exceptionRenderers[$priority][] = $exceptionRenderer; + } } diff --git a/packages/router/src/Routing/Construction/DiscoveredRoute.php b/packages/router/src/Routing/Construction/DiscoveredRoute.php index d8290a84d8..4ea2d27e29 100644 --- a/packages/router/src/Routing/Construction/DiscoveredRoute.php +++ b/packages/router/src/Routing/Construction/DiscoveredRoute.php @@ -25,16 +25,16 @@ public static function fromRoute(Route $route, array $decorators, MethodReflecto $route = $decorator->decorate($route); } - $paramInfo = self::getRouteParams($route->uri); + $uri = self::parseUriAndParameters($route->uri, $methodReflector); return new self( - $route->uri, - $route->method, - $paramInfo['names'], - $paramInfo['optional'], - $route->middleware, - $methodReflector, - $route->without ?? [], + uri: $uri['uri'], + method: $route->method, + parameters: $uri['names'], + optionalParameters: $uri['optional'], + middleware: $route->middleware, + handler: $methodReflector, + without: $route->without ?? [], ); } @@ -55,26 +55,60 @@ private function __construct( } /** + * Parses route parameters and infers type constraints from method signature. + * * @return array{ + * uri: string, * names: string[], * optional: array * } */ - private static function getRouteParams(string $uriPart): array + private static function parseUriAndParameters(string $uri, MethodReflector $methodReflector): array { $regex = '#\{' . self::ROUTE_PARAM_OPTIONAL_REGEX . self::ROUTE_PARAM_NAME_REGEX . self::ROUTE_PARAM_CUSTOM_REGEX . '\}#'; - preg_match_all($regex, $uriPart, $matches); - - $optionalMarkers = $matches[1] ?? []; - $names = $matches[2] ?? []; - + $names = []; $optional = []; - foreach ($names as $i => $name) { - $optional[$name] = $optionalMarkers[$i] === '?'; - } + + $modifiedUri = preg_replace_callback( + pattern: $regex, + callback: static function (array $matches) use ($methodReflector, &$names, &$optional) { + $isOptional = $matches[1] === '?'; + $paramName = $matches[2]; + $providedRegExp = $matches[3] ?? ''; + + $names[] = $paramName; + $optional[$paramName] = $isOptional; + + // Skip if there was already a constraint + if ($providedRegExp !== '') { + return $matches[0]; + } + + $parameter = $methodReflector->getParameter($paramName); + + if ($parameter === null) { + return $matches[0]; + } + + $constraint = match ($parameter->getType()->getName()) { + 'int', 'integer' => ':\d+', + 'float', 'double' => ':[\d.]+', + 'bool', 'boolean' => ':(0|1|true|false)', + default => null, + }; + + if ($constraint !== null) { + return sprintf('{%s%s%s}', $isOptional ? '?' : '', $paramName, $constraint); + } + + return $matches[0]; + }, + subject: $uri, + ); return [ + 'uri' => $modifiedUri, 'names' => $names, 'optional' => $optional, ]; diff --git a/packages/router/src/SecFetchMode.php b/packages/router/src/SecFetchMode.php new file mode 100644 index 0000000000..0e6767a4df --- /dev/null +++ b/packages/router/src/SecFetchMode.php @@ -0,0 +1,40 @@ +cookies->all() as $cookie) { - $cookieValue = $cookie->value === '' ? '' : $this->encrypter->encrypt($cookie->value)->serialize(); + $cookieValue = $cookie->value === '' + ? '' + : $this->encrypter->encrypt($cookie->value)->serialize(); + $response->addHeader('set-cookie', (string) $cookie->withValue($cookieValue)); } diff --git a/packages/router/src/SetCurrentUrlMiddleware.php b/packages/router/src/SetCurrentUrlMiddleware.php deleted file mode 100644 index 05089c3a7e..0000000000 --- a/packages/router/src/SetCurrentUrlMiddleware.php +++ /dev/null @@ -1,28 +0,0 @@ -method === Method::GET) { - $this->session->setPreviousUrl($request->uri); - } - - return $next($request); - } -} diff --git a/packages/router/src/Stateless.php b/packages/router/src/Stateless.php index 36804ea602..9f1c95d224 100644 --- a/packages/router/src/Stateless.php +++ b/packages/router/src/Stateless.php @@ -3,10 +3,10 @@ namespace Tempest\Router; use Attribute; -use Tempest\Http\Session\VerifyCsrfMiddleware; +use Tempest\Http\Session\ManageSessionMiddleware; /** - * Mark a route handler as stateless, causing all cookie- and session-related middleware to be skipped. + * Mark a route handler as stateless, causing all cookie and session-related middleware to be skipped. */ #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)] final class Stateless implements RouteDecorator @@ -15,9 +15,9 @@ public function decorate(Route $route): Route { $route->without = [ ...$route->without, - VerifyCsrfMiddleware::class, - SetCurrentUrlMiddleware::class, - SetCookieMiddleware::class, + PreventCrossSiteRequestsMiddleware::class, + ManageSessionMiddleware::class, + SetCookieHeadersMiddleware::class, ]; return $route; diff --git a/packages/router/src/Static/StaticGenerateCommand.php b/packages/router/src/Static/StaticGenerateCommand.php index 79f1a71867..4de293f5bf 100644 --- a/packages/router/src/Static/StaticGenerateCommand.php +++ b/packages/router/src/Static/StaticGenerateCommand.php @@ -14,6 +14,7 @@ use Tempest\Core\Kernel; use Tempest\EventBus\EventBus; use Tempest\Http\GenericRequest; +use Tempest\Http\HttpRequestFailed; use Tempest\Http\Method; use Tempest\Http\Status; use Tempest\HttpClient\HttpClient; @@ -88,6 +89,10 @@ public function __invoke( "{$event->path}", sprintf("%s DEAD %s", count($event->exception->links), Intl\pluralize('LINK', count($event->exception->links))), ), + $event->exception instanceof HttpRequestFailed => $this->keyValue( + "{$event->path}", + "HTTP {$event->exception->status->value}", + ), $event->exception instanceof InvalidStatusCodeException => $this->keyValue( "{$event->path}", "HTTP {$event->exception->status->value}", @@ -101,8 +106,6 @@ public function __invoke( }; }); - $this->routeConfig->throwHttpExceptions = false; - foreach ($this->staticPageConfig->staticPages as $staticPage) { /** @var DataProvider $dataProvider */ $dataProvider = $this->container->get($staticPage->dataProviderClass ?? GenericDataProvider::class); @@ -125,12 +128,10 @@ public function __invoke( $file = path($publicPath, $fileName); try { - $response = $this->router->dispatch( - new GenericRequest( - method: Method::GET, - uri: $uri, - ), - ); + $response = $this->router->dispatch(new GenericRequest( + method: Method::GET, + uri: $uri, + )); if ($response->status !== Status::OK) { throw new InvalidStatusCodeException($uri, $response->status); @@ -169,8 +170,9 @@ public function __invoke( ob_clean(); } - if ($exception instanceof ViewCompilationFailed && $exception->getPrevious() instanceof ManifestWasNotFound) { + if ($this->isFailureBecauseOfMissingManifest($exception)) { $this->error("A Vite build is needed for [{$uri}]. Run vite build first."); + return ExitCode::ERROR; } @@ -202,6 +204,15 @@ public function __invoke( : ExitCode::SUCCESS; } + private function isFailureBecauseOfMissingManifest(Throwable $exception): bool + { + if (! $exception instanceof ViewCompilationFailed) { + return false; + } + + return $exception->getPrevious() instanceof ManifestWasNotFound; + } + private function detectDeadLinks(string $uri, string $html, bool $checkExternal = false): array { $deadlinks = []; @@ -237,22 +248,28 @@ private function detectDeadLinks(string $uri, string $html, bool $checkExternal // Check internal links with router (/ or same base uri) if (Str\starts_with($link, '/') || Str\starts_with($this->getLinkWithoutProtocol($link), $this->getLinkWithoutProtocol($this->appConfig->baseUri))) { do { + $response = null; $target ??= match (true) { ! Str\starts_with($link, '/') => str($link)->stripStart($this->appConfig->baseUri)->finish('/')->toString(), default => $link, }; - $response = $this->router->dispatch(new GenericRequest( - method: Method::GET, - uri: $target, - )); + try { + $response = $this->router->dispatch(new GenericRequest( + method: Method::GET, + uri: $target, + )); + } catch (HttpRequestFailed) { + $deadlinks[] = $link; + continue; + } - if ($response->status->isRedirect()) { + if ($response?->status->isRedirect()) { $target = Arr\first($response->getHeader('Location')->values); } - } while ($response->status->isRedirect()); + } while ($response?->status->isRedirect()); - if ($response->status->isClientError() || $response->status->isServerError()) { + if ($response?->status->isClientError() || $response?->status->isServerError()) { $deadlinks[] = $link; } diff --git a/packages/router/src/Stubs/ControllerStub.php b/packages/router/src/Stubs/ControllerStub.php index 37d04cf834..d461c0284d 100644 --- a/packages/router/src/Stubs/ControllerStub.php +++ b/packages/router/src/Stubs/ControllerStub.php @@ -8,7 +8,7 @@ use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; #[SkipDiscovery] final class ControllerStub diff --git a/packages/router/src/functions.php b/packages/router/src/functions.php index 33848a8aaa..083c5f8379 100644 --- a/packages/router/src/functions.php +++ b/packages/router/src/functions.php @@ -9,7 +9,7 @@ use Tempest\Reflection\MethodReflector; use Tempest\Router\UriGenerator; -use function Tempest\get; +use function Tempest\Container\get; /** * Creates a valid URI to the given `$action`. diff --git a/packages/router/tests/RateLimiting/CacheRateLimiterTest.php b/packages/router/tests/RateLimiting/CacheRateLimiterTest.php new file mode 100644 index 0000000000..a19a5d90fb --- /dev/null +++ b/packages/router/tests/RateLimiting/CacheRateLimiterTest.php @@ -0,0 +1,115 @@ +cache = new TestingCache(tag: 'rate-limit-test', clock: $clock->toPsrClock()); + $this->rateLimiter = new CacheRateLimiter($this->cache); + } + + public function test_attempt_allows_within_limit(): void + { + $result = $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + + $this->assertTrue($result->allowed); + $this->assertSame(5, $result->limit); + $this->assertSame(4, $result->remaining); + } + + public function test_attempt_denies_after_exceeding_limit(): void + { + // Make 5 attempts (the limit) + for ($i = 0; $i < 5; $i++) { + $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + } + + // 6th attempt should be denied + $result = $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + + $this->assertFalse($result->allowed); + $this->assertSame(0, $result->remaining); + } + + public function test_attempts_returns_count(): void + { + $this->assertSame(0, $this->rateLimiter->attempts('test-key')); + + $this->rateLimiter->attempt('test-key', maxAttempts: 10, decaySeconds: 60); + $this->assertSame(1, $this->rateLimiter->attempts('test-key')); + + $this->rateLimiter->attempt('test-key', maxAttempts: 10, decaySeconds: 60); + $this->assertSame(2, $this->rateLimiter->attempts('test-key')); + } + + public function test_remaining_returns_available_attempts(): void + { + $this->assertSame(10, $this->rateLimiter->remaining('test-key', maxAttempts: 10)); + + $this->rateLimiter->attempt('test-key', maxAttempts: 10, decaySeconds: 60); + $this->assertSame(9, $this->rateLimiter->remaining('test-key', maxAttempts: 10)); + } + + public function test_too_many_attempts_returns_true_when_exceeded(): void + { + $this->assertFalse($this->rateLimiter->tooManyAttempts('test-key', maxAttempts: 2)); + + $this->rateLimiter->attempt('test-key', maxAttempts: 2, decaySeconds: 60); + $this->assertFalse($this->rateLimiter->tooManyAttempts('test-key', maxAttempts: 2)); + + $this->rateLimiter->attempt('test-key', maxAttempts: 2, decaySeconds: 60); + $this->assertTrue($this->rateLimiter->tooManyAttempts('test-key', maxAttempts: 2)); + } + + public function test_clear_resets_the_counter(): void + { + $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + + $this->assertSame(2, $this->rateLimiter->attempts('test-key')); + + $this->rateLimiter->clear('test-key'); + + $this->assertSame(0, $this->rateLimiter->attempts('test-key')); + } + + public function test_different_keys_are_independent(): void + { + $this->rateLimiter->attempt('key-a', maxAttempts: 2, decaySeconds: 60); + $this->rateLimiter->attempt('key-a', maxAttempts: 2, decaySeconds: 60); + + // key-a is at limit + $this->assertTrue($this->rateLimiter->tooManyAttempts('key-a', maxAttempts: 2)); + + // key-b should still be available + $this->assertFalse($this->rateLimiter->tooManyAttempts('key-b', maxAttempts: 2)); + } + + public function test_reset_time_is_set_correctly(): void + { + $beforeTime = time(); + $result = $this->rateLimiter->attempt('test-key', maxAttempts: 5, decaySeconds: 60); + $afterTime = time(); + + // Reset time should be approximately 60 seconds from now + $this->assertGreaterThanOrEqual($beforeTime + 60, $result->resetAt); + $this->assertLessThanOrEqual($afterTime + 60, $result->resetAt); + } +} diff --git a/packages/router/tests/RateLimiting/RateLimitResultTest.php b/packages/router/tests/RateLimiting/RateLimitResultTest.php new file mode 100644 index 0000000000..86c40c1a8b --- /dev/null +++ b/packages/router/tests/RateLimiting/RateLimitResultTest.php @@ -0,0 +1,46 @@ +assertTrue($result->allowed); + $this->assertSame(100, $result->limit); + $this->assertSame(99, $result->remaining); + $this->assertSame($resetAt, $result->resetAt); + $this->assertLessThanOrEqual(60, $result->retryAfter); + } + + public function test_deny_creates_failed_result(): void + { + $resetAt = time() + 30; + $result = RateLimitResult::deny(limit: 100, resetAt: $resetAt); + + $this->assertFalse($result->allowed); + $this->assertSame(100, $result->limit); + $this->assertSame(0, $result->remaining); + $this->assertSame($resetAt, $result->resetAt); + $this->assertLessThanOrEqual(30, $result->retryAfter); + } + + public function test_retry_after_is_never_negative(): void + { + $pastTime = time() - 100; + $result = RateLimitResult::deny(limit: 100, resetAt: $pastTime); + + $this->assertSame(0, $result->retryAfter); + } +} diff --git a/packages/storage/composer.json b/packages/storage/composer.json index 5fc1f61ac2..9156514f2c 100644 --- a/packages/storage/composer.json +++ b/packages/storage/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "league/flysystem": "^3.29.1", "tempest/container": "dev-main" }, diff --git a/packages/storage/src/Config/CustomStorageConfig.php b/packages/storage/src/Config/CustomStorageConfig.php index bf735b7c1c..a4bb6d64f0 100644 --- a/packages/storage/src/Config/CustomStorageConfig.php +++ b/packages/storage/src/Config/CustomStorageConfig.php @@ -5,7 +5,7 @@ use League\Flysystem\FilesystemAdapter; use UnitEnum; -use function Tempest\get; +use function Tempest\Container\get; final class CustomStorageConfig implements StorageConfig { diff --git a/packages/support/composer.json b/packages/support/composer.json index 1c377e3e15..3c52fe5d2b 100644 --- a/packages/support/composer.json +++ b/packages/support/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/container": "dev-main", "voku/portable-ascii": "^2.0.3", "symfony/uid": "^7.1" diff --git a/packages/support/src/Arr/ManipulatesArray.php b/packages/support/src/Arr/ManipulatesArray.php index 7bf8447a9d..633f4819ac 100644 --- a/packages/support/src/Arr/ManipulatesArray.php +++ b/packages/support/src/Arr/ManipulatesArray.php @@ -6,6 +6,7 @@ use Closure; use Stringable; +use Tempest\Mapper; use Tempest\Support\Str\ImmutableString; use function Tempest\Support\Json\encode; @@ -493,7 +494,7 @@ public function each(Closure $each): self */ public function map(Closure $map): self { - return $this->createOrModify(namespace\map_iterable($this->value, $map)); + return $this->createOrModify(map($this->value, $map)); } /** @@ -654,7 +655,7 @@ public function flatMap(Closure $map, int|float $depth = 1): self /** * Maps the items of the instance to the given object. * - * @see \Tempest\map() + * @see \Tempest\Mapper\map() * * @template T * @param class-string $to @@ -668,7 +669,7 @@ public function mapTo(string $to): self /** * Maps the first item of the instance to the given object. * - * @see \Tempest\map() + * @see \Tempest\Mapper\map() * * @template T * @param class-string $to @@ -676,13 +677,13 @@ public function mapTo(string $to): self */ public function mapFirstTo(string $to): mixed { - return \Tempest\map($this->first())->to($to); + return Mapper\map($this->first())->to($to); } /** * Maps the last item of the instance to the given object. * - * @see \Tempest\map() + * @see \Tempest\Mapper\map() * * @template T * @param class-string $to @@ -690,7 +691,7 @@ public function mapFirstTo(string $to): mixed */ public function mapLastTo(string $to): mixed { - return \Tempest\map($this->last())->to($to); + return Mapper\map($this->last())->to($to); } /** diff --git a/packages/support/src/Arr/functions.php b/packages/support/src/Arr/functions.php index b1a5695d0f..4a919e2c24 100644 --- a/packages/support/src/Arr/functions.php +++ b/packages/support/src/Arr/functions.php @@ -2,1283 +2,1263 @@ declare(strict_types=1); -namespace Tempest\Support\Arr { - use Closure; - use Countable; - use Generator; - use InvalidArgumentException; - use LogicException; - use Random\Randomizer; - use Tempest\Support\Str\ImmutableString; - use Traversable; - - use function sort as php_sort; - - /** - * Finds a value in the array and return the corresponding key if successful. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param (Closure(TValue, TKey): bool)|mixed $value The value to search for, a {@see Closure} will find the first item that returns true. - * @param bool $strict Whether to use strict comparison. - * - * @return array-key|null The key for `$value` if found, `null` otherwise. - */ - function find_key(iterable $array, mixed $value, bool $strict = false): int|string|null - { - $array = to_array($array); - - if (! $value instanceof Closure) { - $search = array_search($value, $array, $strict); - - return $search === false ? null : $search; // Keep empty values but convert false to null - } - - return array_find_key($array, static fn ($item, $key) => $value($item, $key) === true); +namespace Tempest\Support\Arr; + +use Closure; +use Countable; +use Generator; +use InvalidArgumentException; +use LogicException; +use Random\Randomizer; +use Tempest\Mapper; +use Tempest\Support\Str\ImmutableString; +use Traversable; + +use function sort as php_sort; + +/** + * Finds a value in the array and return the corresponding key if successful. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param (Closure(TValue, TKey): bool)|mixed $value The value to search for, a {@see Closure} will find the first item that returns true. + * @param bool $strict Whether to use strict comparison. + * + * @return array-key|null The key for `$value` if found, `null` otherwise. + */ +function find_key(iterable $array, mixed $value, bool $strict = false): int|string|null +{ + $array = to_array($array); + + if (! $value instanceof Closure) { + $search = array_search($value, $array, $strict); + + return $search === false ? null : $search; // Keep empty values but convert false to null } - /** - * Chunks the array into chunks of the given size. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param int $size The size of each chunk. - * @param bool $preserveKeys Whether to preserve the keys of the original array. - * - * @return array> - */ - function chunk(iterable $array, int $size, bool $preserveKeys = true): array - { - $array = to_array($array); - - if ($size <= 0) { - return []; - } - - $chunks = []; - foreach (array_chunk($array, $size, $preserveKeys) as $chunk) { - $chunks[] = $chunk; - } + return array_find_key($array, static fn ($item, $key) => $value($item, $key) === true); +} - return $chunks; +/** + * Chunks the array into chunks of the given size. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param int $size The size of each chunk. + * @param bool $preserveKeys Whether to preserve the keys of the original array. + * + * @return array> + */ +function chunk(iterable $array, int $size, bool $preserveKeys = true): array +{ + $array = to_array($array); + + if ($size <= 0) { + return []; } - /** - * Reduces the array to a single value using a callback. - * - * @template TKey of array-key - * @template TValue - * - * @template TReduceInitial - * @template TReduceReturnType - * - * @param iterable $array - * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback - * @param TReduceInitial $initial - * - * @return TReduceReturnType - */ - function reduce(iterable $array, callable $callback, mixed $initial = null): mixed - { - $array = to_array($array); - - $result = $initial; - - foreach ($array as $key => $value) { - $result = $callback($result, $value, $key); - } - - return $result; + $chunks = []; + foreach (array_chunk($array, $size, $preserveKeys) as $chunk) { + $chunks[] = $chunk; } - /** - * Gets a value by its key from the array and remove it. Mutates the array. - * - * @template TKey of array-key - * @template TValue - * - * @param array $array - * @param array-key $key - */ - function pull(array &$array, string|int $key, mixed $default = null): mixed - { - $value = get_by_key($array, $key, $default); - $array = namespace\forget_keys($array, $key); + return $chunks; +} - return $value; +/** + * Reduces the array to a single value using a callback. + * + * @template TKey of array-key + * @template TValue + * + * @template TReduceInitial + * @template TReduceReturnType + * + * @param iterable $array + * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback + * @param TReduceInitial $initial + * + * @return TReduceReturnType + */ +function reduce(iterable $array, callable $callback, mixed $initial = null): mixed +{ + $array = to_array($array); + + $result = $initial; + + foreach ($array as $key => $value) { + $result = $callback($result, $value, $key); } - /** - * Shuffles the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @return array - */ - function shuffle(iterable $array): array - { - return new Randomizer()->shuffleArray(to_array($array)); - } + return $result; +} - /** - * Removes the specified keys from the array. The array is not mutated. - * - * @template TKey of array-key - * @template TValue - * - * @param array $array - * @param array-key|array $keys The keys of the items to remove. - * @return array - */ - function remove_keys(iterable $array, string|int|array $keys): array - { - $array = to_array($array); +/** + * Gets a value by its key from the array and remove it. Mutates the array. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param array-key $key + */ +function pull(array &$array, string|int $key, mixed $default = null): mixed +{ + $value = get_by_key($array, $key, $default); + $array = namespace\forget_keys($array, $key); + + return $value; +} - return namespace\forget_keys($array, $keys); - } +/** + * Shuffles the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @return array + */ +function shuffle(iterable $array): array +{ + return new Randomizer()->shuffleArray(to_array($array)); +} - /** - * Removes the specified values from the array. The array is not mutated. - * - * @template TKey of array-key - * @template TValue - * - * @param array $array - * @param TValue|array $values The values to remove. - * @return array - */ - function remove_values(array $array, string|int|array $values): array - { - $array = to_array($array); +/** + * Removes the specified keys from the array. The array is not mutated. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param array-key|array $keys The keys of the items to remove. + * @return array + */ +function remove_keys(iterable $array, string|int|array $keys): array +{ + $array = to_array($array); + + return namespace\forget_keys($array, $keys); +} + +/** + * Removes the specified values from the array. The array is not mutated. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param TValue|array $values The values to remove. + * @return array + */ +function remove_values(array $array, string|int|array $values): array +{ + $array = to_array($array); + + return namespace\forget_values($array, $values); +} - return namespace\forget_values($array, $values); +/** + * Removes the specified keys from the array. The array is mutated. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param array-key|array $keys The keys of the items to remove. + * @return array + */ +function forget_keys(array &$array, string|int|array $keys): array +{ + $keys = is_array($keys) ? $keys : [$keys]; + + foreach ($keys as $key) { + unset($array[$key]); } - /** - * Removes the specified keys from the array. The array is mutated. - * - * @template TKey of array-key - * @template TValue - * - * @param array $array - * @param array-key|array $keys The keys of the items to remove. - * @return array - */ - function forget_keys(array &$array, string|int|array $keys): array - { - $keys = is_array($keys) ? $keys : [$keys]; + return $array; +} - foreach ($keys as $key) { +/** + * Removes the specified values from the array. The array is mutated. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param TValue|array $values The values to remove. + * @return array + */ +function forget_values(array &$array, string|int|array $values): array +{ + $values = is_array($values) ? $values : [$values]; + + foreach ($values as $value) { + if (! is_null($key = array_find_key($array, fn (mixed $match) => $value === $match))) { unset($array[$key]); } - - return $array; } - /** - * Removes the specified values from the array. The array is mutated. - * - * @template TKey of array-key - * @template TValue - * - * @param array $array - * @param TValue|array $values The values to remove. - * @return array - */ - function forget_values(array &$array, string|int|array $values): array - { - $values = is_array($values) ? $values : [$values]; + return $array; +} - foreach ($values as $value) { - if (! is_null($key = array_find_key($array, fn (mixed $match) => $value === $match))) { - unset($array[$key]); - } - } +/** + * Asserts whether the array is a list. + * An array is a list if its keys consist of consecutive numbers. + */ +function is_list(iterable $array): bool +{ + return array_is_list(to_array($array)); +} - return $array; - } +/** + * Asserts whether the array is a associative. + * An array is associative if its keys do not consist of consecutive numbers. + */ +function is_associative(iterable $array): bool +{ + return ! is_list(to_array($array)); +} - /** - * Asserts whether the array is a list. - * An array is a list if its keys consist of consecutive numbers. - */ - function is_list(iterable $array): bool - { - return array_is_list(to_array($array)); +/** + * Gets one or a specified number of random values from the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param int $number The number of random values to get. + * @param bool $preserveKey Whether to include the keys of the original array. + * + * @return array|mixed The random values, or a single value if `$number` is 1. + */ +function random(iterable $array, int $number = 1, bool $preserveKey = false): mixed +{ + $array = to_array($array); + + $count = count($array); + + if ($number > $count) { + throw new InvalidArgumentException("Cannot retrieve {$number} items from an array of {$count} items."); } - /** - * Asserts whether the array is a associative. - * An array is associative if its keys do not consist of consecutive numbers. - */ - function is_associative(iterable $array): bool - { - return ! is_list(to_array($array)); + if ($number < 1) { + throw new InvalidArgumentException("Random value only accepts positive integers, {$number} requested."); } - /** - * Gets one or a specified number of random values from the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param int $number The number of random values to get. - * @param bool $preserveKey Whether to include the keys of the original array. - * - * @return array|mixed The random values, or a single value if `$number` is 1. - */ - function random(iterable $array, int $number = 1, bool $preserveKey = false): mixed - { - $array = to_array($array); - - $count = count($array); + $keys = new Randomizer()->pickArrayKeys($array, $number); - if ($number > $count) { - throw new InvalidArgumentException("Cannot retrieve {$number} items from an array of {$count} items."); - } - - if ($number < 1) { - throw new InvalidArgumentException("Random value only accepts positive integers, {$number} requested."); - } - - $keys = new Randomizer()->pickArrayKeys($array, $number); - - $randomValues = []; - foreach ($keys as $key) { - $preserveKey - ? ($randomValues[$key] = $array[$key]) - : ($randomValues[] = $array[$key]); - } - - if ($preserveKey === false) { - shuffle($randomValues); - } - - return count($randomValues) > 1 - ? new ImmutableArray($randomValues) - : $randomValues[0]; + $randomValues = []; + foreach ($keys as $key) { + $preserveKey + ? ($randomValues[$key] = $array[$key]) + : ($randomValues[] = $array[$key]); } - /** - * Retrieves values from a given key in each sub-array of the current array. - * Optionally, you can pass a second parameter to also get the keys following the same pattern. - * - * @param string $value The key to assign the values from, support dot notation. - * @param string|null $key The key to assign the keys from, support dot notation. - */ - function pluck(iterable $array, string $value, ?string $key = null): array - { - $array = to_array($array); - - $results = []; - - foreach ($array as $item) { - if (! is_array($item)) { - continue; - } - - $itemValue = get_by_key($item, $value); - - /** - * Perform basic pluck if no key is given. - * Otherwise, also pluck the key as well. - */ - if (is_null($key)) { - $results[] = $itemValue; - } else { - $itemKey = get_by_key($item, $key); - $results[$itemKey] = $itemValue; - } - } - - return $results; + if ($preserveKey === false) { + shuffle($randomValues); } - /** - * Returns a new array with the specified values prepended. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param TValue $values - */ - function prepend(iterable $array, mixed ...$values): array - { - $array = to_array($array); + return count($randomValues) > 1 + ? new ImmutableArray($randomValues) + : $randomValues[0]; +} - foreach (array_reverse($values) as $value) { - $array = [$value, ...$array]; +/** + * Retrieves values from a given key in each sub-array of the current array. + * Optionally, you can pass a second parameter to also get the keys following the same pattern. + * + * @param string $value The key to assign the values from, support dot notation. + * @param string|null $key The key to assign the keys from, support dot notation. + */ +function pluck(iterable $array, string $value, ?string $key = null): array +{ + $array = to_array($array); + + $results = []; + + foreach ($array as $item) { + if (! is_array($item)) { + continue; } - return $array; - } + $itemValue = get_by_key($item, $value); - /** - * Appends the specified values to the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param TValue $values - */ - function append(iterable $array, mixed ...$values): array - { - $array = to_array($array); - - foreach ($values as $value) { - $array = [...$array, $value]; + /** + * Perform basic pluck if no key is given. + * Otherwise, also pluck the key as well. + */ + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = get_by_key($item, $key); + $results[$itemKey] = $itemValue; } - - return $array; - } - - /** - * Appends the specified value to the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param TValue $value - */ - function push(iterable $array, mixed $value): array - { - $array = to_array($array); - $array[] = $value; - - return $array; } - /** - * Pads the array to the specified size with a value. - */ - function pad(iterable $array, int $size, mixed $value): array - { - $array = to_array($array); + return $results; +} - return array_pad($array, $size, $value); +/** + * Returns a new array with the specified values prepended. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param TValue $values + */ +function prepend(iterable $array, mixed ...$values): array +{ + $array = to_array($array); + + foreach (array_reverse($values) as $value) { + $array = [$value, ...$array]; } - /** - * Reverses the keys and values of the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @return array - */ - function flip(iterable $array): array - { - $array = to_array($array); + return $array; +} - return array_flip($array); +/** + * Appends the specified values to the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param TValue $values + */ +function append(iterable $array, mixed ...$values): array +{ + $array = to_array($array); + + foreach ($values as $value) { + $array = [...$array, $value]; } - /** - * Returns a new array with only unique items from the original array. - * - * @param string|null|Closure $key The key to use as the uniqueness criteria in nested arrays. - * @param bool $shouldBeStrict Whether the comparison should be strict, only used when giving a key parameter. - */ - function unique(iterable $array, null|Closure|string $key = null, bool $shouldBeStrict = false): array - { - $array = to_array($array); - - if (is_null($key) && $shouldBeStrict === false) { - return array_unique($array, flags: SORT_REGULAR); - } - - $uniqueItems = []; - $uniqueFilteredValues = []; - - foreach ($array as $item) { - // Ensure we don't check raw values with key filter - if (! is_null($key) && ! is_array($item) && ! $key instanceof Closure) { - continue; - } - - $filterValue = match ($key instanceof Closure) { - true => $key($item, $array), - false => is_array($item) - ? get_by_key($item, $key) - : $item, - }; - - if (is_null($filterValue)) { - continue; - } - - if (in_array($filterValue, $uniqueFilteredValues, strict: $shouldBeStrict)) { - continue; - } - - $uniqueItems[] = $item; - $uniqueFilteredValues[] = $filterValue; - } + return $array; +} - return $uniqueItems; - } +/** + * Appends the specified value to the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param TValue $value + */ +function push(iterable $array, mixed $value): array +{ + $array = to_array($array); + $array[] = $value; + + return $array; +} - /** - * Returns a copy of the given array with only the items that are not present in any of the given arrays. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param array ...$arrays - */ - function diff(iterable $array, array ...$arrays): array - { - return array_diff(to_array($array), ...$arrays); - } +/** + * Pads the array to the specified size with a value. + */ +function pad(iterable $array, int $size, mixed $value): array +{ + $array = to_array($array); - /** - * Returns a new array with only the items whose keys are not present in any of the given arrays. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param array ...$arrays - */ - function diff_keys(iterable $array, array ...$arrays): array - { - return array_diff_key(to_array($array), ...$arrays); - } + return array_pad($array, $size, $value); +} - /** - * Returns a copy of the given array with only the items that are present in all of the given arrays. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param array ...$arrays - */ - function intersect(iterable $array, array ...$arrays): array - { - return array_intersect(to_array($array), ...$arrays); - } +/** + * Reverses the keys and values of the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @return array + */ +function flip(iterable $array): array +{ + $array = to_array($array); + + return array_flip($array); +} - /** - * Returns a copy of the given array with only the items whose keys are present in all of the given arrays. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param array ...$arrays - */ - function intersect_keys(iterable $array, array ...$arrays): array - { - return array_intersect_key(to_array($array), ...$arrays); +/** + * Returns a new array with only unique items from the original array. + * + * @param string|null|Closure $key The key to use as the uniqueness criteria in nested arrays. + * @param bool $shouldBeStrict Whether the comparison should be strict, only used when giving a key parameter. + */ +function unique(iterable $array, null|Closure|string $key = null, bool $shouldBeStrict = false): array +{ + $array = to_array($array); + + if (is_null($key) && $shouldBeStrict === false) { + return array_unique($array, flags: SORT_REGULAR); } - /** - * Merges the array with the given arrays. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param array ...$arrays The arrays to merge. - */ - function merge(iterable $array, iterable ...$arrays): array - { - return array_merge(to_array($array), ...array_map(to_array(...), $arrays)); - } + $uniqueItems = []; + $uniqueFilteredValues = []; - /** - * Creates a new array with this current array values as keys and the given values as values. - * - * @template TCombineValue - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param iterable $values - * - * @return array - */ - function combine(iterable $array, iterable $values): array - { - $array = to_array($array); - $values = to_array($values); - - if (count($array) !== count($values)) { - throw new InvalidArgumentException( - sprintf('Cannot combine arrays of different lengths (%d keys vs %d values)', count($array), count($values)), - ); + foreach ($array as $item) { + // Ensure we don't check raw values with key filter + if (! is_null($key) && ! is_array($item) && ! $key instanceof Closure) { + continue; } - return array_combine($array, $values); - } - - /** - * Asserts whether the given `$array` is equal to `$other` array. - */ - function equals(iterable $array, iterable $other): bool - { - $array = to_array($array); - $other = to_array($other); - - return $array === $other; - } - - /** - * Returns the first item in the array that matches the given `$filter`. - * If `$filter` is `null`, returns the first item. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param null|Closure(TValue $value, TKey $key): bool $filter - * - * @return TValue - */ - function first(iterable $array, ?Closure $filter = null, mixed $default = null): mixed - { - $array = to_array($array); + $filterValue = match ($key instanceof Closure) { + true => $key($item, $array), + false => is_array($item) + ? get_by_key($item, $key) + : $item, + }; - if ($array === []) { - return $default; + if (is_null($filterValue)) { + continue; } - if ($filter === null) { - return $array[array_key_first($array)] ?? $default; + if (in_array($filterValue, $uniqueFilteredValues, strict: $shouldBeStrict)) { + continue; } - return array_find($array, static fn ($value, $key) => $filter($value, $key)) ?? $default; + $uniqueItems[] = $item; + $uniqueFilteredValues[] = $filterValue; } - /** - * Returns the item at the given index in the specified array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * - * @return TValue - */ - function at(iterable $array, int $index, mixed $default = null): mixed - { - $array = to_array($array); + return $uniqueItems; +} - if ($index < 0) { - $index = abs($index) - 1; - $array = namespace\reverse($array); - } +/** + * Returns a copy of the given array with only the items that are not present in any of the given arrays. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param array ...$arrays + */ +function diff(iterable $array, array ...$arrays): array +{ + return array_diff(to_array($array), ...$arrays); +} - return namespace\get_by_key(array_values($array), key: $index, default: $default); - } +/** + * Returns a new array with only the items whose keys are not present in any of the given arrays. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param array ...$arrays + */ +function diff_keys(iterable $array, array ...$arrays): array +{ + return array_diff_key(to_array($array), ...$arrays); +} - /** - * Returns the last item in the array that matches the given `$filter`. - * If `$filter` is `null`, returns the last item. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param null|Closure(TValue $value, TKey $key): bool $filter - * - * @return TValue - */ - function last(iterable $array, ?Closure $filter = null, mixed $default = null): mixed - { - $array = to_array($array); +/** + * Returns a copy of the given array with only the items that are present in all of the given arrays. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param array ...$arrays + */ +function intersect(iterable $array, array ...$arrays): array +{ + return array_intersect(to_array($array), ...$arrays); +} - if ($array === []) { - return $default; - } +/** + * Returns a copy of the given array with only the items whose keys are present in all of the given arrays. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param array ...$arrays + */ +function intersect_keys(iterable $array, array ...$arrays): array +{ + return array_intersect_key(to_array($array), ...$arrays); +} - if ($filter === null) { - return $array[array_key_last($array)] ?? $default; - } +/** + * Merges the array with the given arrays. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param array ...$arrays The arrays to merge. + */ +function merge(iterable $array, iterable ...$arrays): array +{ + return array_merge(to_array($array), ...array_map(to_array(...), $arrays)); +} - return array_find(namespace\reverse($array), static fn ($value, $key) => $filter($value, $key)) ?? $default; +/** + * Creates a new array with this current array values as keys and the given values as values. + * + * @template TCombineValue + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param iterable $values + * + * @return array + */ +function combine(iterable $array, iterable $values): array +{ + $array = to_array($array); + $values = to_array($values); + + if (count($array) !== count($values)) { + throw new InvalidArgumentException( + sprintf('Cannot combine arrays of different lengths (%d keys vs %d values)', count($array), count($values)), + ); } - /** - * Returns a copy of the given array without the last value. - * - * @param mixed $value The popped value will be stored in this variable. - */ - function pop(iterable $array, mixed &$value = null): array - { - $array = to_array($array); - $value = namespace\last($array); + return array_combine($array, $values); +} - return array_slice($array, 0, -1); - } +/** + * Asserts whether the given `$array` is equal to `$other` array. + */ +function equals(iterable $array, iterable $other): bool +{ + $array = to_array($array); + $other = to_array($other); - /** - * Returns a copy of the given array without the first value. - * - * @param mixed $value The unshifted value will be stored in this variable - */ - function unshift(iterable $array, mixed &$value = null): array - { - $array = to_array($array); - $value = namespace\first($array); + return $array === $other; +} - return array_slice($array, 1); +/** + * Returns the first item in the array that matches the given `$filter`. + * If `$filter` is `null`, returns the first item. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param null|Closure(TValue $value, TKey $key): bool $filter + * + * @return TValue + */ +function first(iterable $array, ?Closure $filter = null, mixed $default = null): mixed +{ + $array = to_array($array); + + if ($array === []) { + return $default; } - /** - * Returns a copy of the given array in reverse order. - */ - function reverse(iterable $array): array - { - return array_reverse(to_array($array)); + if ($filter === null) { + return $array[array_key_first($array)] ?? $default; } - /** - * Asserts whether the array is empty. - */ - function is_empty(iterable $array): bool - { - return to_array($array) === []; - } + return array_find($array, static fn ($value, $key) => $filter($value, $key)) ?? $default; +} - /** - * Returns an instance of {@see \Tempest\Support\Str\ImmutableString} with the values of the array joined with the given `$glue`. - */ - function implode(iterable $array, string $glue): ImmutableString - { - return new ImmutableString(\implode($glue, to_array($array))); +/** + * Returns the item at the given index in the specified array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * + * @return TValue + */ +function at(iterable $array, int $index, mixed $default = null): mixed +{ + $array = to_array($array); + + if ($index < 0) { + $index = abs($index) - 1; + $array = namespace\reverse($array); } - /** - * Returns a copy of the given array with the keys of this array as values. - */ - function keys(iterable $array): array - { - return array_keys(to_array($array)); - } + return namespace\get_by_key(array_values($array), key: $index, default: $default); +} - /** - * Returns a copy of the given array without its keys. - */ - function values(iterable $array): array - { - return array_values(to_array($array)); +/** + * Returns the last item in the array that matches the given `$filter`. + * If `$filter` is `null`, returns the last item. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param null|Closure(TValue $value, TKey $key): bool $filter + * + * @return TValue + */ +function last(iterable $array, ?Closure $filter = null, mixed $default = null): mixed +{ + $array = to_array($array); + + if ($array === []) { + return $default; } - /** - * Returns a copy of the given array with only the items that pass the given `$filter`. - * If `$filter` is `null`, the new array will contain only values that are not `false` or `null`. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param null|Closure(TValue $value, TKey $key): bool $filter - */ - function filter(iterable $array, ?Closure $filter = null): array - { - $result = []; - $filter ??= static fn (mixed $value, mixed $_) => ! in_array($value, [false, null], strict: true); - - foreach (to_array($array) as $key => $value) { - if ($filter($value, $key)) { - $result[$key] = $value; - } - } - - return $result; + if ($filter === null) { + return $array[array_key_last($array)] ?? $default; } - /** - * Applies the given callback to all items of the array. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param Closure(TKey $value, TValue $key): void $each - */ - function each(iterable $array, Closure $each): array - { - $array = to_array($array); - - foreach ($array as $key => $value) { - $each($value, $key); - } + return array_find(namespace\reverse($array), static fn ($value, $key) => $filter($value, $key)) ?? $default; +} - return $array; - } +/** + * Returns a copy of the given array without the last value. + * + * @param mixed $value The popped value will be stored in this variable. + */ +function pop(iterable $array, mixed &$value = null): array +{ + $array = to_array($array); + $value = namespace\last($array); + + return array_slice($array, 0, -1); +} - /** - * Returns a copy of the given array with each item transformed by the given callback. - * - * @template TMapValue - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param Closure(TValue, TKey): TMapValue $map - * - * @return array - */ - function map_iterable(iterable $array, Closure $map): array - { - $result = []; +/** + * Returns a copy of the given array without the first value. + * + * @param mixed $value The unshifted value will be stored in this variable + */ +function unshift(iterable $array, mixed &$value = null): array +{ + $array = to_array($array); + $value = namespace\first($array); + + return array_slice($array, 1); +} - foreach (to_array($array) as $key => $value) { - $result[$key] = $map($value, $key); - } +/** + * Returns a copy of the given array in reverse order. + */ +function reverse(iterable $array): array +{ + return array_reverse(to_array($array)); +} - return $result; - } +/** + * Asserts whether the array is empty. + */ +function is_empty(iterable $array): bool +{ + return to_array($array) === []; +} - /** - * Returns a copy of the given array with each item transformed by the given callback. - * The callback must return a generator, associating a key and a value. - * - * ### Example - * ```php - * map_with_keys(['a', 'b'], fn (mixed $value, mixed $key) => yield $key => $value); - * ``` - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param Closure(TValue $value, TKey $key): Generator $map - */ - function map_with_keys(iterable $array, Closure $map): array - { - $result = []; +/** + * Returns an instance of {@see \Tempest\Support\Str\ImmutableString} with the values of the array joined with the given `$glue`. + */ +function implode(iterable $array, string $glue): ImmutableString +{ + return new ImmutableString(\implode($glue, to_array($array))); +} - foreach (to_array($array) as $key => $value) { - $generator = $map($value, $key); +/** + * Returns a copy of the given array with the keys of this array as values. + */ +function keys(iterable $array): array +{ + return array_keys(to_array($array)); +} - // @phpstan-ignore instanceof.alwaysTrue - if (! $generator instanceof Generator) { - throw new MapWithKeysDidNotUseAGenerator(); - } +/** + * Returns a copy of the given array without its keys. + */ +function values(iterable $array): array +{ + return array_values(to_array($array)); +} - $result[$generator->key()] = $generator->current(); +/** + * Returns a copy of the given array with only the items that pass the given `$filter`. + * If `$filter` is `null`, the new array will contain only values that are not `false` or `null`. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param null|Closure(TValue $value, TKey $key): bool $filter + */ +function filter(iterable $array, ?Closure $filter = null): array +{ + $result = []; + $filter ??= static fn (mixed $value, mixed $_) => ! in_array($value, [false, null], strict: true); + + foreach (to_array($array) as $key => $value) { + if ($filter($value, $key)) { + $result[$key] = $value; } - - return $result; } - /** - * Gets the value identified by the specified `$key`, or `$default` if no such value exists. - * @return mixed|ImmutableArray - */ - function get_by_key(iterable $array, int|string $key, mixed $default = null): mixed - { - $value = to_array($array); - - if (isset($value[$key])) { - return is_array($value[$key]) - ? new ImmutableArray($value[$key]) - : $value[$key]; - } + return $result; +} - $keys = is_int($key) - ? [$key] - : explode('.', $key); +/** + * Applies the given callback to all items of the array. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param Closure(TKey $value, TValue $key): void $each + */ +function each(iterable $array, Closure $each): array +{ + $array = to_array($array); + + foreach ($array as $key => $value) { + $each($value, $key); + } - foreach ($keys as $key) { - if (! is_array($value) && ! $value instanceof \ArrayAccess) { - return $default; - } + return $array; +} - if (! isset($value[$key])) { - return $default; - } +/** + * Returns a copy of the given array with each item transformed by the given callback. + * + * @template TMapValue + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param Closure(TValue, TKey): TMapValue $map + * + * @return array + */ +function map(iterable $array, Closure $map): array +{ + $result = []; + + foreach (to_array($array) as $key => $value) { + $result[$key] = $map($value, $key); + } - $value = $value[$key]; - } + return $result; +} - if (is_array($value)) { - return new ImmutableArray($value); +/** + * Returns a copy of the given array with each item transformed by the given callback. + * The callback must return a generator, associating a key and a value. + * + * ### Example + * ```php + * map_with_keys(['a', 'b'], fn (mixed $value, mixed $key) => yield $key => $value); + * ``` + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param Closure(TValue $value, TKey $key): Generator $map + */ +function map_with_keys(iterable $array, Closure $map): array +{ + $result = []; + + foreach (to_array($array) as $key => $value) { + $generator = $map($value, $key); + + // @phpstan-ignore instanceof.alwaysTrue + if (! $generator instanceof Generator) { + throw new MapWithKeysDidNotUseAGenerator(); } - return $value; + $result[$generator->key()] = $generator->current(); } - /** - * Asserts whether a value identified by the specified `$key` exists. Dot notation is supported. - */ - function has_key(iterable $array, int|string $key): bool - { - $array = to_array($array); - - if (isset($array[$key])) { - return true; - } + return $result; +} - $keys = is_int($key) - ? [$key] - : explode('.', $key); +/** + * Gets the value identified by the specified `$key`, or `$default` if no such value exists. + * @return mixed|ImmutableArray + */ +function get_by_key(iterable $array, int|string $key, mixed $default = null): mixed +{ + $value = to_array($array); + + if (isset($value[$key])) { + return is_array($value[$key]) + ? new ImmutableArray($value[$key]) + : $value[$key]; + } - foreach ($keys as $key) { - if (! isset($array[$key])) { - return false; - } + $keys = is_int($key) + ? [$key] + : explode('.', $key); - $array = &$array[$key]; + foreach ($keys as $key) { + if (! is_array($value) && ! $value instanceof \ArrayAccess) { + return $default; } - return true; - } + if (! isset($value[$key])) { + return $default; + } - /** - * Asserts whether the given array contains a value that can be identified by `$search`. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param TValue|Closure(TValue, TKey): bool $search - */ - function contains(iterable $array, mixed $search): bool - { - $array = to_array($array); - $search = $search instanceof Closure - ? $search - : static fn (mixed $value) => $value === $search; - - return array_any($array, static fn ($value, $key) => $search($value, $key)); + $value = $value[$key]; } - /** - * Asserts whether all items in the given array pass the given `$callback`. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param Closure(TValue, TKey): bool $callback - * - * @return bool If the collection is empty, returns `true`. - */ - function every(iterable $array, ?Closure $callback = null): bool - { - $array = to_array($array); - $callback ??= static fn (mixed $value) => ! is_null($value); - - return array_all($array, static fn (mixed $value, int|string $key) => $callback($value, $key)); + if (is_array($value)) { + return new ImmutableArray($value); } - /** - * Returns a copy of the array with the given `$value` associated to the given `$key`. - */ - function set_by_key(iterable $array, string $key, mixed $value): array - { - $array = to_array($array); - $current = &$array; - $keys = explode('.', $key); + return $value; +} - foreach ($keys as $i => $key) { - // If this is the last key in dot notation, we don't - // need to go through the next steps. - if (count($keys) === 1) { - break; - } +/** + * Asserts whether a value identified by the specified `$key` exists. Dot notation is supported. + */ +function has_key(iterable $array, int|string $key): bool +{ + $array = to_array($array); - // Remove the current key from our keys array - // so that later we can use the first value - // from that array as our key. - unset($keys[$i]); + if (isset($array[$key])) { + return true; + } - // If we know this key is not an array, make it one. - if (! isset($current[$key]) || ! is_array($current[$key])) { - $current[$key] = []; - } + $keys = is_int($key) + ? [$key] + : explode('.', $key); - // Set the context to this key. - $current = &$current[$key]; + foreach ($keys as $key) { + if (! isset($array[$key])) { + return false; } - // Pull the first key out of the array - // and use it to set the value. - $current[array_shift($keys)] = $value; - - return $array; + $array = &$array[$key]; } - /** - * Returns a copy of the array that converts the dot-notated keys to a set of nested arrays. - */ - function undot(iterable $array): array - { - $array = to_array($array); - - $unwrapValue = function (string|int $key, mixed $value) { - if (is_int($key)) { - return [$key => $value]; - } - - $keys = explode('.', $key); + return true; +} - for ($i = array_key_last($keys); $i >= 0; $i--) { - $currentKey = $keys[$i]; +/** + * Asserts whether the given array contains a value that can be identified by `$search`. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param TValue|Closure(TValue, TKey): bool $search + */ +function contains(iterable $array, mixed $search): bool +{ + $array = to_array($array); + $search = $search instanceof Closure + ? $search + : static fn (mixed $value) => $value === $search; + + return array_any($array, static fn ($value, $key) => $search($value, $key)); +} - $value = [$currentKey => $value]; - } +/** + * Asserts whether all items in the given array pass the given `$callback`. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param Closure(TValue, TKey): bool $callback + * + * @return bool If the collection is empty, returns `true`. + */ +function every(iterable $array, ?Closure $callback = null): bool +{ + $array = to_array($array); + $callback ??= static fn (mixed $value) => ! is_null($value); + + return array_all($array, static fn (mixed $value, int|string $key) => $callback($value, $key)); +} - return $value; - }; +/** + * Returns a copy of the array with the given `$value` associated to the given `$key`. + */ +function set_by_key(iterable $array, string $key, mixed $value): array +{ + $array = to_array($array); + $current = &$array; + $keys = explode('.', $key); + + foreach ($keys as $i => $key) { + // If this is the last key in dot notation, we don't + // need to go through the next steps. + if (count($keys) === 1) { + break; + } - $unwrapped = []; + // Remove the current key from our keys array + // so that later we can use the first value + // from that array as our key. + unset($keys[$i]); - foreach ($array as $key => $value) { - $unwrapped[] = $unwrapValue($key, $value); + // If we know this key is not an array, make it one. + if (! isset($current[$key]) || ! is_array($current[$key])) { + $current[$key] = []; } - return array_merge_recursive(...$unwrapped); + // Set the context to this key. + $current = &$current[$key]; } - /** - * Returns a copy of the array that converts nested arrays to a single-dimension dot-notation array. - */ - function dot(iterable $array, string $prefix = ''): array - { - $array = to_array($array); + // Pull the first key out of the array + // and use it to set the value. + $current[array_shift($keys)] = $value; - $result = []; + return $array; +} - foreach ($array as $key => $value) { - if (is_array($value)) { - $result = [...$result, ...dot($value, $prefix . $key . '.')]; - } else { - $result[$prefix . $key] = $value; - } - } +/** + * Returns a copy of the array that converts the dot-notated keys to a set of nested arrays. + */ +function undot(iterable $array): array +{ + $array = to_array($array); - return $result; - } + $unwrapValue = function (string|int $key, mixed $value) { + if (is_int($key)) { + return [$key => $value]; + } - /** - * Joins all values using the specified `$glue`. The last item of the string is separated by `$finalGlue`. - */ - function join(iterable $array, string $glue = ', ', ?string $finalGlue = ' and '): ImmutableString - { - $array = to_array($array); + $keys = explode('.', $key); - if ($finalGlue === '' || is_null($finalGlue)) { - return namespace\implode($array, $glue); - } + for ($i = array_key_last($keys); $i >= 0; $i--) { + $currentKey = $keys[$i]; - if (namespace\is_empty($array)) { - return new ImmutableString(''); + $value = [$currentKey => $value]; } - $parts = namespace\pop($array, $last); + return $value; + }; - if (! namespace\is_empty($parts)) { - return namespace\implode($parts, $glue)->append($finalGlue, $last); - } + $unwrapped = []; - return new ImmutableString($last); + foreach ($array as $key => $value) { + $unwrapped[] = $unwrapValue($key, $value); } - /** - * Returns a copy of the array flattened to a single level, or until the specified `$depth` is reached. - * - * ### Example - * ```php - * flatten(['foo', ['bar', 'baz']]); // ['foo', 'bar', 'baz'] - * ``` - */ - function flatten(iterable $array, int|float $depth = INF): array - { - $array = to_array($array); - $result = []; - - foreach ($array as $item) { - if (! is_array($item)) { - $result[] = $item; + return array_merge_recursive(...$unwrapped); +} - continue; - } +/** + * Returns a copy of the array that converts nested arrays to a single-dimension dot-notation array. + */ +function dot(iterable $array, string $prefix = ''): array +{ + $array = to_array($array); - $values = $depth === 1 - ? namespace\values($item) - : namespace\flatten($item, $depth - 1); + $result = []; - foreach ($values as $value) { - $result[] = $value; - } + foreach ($array as $key => $value) { + if (is_array($value)) { + $result = [...$result, ...dot($value, $prefix . $key . '.')]; + } else { + $result[$prefix . $key] = $value; } - - return $result; } - /** - * Returns a copy of the array grouped by the result of the given `$keyExtractor`. - * The keys of the resulting array are the values returned by the `$keyExtractor`. - * - * ### Example - * ```php - * group_by( - * [ - * ['country' => 'france', 'continent' => 'europe'], - * ['country' => 'Sweden', 'continent' => 'europe'], - * ['country' => 'USA', 'continent' => 'america'] - * ], - * fn($item) => $item['continent'] - * ); - * // [ - * // 'europe' => [ - * // ['country' => 'france', 'continent' => 'europe'], - * // ['country' => 'Sweden', 'continent' => 'europe'] - * // ], - * // 'america' => [ - * // ['country' => 'USA', 'continent' => 'america'] - * // ] - * // ] - * ``` - * - * @template TKey of array-key - * @template TValue - * @param iterable $array - * @param Closure(TValue, TKey): array-key $keyExtracor - */ - function group_by(iterable $array, Closure $keyExtracor): array - { - $array = to_array($array); - - $result = []; - - foreach ($array as $key => $item) { - $key = $keyExtracor($item, $key); + return $result; +} - $result[$key][] = $item; - } +/** + * Joins all values using the specified `$glue`. The last item of the string is separated by `$finalGlue`. + */ +function join(iterable $array, string $glue = ', ', ?string $finalGlue = ' and '): ImmutableString +{ + $array = to_array($array); - return $result; + if ($finalGlue === '' || is_null($finalGlue)) { + return namespace\implode($array, $glue); } - /** - * Returns a copy of the given array, with each item transformed by the given callback, then flattens it by the specified depth. - * - * @template TMapValue - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param Closure(TValue,TKey): TMapValue[] $map - * - * @return array - */ - function flat_map(iterable $array, Closure $map, int|float $depth = 1): array - { - return namespace\flatten(namespace\map_iterable(to_array($array), $map), $depth); + if (namespace\is_empty($array)) { + return new ImmutableString(''); } - /** - * Returns a new array with the value of the given array mapped to the given object. - * - * @see Tempest\map() - * - * @template T - * @param class-string $to - */ - function map_to(iterable $array, string $to): array - { - return \Tempest\map(to_array($array))->collection()->to($to); - } + $parts = namespace\pop($array, $last); - /** - * Returns a copy of the given array sorted by its values. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). - * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. - * Defaults to `null`, which auto-detects preservation based on array type (associative or list). - * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. - * @return array Key type depends on whether array keys are preserved or not. - */ - function sort(iterable $array, bool $desc = false, ?bool $preserveKeys = null, int $flags = SORT_REGULAR): array - { - $array = to_array($array); + if (! namespace\is_empty($parts)) { + return namespace\implode($parts, $glue)->append($finalGlue, $last); + } - if ($preserveKeys === null) { - $preserveKeys = is_associative($array); - } + return new ImmutableString($last); +} - if ($preserveKeys) { - $desc ? arsort($array, $flags) : asort($array, $flags); - } else { - $desc ? rsort($array, $flags) : php_sort($array, $flags); +/** + * Returns a copy of the array flattened to a single level, or until the specified `$depth` is reached. + * + * ### Example + * ```php + * flatten(['foo', ['bar', 'baz']]); // ['foo', 'bar', 'baz'] + * ``` + */ +function flatten(iterable $array, int|float $depth = INF): array +{ + $array = to_array($array); + $result = []; + + foreach ($array as $item) { + if (! is_array($item)) { + $result[] = $item; + + continue; } - return $array; - } - - /** - * Returns a copy of the given array sorted by its values using a callback function. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param \Closure(TValue $a, TValue $b) $callback The function to use for comparing values. It should accept two parameters and return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second. - * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. Defaults to `null`, which auto-detects preservation based on array type (associative or list). - * @return array Key type depends on whether array keys are preserved or not. - */ - function sort_by_callback(iterable $array, callable $callback, ?bool $preserveKeys = null): array - { - $array = to_array($array); + $values = $depth === 1 + ? namespace\values($item) + : namespace\flatten($item, $depth - 1); - if ($preserveKeys === null) { - $preserveKeys = is_associative($array); + foreach ($values as $value) { + $result[] = $value; } + } - $preserveKeys ? uasort($array, $callback) : usort($array, $callback); + return $result; +} - return $array; +/** + * Returns a copy of the array grouped by the result of the given `$keyExtractor`. + * The keys of the resulting array are the values returned by the `$keyExtractor`. + * + * ### Example + * ```php + * group_by( + * [ + * ['country' => 'france', 'continent' => 'europe'], + * ['country' => 'Sweden', 'continent' => 'europe'], + * ['country' => 'USA', 'continent' => 'america'] + * ], + * fn($item) => $item['continent'] + * ); + * // [ + * // 'europe' => [ + * // ['country' => 'france', 'continent' => 'europe'], + * // ['country' => 'Sweden', 'continent' => 'europe'] + * // ], + * // 'america' => [ + * // ['country' => 'USA', 'continent' => 'america'] + * // ] + * // ] + * ``` + * + * @template TKey of array-key + * @template TValue + * @param iterable $array + * @param Closure(TValue, TKey): array-key $keyExtracor + */ +function group_by(iterable $array, Closure $keyExtracor): array +{ + $array = to_array($array); + + $result = []; + + foreach ($array as $key => $item) { + $key = $keyExtracor($item, $key); + + $result[$key][] = $item; } - /** - * Returns a copy of the given array sorted by its keys. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). - * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. - * @return array - */ - function sort_keys(iterable $array, bool $desc = false, int $flags = SORT_REGULAR): array - { - $array = to_array($array); - - $desc ? krsort($array, $flags) : ksort($array, $flags); + return $result; +} - return $array; - } +/** + * Returns a copy of the given array, with each item transformed by the given callback, then flattens it by the specified depth. + * + * @template TMapValue + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param Closure(TValue,TKey): TMapValue[] $map + * + * @return array + */ +function flat_map(iterable $array, Closure $map, int|float $depth = 1): array +{ + return namespace\flatten(map(to_array($array), $map), $depth); +} - /** - * Returns a copy of the given array sorted by its keys using a callback function. - * - * @template TKey of array-key - * @template TValue - * - * @param iterable $array - * @param callable $callback The function to use for comparing keys. It should accept two parameters - * and return an integer less than, equal to, or greater than zero if the - * first argument is considered to be respectively less than, equal to, or - * greater than the second. - * @return array - */ - function sort_keys_by_callback(iterable $array, callable $callback): array - { - $array = to_array($array); +/** + * Returns a new array with the value of the given array mapped to the given object. + * + * @see Tempest\Mapper\map() + * + * @template T + * @param class-string $to + */ +function map_to(iterable $array, string $to): array +{ + return Mapper\map(to_array($array))->collection()->to($to); +} - uksort($array, $callback); +/** + * Returns a copy of the given array sorted by its values. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). + * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. + * Defaults to `null`, which auto-detects preservation based on array type (associative or list). + * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. + * @return array Key type depends on whether array keys are preserved or not. + */ +function sort(iterable $array, bool $desc = false, ?bool $preserveKeys = null, int $flags = SORT_REGULAR): array +{ + $array = to_array($array); + + if ($preserveKeys === null) { + $preserveKeys = is_associative($array); + } - return $array; + if ($preserveKeys) { + $desc ? arsort($array, $flags) : asort($array, $flags); + } else { + $desc ? rsort($array, $flags) : php_sort($array, $flags); } - /** - * Extracts a part of the array. - * - * ### Example - * ```php - * slice([1, 2, 3, 4, 5], 2); // [3, 4, 5] - * ``` - */ - function slice(iterable $array, int $offset, ?int $length = null): array - { - $array = to_array($array); - $length ??= count($array) - $offset; + return $array; +} - return array_slice($array, $offset, $length); +/** + * Returns a copy of the given array sorted by its values using a callback function. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param \Closure(TValue $a, TValue $b) $callback The function to use for comparing values. It should accept two parameters and return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second. + * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`. Defaults to `null`, which auto-detects preservation based on array type (associative or list). + * @return array Key type depends on whether array keys are preserved or not. + */ +function sort_by_callback(iterable $array, callable $callback, ?bool $preserveKeys = null): array +{ + $array = to_array($array); + + if ($preserveKeys === null) { + $preserveKeys = is_associative($array); } - /** - * Returns a new list containing the range of numbers from `$start` to `$end` - * inclusive, with the step between elements being `$step` if provided, or 1 by - * default. - * - * If `$start > $end`, it returns a descending range instead of an empty one. - * - * Examples: - * - * range(0, 5) - * => array(0, 1, 2, 3, 4, 5) - * - * range(5, 0) - * => array(5, 4, 3, 2, 1, 0) - * - * range(0.0, 3.0, 0.5) - * => array(0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0) - * - * range(3.0, 0.0, -0.5) - * => array(3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0) - * - * @template T of int|float - * - * @param T $start - * @param T $end - * @param T|null $step - * - * @throws LogicException If $start < $end, and $step is negative. - * - * @return non-empty-list - */ - function range(int|float $start, int|float $end, int|float|null $step = null): array - { - if ((float) $start === (float) $end) { - return [$start]; - } + $preserveKeys ? uasort($array, $callback) : usort($array, $callback); - if ($start < $end) { - if (null === $step) { - /** @var T $step */ - $step = 1; - } + return $array; +} - if ($step < 0) { - throw new LogicException('If $end is greater than $start, then $step must be positive or null.'); - } +/** + * Returns a copy of the given array sorted by its keys. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending). + * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`. + * @return array + */ +function sort_keys(iterable $array, bool $desc = false, int $flags = SORT_REGULAR): array +{ + $array = to_array($array); + + $desc ? krsort($array, $flags) : ksort($array, $flags); + + return $array; +} - $result = []; +/** + * Returns a copy of the given array sorted by its keys using a callback function. + * + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param callable $callback The function to use for comparing keys. It should accept two parameters + * and return an integer less than, equal to, or greater than zero if the + * first argument is considered to be respectively less than, equal to, or + * greater than the second. + * @return array + */ +function sort_keys_by_callback(iterable $array, callable $callback): array +{ + $array = to_array($array); + + uksort($array, $callback); + + return $array; +} - /** - * @var int|float $start - * @var int|float $step - */ - for ($i = $start; $i <= $end; $i += $step) { - $result[] = $i; - } +/** + * Extracts a part of the array. + * + * ### Example + * ```php + * slice([1, 2, 3, 4, 5], 2); // [3, 4, 5] + * ``` + */ +function slice(iterable $array, int $offset, ?int $length = null): array +{ + $array = to_array($array); + $length ??= count($array) - $offset; + + return array_slice($array, $offset, $length); +} - return $result; - } +/** + * Returns a new list containing the range of numbers from `$start` to `$end` + * inclusive, with the step between elements being `$step` if provided, or 1 by + * default. + * + * If `$start > $end`, it returns a descending range instead of an empty one. + * + * Examples: + * + * range(0, 5) + * => array(0, 1, 2, 3, 4, 5) + * + * range(5, 0) + * => array(5, 4, 3, 2, 1, 0) + * + * range(0.0, 3.0, 0.5) + * => array(0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0) + * + * range(3.0, 0.0, -0.5) + * => array(3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0) + * + * @template T of int|float + * + * @param T $start + * @param T $end + * @param T|null $step + * + * @throws LogicException If $start < $end, and $step is negative. + * + * @return non-empty-list + */ +function range(int|float $start, int|float $end, int|float|null $step = null): array +{ + if ((float) $start === (float) $end) { + return [$start]; + } + if ($start < $end) { if (null === $step) { /** @var T $step */ - $step = -1; + $step = 1; } - if ($step > 0) { - throw new LogicException('If $start is greater than $end, then $step must be negative or null.'); + if ($step < 0) { + throw new LogicException('If $end is greater than $start, then $step must be positive or null.'); } $result = []; @@ -1287,109 +1267,130 @@ function range(int|float $start, int|float $end, int|float|null $step = null): a * @var int|float $start * @var int|float $step */ - for ($i = $start; $i >= $end; $i += $step) { + for ($i = $start; $i <= $end; $i += $step) { $result[] = $i; } return $result; } + if (null === $step) { + /** @var T $step */ + $step = -1; + } + + if ($step > 0) { + throw new LogicException('If $start is greater than $end, then $step must be negative or null.'); + } + + $result = []; + /** - * Returns a pair containing lists for which the given predicate returned `true` and `false`, respectively. - * - * @template T - * - * @param iterable $iterable - * @param (Closure(T): bool) $predicate - * - * @return array{0: array, 1: array} + * @var int|float $start + * @var int|float $step */ - function partition(iterable $iterable, Closure $predicate): array - { - $success = []; - $failure = []; - - foreach ($iterable as $value) { - if ($predicate($value)) { - $success[] = $value; - continue; - } + for ($i = $start; $i >= $end; $i += $step) { + $result[] = $i; + } + + return $result; +} - $failure[] = $value; +/** + * Returns a pair containing lists for which the given predicate returned `true` and `false`, respectively. + * + * @template T + * + * @param iterable $iterable + * @param (Closure(T): bool) $predicate + * + * @return array{0: array, 1: array} + */ +function partition(iterable $iterable, Closure $predicate): array +{ + $success = []; + $failure = []; + + foreach ($iterable as $value) { + if ($predicate($value)) { + $success[] = $value; + continue; } - return [$success, $failure]; + $failure[] = $value; } - /** - * Wraps the specified `$input` into an array. If the `$input` is already an array, it is returned. - * As opposed to {@see \Tempest\Support\Arr\to_array}, this function does not convert {@see Traversable} and {@see Countable} instances to arrays. - * - * @template TKey of array-key - * @template TValue - * @param null|array|ArrayInterface|TValue $input - * @return array - */ - function wrap(mixed $input = []): array - { - if (is_array($input)) { - return $input; - } + return [$success, $failure]; +} - if ($input instanceof ArrayInterface) { - return $input->toArray(); - } +/** + * Wraps the specified `$input` into an array. If the `$input` is already an array, it is returned. + * As opposed to {@see \Tempest\Support\Arr\to_array}, this function does not convert {@see Traversable} and {@see Countable} instances to arrays. + * + * @template TKey of array-key + * @template TValue + * @param null|array|ArrayInterface|TValue $input + * @return array + */ +function wrap(mixed $input = []): array +{ + if (is_array($input)) { + return $input; + } - if ($input === null) { - return []; - } + if ($input instanceof ArrayInterface) { + return $input->toArray(); + } - return [$input]; + if ($input === null) { + return []; } - /** - * Converts various data structures to a PHP array. - * As opposed to `{@see \Tempest\Support\Arr\wrap}`, this function converts {@see Traversable} and {@see Countable} instances to arrays. - * - * @param mixed $input Any value that can be converted to an array: - * - Arrays are returned as-is - * - Scalar values are wrapped in an array - * - Traversable objects are converted using `{@see iterator_to_array}` - * - {@see Countable} objects are converted to arrays - * - {@see null} becomes an empty array - */ - function to_array(mixed $input): array - { - if (is_array($input)) { - return $input; - } + return [$input]; +} - if ($input instanceof ArrayInterface) { - return $input->toArray(); - } +/** + * Converts various data structures to a PHP array. + * As opposed to `{@see \Tempest\Support\Arr\wrap}`, this function converts {@see Traversable} and {@see Countable} instances to arrays. + * + * @param mixed $input Any value that can be converted to an array: + * - Arrays are returned as-is + * - Scalar values are wrapped in an array + * - Traversable objects are converted using `{@see iterator_to_array}` + * - {@see Countable} objects are converted to arrays + * - {@see null} becomes an empty array + */ +function to_array(mixed $input): array +{ + if (is_array($input)) { + return $input; + } - if ($input instanceof Traversable) { - return iterator_to_array($input); - } + if ($input instanceof ArrayInterface) { + return $input->toArray(); + } - if ($input instanceof Countable) { - $count = count($input); - $result = []; + if ($input instanceof Traversable) { + return iterator_to_array($input); + } - for ($i = 0; $i < $count; $i++) { - if (isset($input[$i])) { - $result[$i] = $input[$i]; - } - } + if ($input instanceof Countable) { + $count = count($input); + $result = []; - return $result; + for ($i = 0; $i < $count; $i++) { + if (isset($input[$i])) { + $result[$i] = $input[$i]; + } } - // Scalar values (string, int, float, bool) and objects are wrapped - if (is_scalar($input) || is_object($input)) { - return [$input]; - } + return $result; + } - return []; + // Scalar values (string, int, float, bool) and objects are wrapped + if (is_scalar($input) || is_object($input)) { + return [$input]; } + + return []; } diff --git a/packages/support/src/Comparison/functions.php b/packages/support/src/Comparison/functions.php index af48429702..7b248da655 100644 --- a/packages/support/src/Comparison/functions.php +++ b/packages/support/src/Comparison/functions.php @@ -1,109 +1,109 @@ value; - } +namespace Tempest\Support\Comparison; - /** - * @template T - * - * @param T $a - * @param T $b - */ - function not_equal(mixed $a, mixed $b): bool - { - return compare($a, $b) !== Order::EQUAL; - } +/** + * @template T + * + * @param T $a + * @param T $b + * + * This method can be used as a sorter callback function for Comparable items. + * + * Vec\sort($list, Comparable\sort(...)) + */ +function sort(mixed $a, mixed $b): int +{ + return compare($a, $b)->value; +} - /** - * @template T - * - * @param T $a - * @param T $b - */ - function less(mixed $a, mixed $b): bool - { - return compare($a, $b) === Order::LESS; - } +/** + * @template T + * + * @param T $a + * @param T $b + */ +function not_equal(mixed $a, mixed $b): bool +{ + return compare($a, $b) !== Order::EQUAL; +} - /** - * @template T - * - * @param T $a - * @param T $b - */ - function less_or_equal(mixed $a, mixed $b): bool - { - $order = compare($a, $b); +/** + * @template T + * + * @param T $a + * @param T $b + */ +function less(mixed $a, mixed $b): bool +{ + return compare($a, $b) === Order::LESS; +} - return $order === Order::EQUAL || $order === Order::LESS; - } +/** + * @template T + * + * @param T $a + * @param T $b + */ +function less_or_equal(mixed $a, mixed $b): bool +{ + $order = compare($a, $b); - /** - * @template T - * - * @param T $a - * @param T $b - */ - function greater(mixed $a, mixed $b): bool - { - return compare($a, $b) === Order::GREATER; - } + return $order === Order::EQUAL || $order === Order::LESS; +} - /** - * @template T - * - * @param T $a - * @param T $b - */ - function greater_or_equal(mixed $a, mixed $b): bool - { - $order = compare($a, $b); +/** + * @template T + * + * @param T $a + * @param T $b + */ +function greater(mixed $a, mixed $b): bool +{ + return compare($a, $b) === Order::GREATER; +} - return $order === Order::EQUAL || $order === Order::GREATER; - } +/** + * @template T + * + * @param T $a + * @param T $b + */ +function greater_or_equal(mixed $a, mixed $b): bool +{ + $order = compare($a, $b); - /** - * @template T - * - * @param T $a - * @param T $b - */ - function equal(mixed $a, mixed $b): bool - { - return compare($a, $b) === Order::EQUAL; - } + return $order === Order::EQUAL || $order === Order::GREATER; +} - /** - * @template T - * - * @param T $a - * @param T $b - * - * This function can compare 2 values of a similar type. - * When the type happens to be mixed or never, it will fall back to PHP's internal comparison rules: - * - * @link https://www.php.net/manual/en/language.operators.comparison.php - * @link https://www.php.net/manual/en/types.comparisons.php - */ - function compare(mixed $a, mixed $b): Order - { - if ($a instanceof Comparable) { - return $a->compare($b); - } +/** + * @template T + * + * @param T $a + * @param T $b + */ +function equal(mixed $a, mixed $b): bool +{ + return compare($a, $b) === Order::EQUAL; +} - return Order::from($a <=> $b); +/** + * @template T + * + * @param T $a + * @param T $b + * + * This function can compare 2 values of a similar type. + * When the type happens to be mixed or never, it will fall back to PHP's internal comparison rules: + * + * @link https://www.php.net/manual/en/language.operators.comparison.php + * @link https://www.php.net/manual/en/types.comparisons.php + */ +function compare(mixed $a, mixed $b): Order +{ + if ($a instanceof Comparable) { + return $a->compare($b); } + + return Order::from($a <=> $b); } diff --git a/packages/support/src/Filesystem/LockType.php b/packages/support/src/Filesystem/LockType.php new file mode 100644 index 0000000000..6400a0e66e --- /dev/null +++ b/packages/support/src/Filesystem/LockType.php @@ -0,0 +1,32 @@ + fopen($filename, 'rb')); + + if ($handle === false) { + throw new Exceptions\RuntimeException(sprintf( + 'Failed to open file "%s": %s', + $filename, + $openMessage ?? 'internal error', + )); + } + + if (! flock($handle, $type->value)) { + fclose($handle); + + throw new Exceptions\RuntimeException(sprintf( + 'Failed to acquire lock on file "%s"', + $filename, + )); + } + + [$content, $readMessage] = box(static fn (): false|string => stream_get_contents($handle)); + + flock($handle, LOCK_UN); + fclose($handle); + + if ($content === false) { + throw new Exceptions\RuntimeException(sprintf( + 'Failed to read file "%s": %s', + $filename, + $readMessage ?? 'internal error', + )); + } + + return $content; +} + /** * Ensures that the specified directory exists. */ @@ -163,6 +258,26 @@ function create_directory(string $directory, int $permissions = 0o777): void } } +/** + * Creates and returns a unique empty temporary directory. + * + * @return non-empty-string + */ +function create_temporary_directory(?string $prefix = null): string +{ + $temporaryDirectory = sys_get_temp_dir(); + $uniqueDirectory = $temporaryDirectory . '/' . uniqid(prefix: $prefix ?? ''); + + if ($uniqueDirectory === false) { + throw new Exceptions\RuntimeException('Failed to create a temporary directory.'); + } + + namespace\ensure_directory_exists($uniqueDirectory); + namespace\ensure_directory_empty($uniqueDirectory); + + return $uniqueDirectory; +} + /** * Creates the directory where the $filename is or will be stored. * @@ -220,7 +335,7 @@ function delete(string $path, bool $recursive = true): void return; } - if (namespace\is_file($path)) { + if (namespace\is_file($path) || namespace\is_symbolic_link($path)) { namespace\delete_file($path); } elseif (namespace\is_directory($path)) { namespace\delete_directory($path, $recursive); diff --git a/packages/support/src/Html/functions.php b/packages/support/src/Html/functions.php index 7e2bae244e..1dde0cb19f 100644 --- a/packages/support/src/Html/functions.php +++ b/packages/support/src/Html/functions.php @@ -2,206 +2,206 @@ declare(strict_types=1); -namespace Tempest\Support\Html { - use Stringable; +namespace Tempest\Support\Html; - use function Tempest\Support\arr; +use Stringable; - /** - * Determines whether the specified HTML tag is a void tag. - * @see https://developer.mozilla.org/en-US/docs/Glossary/Void_element - */ - function is_void_tag(Stringable|string $tag): bool - { - return in_array( +use function Tempest\Support\arr; + +/** + * Determines whether the specified HTML tag is a void tag. + * @see https://developer.mozilla.org/en-US/docs/Glossary/Void_element + */ +function is_void_tag(Stringable|string $tag): bool +{ + return in_array( + (string) $tag, + [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', + ], + strict: true, + ); +} + +/** + * Determines whether the specified HTML tag is known HTML tag. + * @see https://developer.mozilla.org/en-US/docs/Glossary/Tag + */ +function is_html_tag(Stringable|string $tag): bool +{ + return ( + is_void_tag($tag) + || in_array( (string) $tag, [ + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', 'area', + 'article', + 'aside', + 'audio', + 'b', 'base', + 'basefont', + 'bdi', + 'bdo', + 'big', + 'blockquote', + 'body', 'br', + 'button', + 'canvas', + 'caption', + 'center', + 'cite', + 'code', 'col', + 'colgroup', + 'data', + 'datalist', + 'dd', + 'del', + 'details', + 'dfn', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'em', 'embed', + 'fieldset', + 'figcaption', + 'figure', + 'font', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hgroup', 'hr', + 'html', + 'i', + 'iframe', 'img', 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', 'link', + 'main', + 'map', + 'mark', + 'menu', 'meta', + 'meter', + 'nav', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'output', + 'p', 'param', + 'picture', + 'pre', + 'progress', + 'q', + 'rp', + 'rt', + 'ruby', + 's', + 'samp', + 'script', + 'search', + 'section', + 'select', + 'small', 'source', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'summary', + 'sup', + 'svg', + 'table', + 'tbody', + 'td', + 'template', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'time', + 'title', + 'tr', 'track', + 'tt', + 'u', + 'ul', + 'var', + 'video', 'wbr', ], strict: true, - ); - } - - /** - * Determines whether the specified HTML tag is known HTML tag. - * @see https://developer.mozilla.org/en-US/docs/Glossary/Tag - */ - function is_html_tag(Stringable|string $tag): bool - { - return ( - is_void_tag($tag) - || in_array( - (string) $tag, - [ - 'a', - 'abbr', - 'acronym', - 'address', - 'applet', - 'area', - 'article', - 'aside', - 'audio', - 'b', - 'base', - 'basefont', - 'bdi', - 'bdo', - 'big', - 'blockquote', - 'body', - 'br', - 'button', - 'canvas', - 'caption', - 'center', - 'cite', - 'code', - 'col', - 'colgroup', - 'data', - 'datalist', - 'dd', - 'del', - 'details', - 'dfn', - 'dialog', - 'dir', - 'div', - 'dl', - 'dt', - 'em', - 'embed', - 'fieldset', - 'figcaption', - 'figure', - 'font', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', - 'header', - 'hgroup', - 'hr', - 'html', - 'i', - 'iframe', - 'img', - 'input', - 'ins', - 'kbd', - 'label', - 'legend', - 'li', - 'link', - 'main', - 'map', - 'mark', - 'menu', - 'meta', - 'meter', - 'nav', - 'noframes', - 'noscript', - 'object', - 'ol', - 'optgroup', - 'option', - 'output', - 'p', - 'param', - 'picture', - 'pre', - 'progress', - 'q', - 'rp', - 'rt', - 'ruby', - 's', - 'samp', - 'script', - 'search', - 'section', - 'select', - 'small', - 'source', - 'span', - 'strike', - 'strong', - 'style', - 'sub', - 'summary', - 'sup', - 'svg', - 'table', - 'tbody', - 'td', - 'template', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'time', - 'title', - 'tr', - 'track', - 'tt', - 'u', - 'ul', - 'var', - 'video', - 'wbr', - ], - strict: true, - ) - ); - } - - function format_attributes(array $attributes = []): string - { - return $attributes = arr($attributes) - ->filter(fn (mixed $value) => ! in_array($value, [false, null], strict: true)) - ->map(fn (mixed $value, int|string $key) => $value === true ? $key : $key . '="' . $value . '"') - ->values() - ->implode(' ') - ->when( - condition: fn ($string) => $string->length() !== 0, - callback: fn ($string) => $string->prepend(' '), - ) - ->toString(); - } + ) + ); +} - /** - * Creates an HTML tag with the specified optional attributes and content. - */ - function create_tag(string $tag, array $attributes = [], ?string $content = null): HtmlString - { - $attributes = namespace\format_attributes($attributes); +function format_attributes(array $attributes = []): string +{ + return $attributes = arr($attributes) + ->filter(fn (mixed $value) => ! in_array($value, [false, null], strict: true)) + ->map(fn (mixed $value, int|string $key) => $value === true ? $key : $key . '="' . $value . '"') + ->values() + ->implode(' ') + ->when( + condition: fn ($string) => $string->length() !== 0, + callback: fn ($string) => $string->prepend(' '), + ) + ->toString(); +} - if ($content || ! is_void_tag($tag)) { - return new HtmlString(sprintf('<%s%s>%s', $tag, $attributes, $content ?? '', $tag)); - } +/** + * Creates an HTML tag with the specified optional attributes and content. + */ +function create_tag(string $tag, array $attributes = [], ?string $content = null): HtmlString +{ + $attributes = namespace\format_attributes($attributes); - return new HtmlString(sprintf('<%s%s />', $tag, $attributes)); + if ($content || ! is_void_tag($tag)) { + return new HtmlString(sprintf('<%s%s>%s', $tag, $attributes, $content ?? '', $tag)); } + + return new HtmlString(sprintf('<%s%s />', $tag, $attributes)); } diff --git a/packages/support/src/Math/functions.php b/packages/support/src/Math/functions.php index 51420afe99..722bf80528 100644 --- a/packages/support/src/Math/functions.php +++ b/packages/support/src/Math/functions.php @@ -1,578 +1,578 @@ $fromBase - * @param int<2, 36> $toBase - * - * @throws Exception\InvalidArgumentException If the given value is invalid. - */ - function base_convert(string $value, int $fromBase, int $toBase): string - { - $fromAlphabet = mb_substr(Str\ALPHABET_ALPHANUMERIC, 0, $fromBase); - $resultDecimal = '0'; - $placeValue = bcpow((string) $fromBase, (string) (strlen($value) - 1)); - - foreach (str_split($value) as $digit) { - $digitNumeric = stripos($fromAlphabet, $digit); - - if (false === $digitNumeric) { - throw new Exception\InvalidArgumentException(sprintf('Invalid digit %s in base %d', $digit, $fromBase)); - } - - $resultDecimal = bcadd($resultDecimal, bcmul((string) $digitNumeric, $placeValue)); - $placeValue = bcdiv($placeValue, (string) $fromBase); - } +/** + * Returns the arc tangent of the given coordinates. + */ +function atan2(float $y, float $x): float +{ + return php_atan2($y, $x); +} - if (10 === $toBase) { - return $resultDecimal; +/** + * Converts the given string in base `$from_base` to base `$to_base`, assuming + * letters a-z are used for digits for bases greater than 10. The conversion is + * done to arbitrary precision. + * + * @param non-empty-string $value + * @param int<2, 36> $fromBase + * @param int<2, 36> $toBase + * + * @throws Exception\InvalidArgumentException If the given value is invalid. + */ +function base_convert(string $value, int $fromBase, int $toBase): string +{ + $fromAlphabet = mb_substr(Str\ALPHABET_ALPHANUMERIC, 0, $fromBase); + $resultDecimal = '0'; + $placeValue = bcpow((string) $fromBase, (string) (strlen($value) - 1)); + + foreach (str_split($value) as $digit) { + $digitNumeric = stripos($fromAlphabet, $digit); + + if (false === $digitNumeric) { + throw new Exception\InvalidArgumentException(sprintf('Invalid digit %s in base %d', $digit, $fromBase)); } - $toAlphabet = mb_substr(Str\ALPHABET_ALPHANUMERIC, 0, $toBase); - $result = ''; - - do { - $result = $toAlphabet[(int) bcmod($resultDecimal, (string) $toBase)] . $result; - $resultDecimal = bcdiv($resultDecimal, (string) $toBase); - } while (bccomp($resultDecimal, '0') > 0); - - return $result; + $resultDecimal = bcadd($resultDecimal, bcmul((string) $digitNumeric, $placeValue)); + $placeValue = bcdiv($placeValue, (string) $fromBase); } - /** - * Return the smallest integer value greater than or equal to the given number. - */ - function ceil(float $number): float - { - return php_ceil($number); + if (10 === $toBase) { + return $resultDecimal; } - /** - * Returns the given number clamped to the given range. - * - * @template T of float|int - * - * @param T $number - * @param T $min - * @param T $max - * - * @throws Exception\InvalidArgumentException If $min is bigger than $max - * - * @return T - */ - function clamp(int|float $number, int|float $min, int|float $max): int|float - { - if ($max < $min) { - throw new Exception\InvalidArgumentException('Expected $min to be lower or equal to $max.'); - } + $toAlphabet = mb_substr(Str\ALPHABET_ALPHANUMERIC, 0, $toBase); + $result = ''; - if ($number < $min) { - return $min; - } + do { + $result = $toAlphabet[(int) bcmod($resultDecimal, (string) $toBase)] . $result; + $resultDecimal = bcdiv($resultDecimal, (string) $toBase); + } while (bccomp($resultDecimal, '0') > 0); - if ($number > $max) { - return $max; - } + return $result; +} - return $number; - } +/** + * Return the smallest integer value greater than or equal to the given number. + */ +function ceil(float $number): float +{ + return php_ceil($number); +} - /** - * Return the cosine of the given number. - */ - function cos(float $number): float - { - return php_cos($number); +/** + * Returns the given number clamped to the given range. + * + * @template T of float|int + * + * @param T $number + * @param T $min + * @param T $max + * + * @throws Exception\InvalidArgumentException If $min is bigger than $max + * + * @return T + */ +function clamp(int|float $number, int|float $min, int|float $max): int|float +{ + if ($max < $min) { + throw new Exception\InvalidArgumentException('Expected $min to be lower or equal to $max.'); } - /** - * Returns the result of integer division of the given numerator by the given denominator. - * - * @throws Exception\ArithmeticException If the $numerator is Math\INT64_MIN and the $denominator is -1. - * @throws Exception\DivisionByZeroException If the $denominator is 0. - */ - function div(int $numerator, int $denominator): int - { - try { - return intdiv($numerator, $denominator); - } catch (DivisionByZeroError $error) { - throw new Exception\DivisionByZeroException(sprintf('%s.', $error->getMessage()), $error->getCode(), $error); - } catch (ArithmeticError $error) { - throw new Exception\ArithmeticException( - 'Division of Math\INT64_MIN by -1 is not an integer.', - $error->getCode(), - $error, - ); - } + if ($number < $min) { + return $min; } - /** - * Returns the exponential of the given number. - */ - function exp(float $number): float - { - return php_exp($number); + if ($number > $max) { + return $max; } - /** - * Return the largest integer value less then or equal to the given number. - */ - function floor(float $number): float - { - return php_floor($number); - } + return $number; +} - /** - * Converts the given string in base `$from_base` to an integer, assuming letters a-z - * are used for digits when `$from_base` > 10. - * - * @param non-empty-string $number - * @param int<2, 36> $fromBase - * - * @throws Exception\InvalidArgumentException If $number contains an invalid digit in base $from_base - * @throws Exception\OverflowException In case of an integer overflow - */ - function from_base(string $number, int $fromBase): int - { - $limit = div(INT64_MAX, $fromBase); - $result = 0; - - foreach (str_split($number) as $digit) { - $oval = ord($digit); - - // Branches sorted by guesstimated frequency of use. */ - if (/* '0' - '9' */ $oval <= 57 && $oval >= 48) { - $dval = $oval - 48; - } elseif (/* 'a' - 'z' */ $oval >= 97 && $oval <= 122) { - $dval = $oval - 87; - } elseif (/* 'A' - 'Z' */ $oval >= 65 && $oval <= 90) { - $dval = $oval - 55; - } else { - $dval = 99; - } - - if ($fromBase < $dval) { - throw new Exception\InvalidArgumentException(sprintf('Invalid digit %s in base %d', $digit, $fromBase)); - } - - $oldval = $result; - $result = ($fromBase * $result) + $dval; - if ($oldval > $limit || $oldval > $result) { - throw new Exception\OverflowException(sprintf('Unexpected integer overflow parsing %s from base %d', $number, $fromBase)); - } - } +/** + * Return the cosine of the given number. + */ +function cos(float $number): float +{ + return php_cos($number); +} - return $result; +/** + * Returns the result of integer division of the given numerator by the given denominator. + * + * @throws Exception\ArithmeticException If the $numerator is Math\INT64_MIN and the $denominator is -1. + * @throws Exception\DivisionByZeroException If the $denominator is 0. + */ +function div(int $numerator, int $denominator): int +{ + try { + return intdiv($numerator, $denominator); + } catch (DivisionByZeroError $error) { + throw new Exception\DivisionByZeroException(sprintf('%s.', $error->getMessage()), $error->getCode(), $error); + } catch (ArithmeticError $error) { + throw new Exception\ArithmeticException( + 'Division of Math\INT64_MIN by -1 is not an integer.', + $error->getCode(), + $error, + ); } +} - /** - * Converts the given non-negative number into the given base, using letters a-z - * for digits when then given base is > 10. - * - * @param int<0, max> $number - * @param int<2, 36> $base - * - * @return non-empty-string - */ - function to_base(int $number, int $base): string - { - $result = ''; - - do { - $quotient = div($number, $base); - $result = Str\ALPHABET_ALPHANUMERIC[$number - ($quotient * $base)] . $result; - $number = $quotient; - } while (0 !== $number); - - return $result; - } +/** + * Returns the exponential of the given number. + */ +function exp(float $number): float +{ + return php_exp($number); +} - /** - * Returns the logarithm of the given number. - * - * @throws Exception\InvalidArgumentException If $number or $base are negative, or $base is equal to 1.0. - */ - function log(float $number, ?float $base = null): float - { - if ($number <= 0) { - throw new Exception\InvalidArgumentException('$number must be positive.'); - } +/** + * Return the largest integer value less then or equal to the given number. + */ +function floor(float $number): float +{ + return php_floor($number); +} - if (null === $base) { - return php_log($number); +/** + * Converts the given string in base `$from_base` to an integer, assuming letters a-z + * are used for digits when `$from_base` > 10. + * + * @param non-empty-string $number + * @param int<2, 36> $fromBase + * + * @throws Exception\InvalidArgumentException If $number contains an invalid digit in base $from_base + * @throws Exception\OverflowException In case of an integer overflow + */ +function from_base(string $number, int $fromBase): int +{ + $limit = div(INT64_MAX, $fromBase); + $result = 0; + + foreach (str_split($number) as $digit) { + $oval = ord($digit); + + // Branches sorted by guesstimated frequency of use. */ + if (/* '0' - '9' */ $oval <= 57 && $oval >= 48) { + $dval = $oval - 48; + } elseif (/* 'a' - 'z' */ $oval >= 97 && $oval <= 122) { + $dval = $oval - 87; + } elseif (/* 'A' - 'Z' */ $oval >= 65 && $oval <= 90) { + $dval = $oval - 55; + } else { + $dval = 99; } - if ($base <= 0) { - throw new Exception\InvalidArgumentException('$base must be positive.'); + if ($fromBase < $dval) { + throw new Exception\InvalidArgumentException(sprintf('Invalid digit %s in base %d', $digit, $fromBase)); } - if ($base === 1.0) { - throw new Exception\InvalidArgumentException('Logarithm undefined for $base of 1.0.'); + $oldval = $result; + $result = ($fromBase * $result) + $dval; + if ($oldval > $limit || $oldval > $result) { + throw new Exception\OverflowException(sprintf('Unexpected integer overflow parsing %s from base %d', $number, $fromBase)); } - - return php_log($number, $base); } - /** - * Returns the largest element of the given iterable, or null if the - * iterable is empty. - * - * The value for comparison is determined by the given function. - * - * In the case of duplicate values, later values overwrite previous ones. - * - * @template T - * - * @param iterable $numbers - * @param (Closure(T): numeric) $numericFunction - * - * @return T|null - */ - function max_by(iterable $numbers, Closure $numericFunction): mixed - { - $max = null; - $maxNum = null; - - foreach ($numbers as $value) { - $valueNum = $numericFunction($value); - if (null === $maxNum || $valueNum >= $maxNum) { - $max = $value; - $maxNum = $valueNum; - } - } - - return $max; - } + return $result; +} - /** - * Returns the largest element of the given list, or null if the array is empty. - * - * @template T of int|float - * - * @param array $numbers - * - * @return ($numbers is non-empty-list ? T : null) - */ - function max(array $numbers): null|int|float - { - $max = null; - - foreach ($numbers as $number) { - if (null === $max || $number > $max) { - $max = $number; - } - } +/** + * Converts the given non-negative number into the given base, using letters a-z + * for digits when then given base is > 10. + * + * @param int<0, max> $number + * @param int<2, 36> $base + * + * @return non-empty-string + */ +function to_base(int $number, int $base): string +{ + $result = ''; + + do { + $quotient = div($number, $base); + $result = Str\ALPHABET_ALPHANUMERIC[$number - ($quotient * $base)] . $result; + $number = $quotient; + } while (0 !== $number); + + return $result; +} - return $max; +/** + * Returns the logarithm of the given number. + * + * @throws Exception\InvalidArgumentException If $number or $base are negative, or $base is equal to 1.0. + */ +function log(float $number, ?float $base = null): float +{ + if ($number <= 0) { + throw new Exception\InvalidArgumentException('$number must be positive.'); } - /** - * Returns the largest number of all the given numbers. - * - * @template T of int|float - * - * @param T $first - * @param T $second - * @param T ...$rest - * - * @return T - */ - function maxva(int|float $first, int|float $second, int|float ...$rest): int|float - { - $max = \max($first, $second); - - foreach ($rest as $number) { - if ($number > $max) { - $max = $number; - } - } + if (null === $base) { + return php_log($number); + } - return $max; + if ($base <= 0) { + throw new Exception\InvalidArgumentException('$base must be positive.'); } - /** - * Returns the arithmetic mean of the given numbers in the list. - * - * Return null if the given list is empty. - * - * @param array $numbers - * - * @return ($numbers is non-empty-list ? float : null) - */ - function mean(array $numbers): ?float - { - $count = (float) count($numbers); - - if (0.0 === $count) { - return null; - } + if ($base === 1.0) { + throw new Exception\InvalidArgumentException('Logarithm undefined for $base of 1.0.'); + } - $mean = 0.0; + return php_log($number, $base); +} - foreach ($numbers as $number) { - $mean += (float) $number / $count; +/** + * Returns the largest element of the given iterable, or null if the + * iterable is empty. + * + * The value for comparison is determined by the given function. + * + * In the case of duplicate values, later values overwrite previous ones. + * + * @template T + * + * @param iterable $numbers + * @param (Closure(T): numeric) $numericFunction + * + * @return T|null + */ +function max_by(iterable $numbers, Closure $numericFunction): mixed +{ + $max = null; + $maxNum = null; + + foreach ($numbers as $value) { + $valueNum = $numericFunction($value); + if (null === $maxNum || $valueNum >= $maxNum) { + $max = $value; + $maxNum = $valueNum; } - - return $mean; } - /** - * Returns the median of the given numbers in the list. - * - * Returns null if the given iterable is empty. - * - * @param array $numbers - * - * @return ($numbers is non-empty-list ? float : null) - */ - function median(array $numbers): ?float - { - sort($numbers); - $count = count($numbers); - - if (0 === $count) { - return null; + return $max; +} + +/** + * Returns the largest element of the given list, or null if the array is empty. + * + * @template T of int|float + * + * @param array $numbers + * + * @return ($numbers is non-empty-list ? T : null) + */ +function max(array $numbers): null|int|float +{ + $max = null; + + foreach ($numbers as $number) { + if (null === $max || $number > $max) { + $max = $number; } + } - $middleIndex = div($count, 2); + return $max; +} - if (0 === ($count % 2)) { - return mean([$numbers[$middleIndex], $numbers[$middleIndex - 1]]); +/** + * Returns the largest number of all the given numbers. + * + * @template T of int|float + * + * @param T $first + * @param T $second + * @param T ...$rest + * + * @return T + */ +function maxva(int|float $first, int|float $second, int|float ...$rest): int|float +{ + $max = \max($first, $second); + + foreach ($rest as $number) { + if ($number > $max) { + $max = $number; } + } - return (float) $numbers[$middleIndex]; + return $max; +} + +/** + * Returns the arithmetic mean of the given numbers in the list. + * + * Return null if the given list is empty. + * + * @param array $numbers + * + * @return ($numbers is non-empty-list ? float : null) + */ +function mean(array $numbers): ?float +{ + $count = (float) count($numbers); + + if (0.0 === $count) { + return null; } - /** - * Returns the smallest element of the given iterable, or null if the - * iterable is empty. - * - * The value for comparison is determined by the given function. - * - * In the case of duplicate values, later values overwrite previous ones. - * - * @template T - * - * @param iterable $numbers - * @param (Closure(T): numeric) $numericFunction - * - * @return T|null - */ - function min_by(iterable $numbers, Closure $numericFunction): mixed - { - $min = null; - $minNum = null; - - foreach ($numbers as $value) { - $valueNum = $numericFunction($value); - - if (null === $minNum || $valueNum <= $minNum) { - $min = $value; - $minNum = $valueNum; - } - } + $mean = 0.0; - return $min; + foreach ($numbers as $number) { + $mean += (float) $number / $count; } - /** - * Returns the smallest element of the given list, or null if the - * list is empty. - * - * @template T of int|float - * - * @param array $numbers - * - * @return ($numbers is non-empty-list ? T : null) - */ - function min(array $numbers): null|float|int - { - $min = null; - - foreach ($numbers as $number) { - if (null === $min || $number < $min) { - $min = $number; - } - } + return $mean; +} - return $min; +/** + * Returns the median of the given numbers in the list. + * + * Returns null if the given iterable is empty. + * + * @param array $numbers + * + * @return ($numbers is non-empty-list ? float : null) + */ +function median(array $numbers): ?float +{ + sort($numbers); + $count = count($numbers); + + if (0 === $count) { + return null; } - /** - * Returns the smallest number of all the given numbers. - * - * @template T of int|float - * - * @param T $first - * @param T $second - * @param T ...$rest - * - * @return T - */ - function minva(int|float $first, int|float $second, int|float ...$rest): int|float - { - $min = \min($first, $second); - - foreach ($rest as $number) { - if ($number < $min) { - $min = $number; - } - } + $middleIndex = div($count, 2); - return $min; + if (0 === ($count % 2)) { + return mean([$numbers[$middleIndex], $numbers[$middleIndex - 1]]); } - /** - * Returns the given number rounded to the specified precision. - * - * A positive precision rounds to the nearest decimal place whereas a negative precision - * rounds to the nearest power of ten. - * - * For example, a precision of 1 rounds to the nearest tenth whereas a precision of -1 rounds to the nearst nearest. - */ - function round(float $number, int $precision = 0): float - { - return php_round($number, $precision); + return (float) $numbers[$middleIndex]; +} + +/** + * Returns the smallest element of the given iterable, or null if the + * iterable is empty. + * + * The value for comparison is determined by the given function. + * + * In the case of duplicate values, later values overwrite previous ones. + * + * @template T + * + * @param iterable $numbers + * @param (Closure(T): numeric) $numericFunction + * + * @return T|null + */ +function min_by(iterable $numbers, Closure $numericFunction): mixed +{ + $min = null; + $minNum = null; + + foreach ($numbers as $value) { + $valueNum = $numericFunction($value); + + if (null === $minNum || $valueNum <= $minNum) { + $min = $value; + $minNum = $valueNum; + } } - /** - * Returns the sine of the given number. - */ - function sin(float $number): float - { - return php_sin($number); + return $min; +} + +/** + * Returns the smallest element of the given list, or null if the + * list is empty. + * + * @template T of int|float + * + * @param array $numbers + * + * @return ($numbers is non-empty-list ? T : null) + */ +function min(array $numbers): null|float|int +{ + $min = null; + + foreach ($numbers as $number) { + if (null === $min || $number < $min) { + $min = $number; + } } - /** - * Returns the sum of all the given numbers. - * - * @param array $numbers - */ - function sum_floats(array $numbers): float - { - $result = 0.0; - - foreach ($numbers as $number) { - $result += (float) $number; + return $min; +} + +/** + * Returns the smallest number of all the given numbers. + * + * @template T of int|float + * + * @param T $first + * @param T $second + * @param T ...$rest + * + * @return T + */ +function minva(int|float $first, int|float $second, int|float ...$rest): int|float +{ + $min = \min($first, $second); + + foreach ($rest as $number) { + if ($number < $min) { + $min = $number; } + } + + return $min; +} + +/** + * Returns the given number rounded to the specified precision. + * + * A positive precision rounds to the nearest decimal place whereas a negative precision + * rounds to the nearest power of ten. + * + * For example, a precision of 1 rounds to the nearest tenth whereas a precision of -1 rounds to the nearst nearest. + */ +function round(float $number, int $precision = 0): float +{ + return php_round($number, $precision); +} - return $result; +/** + * Returns the sine of the given number. + */ +function sin(float $number): float +{ + return php_sin($number); +} + +/** + * Returns the sum of all the given numbers. + * + * @param array $numbers + */ +function sum_floats(array $numbers): float +{ + $result = 0.0; + + foreach ($numbers as $number) { + $result += (float) $number; } - /** - * Returns the sum of all the given numbers. - * - * @param array $numbers - */ - function sum(array $numbers): int - { - $result = 0; - - foreach ($numbers as $number) { - $result += $number; - } + return $result; +} - return $result; +/** + * Returns the sum of all the given numbers. + * + * @param array $numbers + */ +function sum(array $numbers): int +{ + $result = 0; + + foreach ($numbers as $number) { + $result += $number; } + + return $result; } diff --git a/packages/support/src/Memoization/HasMemoization.php b/packages/support/src/Memoization/HasMemoization.php new file mode 100644 index 0000000000..cca26ef698 --- /dev/null +++ b/packages/support/src/Memoization/HasMemoization.php @@ -0,0 +1,19 @@ +memoize)) { + $this->memoize[$key] = $closure(); + } + + return $this->memoize[$key]; + } +} diff --git a/packages/support/src/Path/functions.php b/packages/support/src/Path/functions.php index ae8283fc79..f5035c494f 100644 --- a/packages/support/src/Path/functions.php +++ b/packages/support/src/Path/functions.php @@ -2,155 +2,155 @@ declare(strict_types=1); -namespace Tempest\Support\Path { - use Stringable; - - use function Tempest\Support\Regex\matches; - use function Tempest\Support\Str\ends_with; - use function Tempest\Support\Str\starts_with; - - /** - * Determines whether the given path is a relative path. The path is not checked against the filesystem. - */ - function is_relative_path(null|Stringable|string ...$parts): bool - { - return ! namespace\is_absolute_path(...$parts); - } +namespace Tempest\Support\Path; - /** - * Converts the given absolute path to a path relative to `$from`. - * If the given path is not an absolute path, it is assumed to already by relative to `$from` and will be returned as-is. - */ - function to_relative_path(null|Stringable|string $from, Stringable|string ...$parts): string - { - $path = namespace\normalize(...$parts); - $from = $from === null ? '' : (string) $from; - - if (is_relative_path($path)) { - return $path; - } +use Stringable; - $from = rtrim($from, '/'); - $path = rtrim($path, '/'); +use function Tempest\Support\Regex\matches; +use function Tempest\Support\Str\ends_with; +use function Tempest\Support\Str\starts_with; - $fromParts = explode('/', $from); - $pathParts = explode('/', $path); +/** + * Determines whether the given path is a relative path. The path is not checked against the filesystem. + */ +function is_relative_path(null|Stringable|string ...$parts): bool +{ + return ! namespace\is_absolute_path(...$parts); +} - while ($fromParts !== [] && $pathParts !== [] && $fromParts[0] === $pathParts[0]) { - array_shift($fromParts); - array_shift($pathParts); - } +/** + * Converts the given absolute path to a path relative to `$from`. + * If the given path is not an absolute path, it is assumed to already by relative to `$from` and will be returned as-is. + */ +function to_relative_path(null|Stringable|string $from, Stringable|string ...$parts): string +{ + $path = namespace\normalize(...$parts); + $from = $from === null ? '' : (string) $from; - $upDirs = count($fromParts); - $relativePath = str_repeat('../', $upDirs) . implode('/', $pathParts); + if (is_relative_path($path)) { + return $path; + } + + $from = rtrim($from, '/'); + $path = rtrim($path, '/'); - return $relativePath === '' ? '.' : $relativePath; + $fromParts = explode('/', $from); + $pathParts = explode('/', $path); + + while ($fromParts !== [] && $pathParts !== [] && $fromParts[0] === $pathParts[0]) { + array_shift($fromParts); + array_shift($pathParts); } - /** - * Determines whether the given path is an absolute path. The path is not checked against the filesystem. - */ - function is_absolute_path(null|Stringable|string ...$parts): bool - { - $path = namespace\normalize(...$parts); + $upDirs = count($fromParts); + $relativePath = str_repeat('../', $upDirs) . implode('/', $pathParts); - if (strlen($path) === 0 || '.' === $path[0]) { - return false; - } + return $relativePath === '' ? '.' : $relativePath; +} - if (preg_match('#^[a-zA-Z]:/#', $path)) { - return true; - } +/** + * Determines whether the given path is an absolute path. The path is not checked against the filesystem. + */ +function is_absolute_path(null|Stringable|string ...$parts): bool +{ + $path = namespace\normalize(...$parts); - return '/' === $path[0]; + if (strlen($path) === 0 || '.' === $path[0]) { + return false; } - /** - * Converts the given path to an absolute path. - */ - function to_absolute_path(Stringable|string $cwd, null|Stringable|string ...$parts): string - { - $cwd = namespace\normalize($cwd); - $path = namespace\normalize(...$parts); + if (preg_match('#^[a-zA-Z]:/#', $path)) { + return true; + } - if (starts_with($path, $cwd) && namespace\is_absolute_path($path)) { - return $path; - } + return '/' === $path[0]; +} - $segments = explode('/', namespace\normalize($cwd, $path)); - $resolved = []; +/** + * Converts the given path to an absolute path. + */ +function to_absolute_path(Stringable|string $cwd, null|Stringable|string ...$parts): string +{ + $cwd = namespace\normalize($cwd); + $path = namespace\normalize(...$parts); - foreach ($segments as $part) { - if ($part === '') { - continue; - } + if (starts_with($path, $cwd) && namespace\is_absolute_path($path)) { + return $path; + } - if ($part === '.') { - continue; - } + $segments = explode('/', namespace\normalize($cwd, $path)); + $resolved = []; - if ($part === '..') { - if ($resolved !== []) { - array_pop($resolved); - } - } else { - $resolved[] = $part; - } + foreach ($segments as $part) { + if ($part === '') { + continue; } - $absolutePath = namespace\normalize(...$resolved); + if ($part === '.') { + continue; + } - if (matches($cwd, '#^[a-zA-Z]:/#')) { - return $absolutePath; + if ($part === '..') { + if ($resolved !== []) { + array_pop($resolved); + } + } else { + $resolved[] = $part; } + } + + $absolutePath = namespace\normalize(...$resolved); - return '/' . $absolutePath; + if (matches($cwd, '#^[a-zA-Z]:/#')) { + return $absolutePath; } - /** - * Normalizes the given path to use forward-slashes. - */ - function normalize(null|Stringable|string ...$paths): string - { - if ($paths === []) { - return ''; - } + return '/' . $absolutePath; +} - $paths = array_map( - fn (null|Stringable|string $path) => $path === null ? '' : (string) $path, - $paths, - ); +/** + * Normalizes the given path to use forward-slashes. + */ +function normalize(null|Stringable|string ...$paths): string +{ + if ($paths === []) { + return ''; + } - // Split paths items on forward and backward slashes - $parts = array_reduce($paths, fn (array $carry, string $part) => [...$carry, ...explode('/', $part)], []); - $parts = array_reduce($parts, fn (array $carry, string $part) => [...$carry, ...explode('\\', $part)], []); + $paths = array_map( + fn (null|Stringable|string $path) => $path === null ? '' : (string) $path, + $paths, + ); - // Trim forward and backward slashes - $parts = array_map(fn (string $part) => trim($part, '/\\'), $parts); - $parts = array_filter($parts); + // Split paths items on forward and backward slashes + $parts = array_reduce($paths, fn (array $carry, string $part) => [...$carry, ...explode('/', $part)], []); + $parts = array_reduce($parts, fn (array $carry, string $part) => [...$carry, ...explode('\\', $part)], []); - // Glue parts together - $path = implode('/', $parts); + // Trim forward and backward slashes + $parts = array_map(fn (string $part) => trim($part, '/\\'), $parts); + $parts = array_filter($parts, fn (string $part) => $part !== ''); - // Add / if first entry starts with forward- or backward slash - $firstEntry = $paths[0]; + // Glue parts together + $path = implode('/', $parts); - if (starts_with($firstEntry, ['/', '\\'])) { - $path = '/' . $path; - } + // Add / if first entry starts with forward- or backward slash + $firstEntry = $paths[0]; - // Add / if last entry ends with forward- or backward slash - $lastEntry = $paths[count($paths) - 1]; + if (starts_with($firstEntry, ['/', '\\'])) { + $path = '/' . $path; + } - if ((count($paths) > 1 || strlen($lastEntry) > 1) && ends_with($lastEntry, ['/', '\\'])) { - $path .= '/'; - } + // Add / if last entry ends with forward- or backward slash + $lastEntry = $paths[count($paths) - 1]; - // Restore virtual phar prefix - if (str_starts_with($path, 'phar:')) { - $path = str_replace('phar:', 'phar://', $path); - } + if ((count($paths) > 1 || strlen($lastEntry) > 1) && ends_with($lastEntry, ['/', '\\'])) { + $path .= '/'; + } - return $path; + // Restore virtual phar prefix + if (str_starts_with($path, 'phar:')) { + $path = str_replace('phar:', 'phar://', $path); } + + return $path; } diff --git a/packages/support/src/Regex/functions.php b/packages/support/src/Regex/functions.php index ab24e7a999..21591270d3 100644 --- a/packages/support/src/Regex/functions.php +++ b/packages/support/src/Regex/functions.php @@ -2,136 +2,129 @@ declare(strict_types=1); -namespace Tempest\Support\Regex { - use Closure; - use RuntimeException; - use Stringable; - - use function Tempest\Support\arr; - use function Tempest\Support\Arr\filter; - use function Tempest\Support\Arr\first; - use function Tempest\Support\Arr\get_by_key; - use function Tempest\Support\Arr\wrap; - use function Tempest\Support\Str\starts_with; - use function Tempest\Support\Str\strip_end; - use function Tempest\Support\Str\strip_start; - - /** - * Returns portions of the `$subject` that match the given `$pattern`. If `$global` is set to `true`, returns all matches. Otherwise, only returns the first one. - * - * @param non-empty-string $pattern The pattern to match against. - * @param 0|2|256|512|768 $flags - */ - function get_matches(Stringable|string $subject, Stringable|string $pattern, bool $global = false, int $flags = 0, int $offset = 0): array - { - if (str_ends_with($pattern, 'g')) { - $global = true; - $pattern = strip_end($pattern, 'g'); - } - - return call_preg($global ? 'preg_match_all' : 'preg_match', static function () use ($subject, $pattern, $global, $flags, $offset): array { - $matches = []; - $result = match ($global) { - true => preg_match_all( - (string) $pattern, - (string) $subject, - $matches, - $flags, - $offset, - ), - false => preg_match( - (string) $pattern, - (string) $subject, - $matches, - $flags, - $offset, - ), - }; - - if ($result === false || $result === 0) { - return []; - } - - return $matches; - }); - } - - /** - * Returns the specified matches of `$pattern` in `$subject`. - * - * @param non-empty-string $pattern The pattern to match against. - */ - function get_all_matches( - Stringable|string $subject, - Stringable|string $pattern, - Stringable|string|int|array $matches = 0, - int $offset = 0, - ): array { - $result = get_matches($subject, $pattern, true, PREG_SET_ORDER, $offset); - - return arr($result) - ->map(fn (array $result) => filter($result, fn ($_, string|int $key) => in_array($key, wrap($matches), strict: false))) - ->toArray(); +namespace Tempest\Support\Regex; + +use Closure; +use RuntimeException; +use Stringable; + +use function Tempest\Support\arr; +use function Tempest\Support\Arr\filter; +use function Tempest\Support\Arr\first; +use function Tempest\Support\Arr\get_by_key; +use function Tempest\Support\Arr\wrap; +use function Tempest\Support\Str\starts_with; +use function Tempest\Support\Str\strip_end; +use function Tempest\Support\Str\strip_start; + +/** + * Returns portions of the `$subject` that match the given `$pattern`. If `$global` is set to `true`, returns all matches. Otherwise, only returns the first one. + * + * @param non-empty-string $pattern The pattern to match against. + * @param 0|2|256|512|768 $flags + */ +function get_matches(Stringable|string $subject, Stringable|string $pattern, bool $global = false, int $flags = 0, int $offset = 0): array +{ + if (str_ends_with($pattern, 'g')) { + $global = true; + $pattern = strip_end($pattern, 'g'); } - /** - * Returns the specified match of `$pattern` in `$subject`. If no match is specified, returns the whole matching array. - * - * @param non-empty-string $pattern The pattern to match against. - * @param 0|256|512|768 $flags - */ - function get_match( - Stringable|string $subject, - Stringable|string $pattern, - null|array|Stringable|int|string $match = null, - mixed $default = null, - int $flags = 0, - int $offset = 0, - ): null|int|string|array { - $result = get_matches($subject, $pattern, false, $flags, $offset); - - if ($match === null) { - return $result; + return call_preg($global ? 'preg_match_all' : 'preg_match', static function () use ($subject, $pattern, $global, $flags, $offset): array { + $matches = []; + $result = match ($global) { + true => preg_match_all( + (string) $pattern, + (string) $subject, + $matches, + $flags, + $offset, + ), + false => preg_match( + (string) $pattern, + (string) $subject, + $matches, + $flags, + $offset, + ), + }; + + if ($result === false || $result === 0) { + return []; } - if (is_array($match)) { - return arr($result) - ->filter(fn ($_, string|int $key) => in_array($key, $match, strict: false)) - ->mapWithKeys(fn (array $matches, string|int $key) => yield $key => first($matches)) - ->toArray(); - } + return $matches; + }); +} + +/** + * Returns the specified matches of `$pattern` in `$subject`. + * + * @param non-empty-string $pattern The pattern to match against. + */ +function get_all_matches( + Stringable|string $subject, + Stringable|string $pattern, + Stringable|string|int|array $matches = 0, + int $offset = 0, +): array { + $result = get_matches($subject, $pattern, true, PREG_SET_ORDER, $offset); + + return arr($result) + ->map(fn (array $result) => filter($result, fn ($_, string|int $key) => in_array($key, wrap($matches), strict: false))) + ->toArray(); +} - return get_by_key($result, $match, $default); +/** + * Returns the specified match of `$pattern` in `$subject`. If no match is specified, returns the whole matching array. + * + * @param non-empty-string $pattern The pattern to match against. + * @param 0|256|512|768 $flags + */ +function get_match( + Stringable|string $subject, + Stringable|string $pattern, + null|array|Stringable|int|string $match = null, + mixed $default = null, + int $flags = 0, + int $offset = 0, +): null|int|string|array { + $result = get_matches($subject, $pattern, false, $flags, $offset); + + if ($match === null) { + return $result; } - /** - * Determines if $subject matches the given $pattern. - * - * @param non-empty-string $pattern The pattern to match against. - */ - function matches(string $subject, string $pattern, int $offset = 0): bool - { - return call_preg('preg_match', static fn (): int|false => preg_match($pattern, $subject, offset: $offset)) === 1; + if (is_array($match)) { + return arr($result) + ->filter(fn ($_, string|int $key) => in_array($key, $match, strict: false)) + ->mapWithKeys(fn (array $matches, string|int $key) => yield $key => first($matches)) + ->toArray(); } - /** - * Returns the '$haystack' string with all occurrences of `$pattern` replaced by `$replacement`. - * - * @param non-empty-string $pattern The pattern to search for. - * @param null|positive-int $limit The maximum possible replacements for $pattern within $haystack. - */ - function replace(array|string $haystack, array|string $pattern, Closure|array|string $replacement, ?int $limit = null): string - { - if ($replacement instanceof Closure) { - return (string) call_preg('preg_replace_callback', static fn (): ?string => preg_replace_callback( - $pattern, - $replacement, - $haystack, - $limit ?? -1, - )); - } + return get_by_key($result, $match, $default); +} - return (string) call_preg('preg_replace', static fn (): ?string => preg_replace( +/** + * Determines if $subject matches the given $pattern. + * + * @param non-empty-string $pattern The pattern to match against. + */ +function matches(string $subject, string $pattern, int $offset = 0): bool +{ + return call_preg('preg_match', static fn (): int|false => preg_match($pattern, $subject, offset: $offset)) === 1; +} + +/** + * Returns the '$haystack' string with all occurrences of `$pattern` replaced by `$replacement`. + * + * @param non-empty-string $pattern The pattern to search for. + * @param null|positive-int $limit The maximum possible replacements for $pattern within $haystack. + */ +function replace(array|string $haystack, array|string $pattern, Closure|array|string $replacement, ?int $limit = null): string +{ + if ($replacement instanceof Closure) { + return (string) call_preg('preg_replace_callback', static fn (): ?string => preg_replace_callback( $pattern, $replacement, $haystack, @@ -139,76 +132,83 @@ function replace(array|string $haystack, array|string $pattern, Closure|array|st )); } - /** - * Returns the '$haystack' string with all occurrences of the keys of - * '$replacements' (patterns) replaced by the corresponding values. - * - * @param array $replacements An array where the keys are regular expression patterns, and the values are the replacements. - * @param null|positive-int $limit The maximum possible replacements for each pattern in $haystack. - */ - function replace_every(string $haystack, array $replacements, ?int $limit = null): string - { - return (string) call_preg('preg_replace', static fn (): ?string => preg_replace( - array_keys($replacements), - array_values($replacements), - $haystack, - $limit ?? -1, - )); - } + return (string) call_preg('preg_replace', static fn (): ?string => preg_replace( + $pattern, + $replacement, + $haystack, + $limit ?? -1, + )); +} - /** - * @return null|array{message: string, code: int, pattern_message: null|string} - * @internal - */ - function get_preg_error(string $function): ?array - { - $code = preg_last_error(); - if ($code === PREG_NO_ERROR) { - return null; - } +/** + * Returns the '$haystack' string with all occurrences of the keys of + * '$replacements' (patterns) replaced by the corresponding values. + * + * @param array $replacements An array where the keys are regular expression patterns, and the values are the replacements. + * @param null|positive-int $limit The maximum possible replacements for each pattern in $haystack. + */ +function replace_every(string $haystack, array $replacements, ?int $limit = null): string +{ + return (string) call_preg('preg_replace', static fn (): ?string => preg_replace( + array_keys($replacements), + array_values($replacements), + $haystack, + $limit ?? -1, + )); +} - $messages = [ - PREG_INTERNAL_ERROR => 'Internal error', - PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 characters, possibly incorrectly encoded', - PREG_BAD_UTF8_OFFSET_ERROR => 'The offset did not correspond to the beginning of a valid UTF-8 code point', - PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted', - PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted', - PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted', - ]; - - $message = $messages[$code] ?? 'Unknown error'; - $result = ['message' => $message, 'code' => $code, 'pattern_message' => null]; - $error = error_get_last(); - - if ($error !== null && starts_with($error['message'], $function)) { - $result['pattern_message'] = strip_start($error['message'], sprintf('%s(): ', $function)); - } +/** + * @return null|array{message: string, code: int, pattern_message: null|string} + * @internal + */ +function get_preg_error(string $function): ?array +{ + $code = preg_last_error(); + if ($code === PREG_NO_ERROR) { + return null; + } - return $result; + $messages = [ + PREG_INTERNAL_ERROR => 'Internal error', + PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 characters, possibly incorrectly encoded', + PREG_BAD_UTF8_OFFSET_ERROR => 'The offset did not correspond to the beginning of a valid UTF-8 code point', + PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted', + PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted', + PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted', + ]; + + $message = $messages[$code] ?? 'Unknown error'; + $result = ['message' => $message, 'code' => $code, 'pattern_message' => null]; + $error = error_get_last(); + + if ($error !== null && starts_with($error['message'], $function)) { + $result['pattern_message'] = strip_start($error['message'], sprintf('%s(): ', $function)); } - /** - * @template T - * - * @param non-empty-string $function - * @param Closure(): T $closure - * - * @return T - * @internal - */ - function call_preg(string $function, Closure $closure): mixed - { - error_clear_last(); - $result = @$closure(); - - if ($error = get_preg_error($function)) { - if ($error['pattern_message'] !== null) { - throw new InvalidPatternException($error['pattern_message'], $error['code']); - } - - throw new RuntimeException($error['message'], $error['code']); + return $result; +} + +/** + * @template T + * + * @param non-empty-string $function + * @param Closure(): T $closure + * + * @return T + * @internal + */ +function call_preg(string $function, Closure $closure): mixed +{ + error_clear_last(); + $result = @$closure(); + + if ($error = get_preg_error($function)) { + if ($error['pattern_message'] !== null) { + throw new InvalidPatternException($error['pattern_message'], $error['code']); } - return $result; + throw new RuntimeException($error['message'], $error['code']); } + + return $result; } diff --git a/packages/support/src/Str/functions.php b/packages/support/src/Str/functions.php index e56f30a22c..5842b86a7c 100644 --- a/packages/support/src/Str/functions.php +++ b/packages/support/src/Str/functions.php @@ -2,933 +2,933 @@ declare(strict_types=1); -namespace Tempest\Support\Str { - use BackedEnum; - use Stringable; - use Tempest\Support\Arr; - use UnitEnum; - use voku\helper\ASCII; - - use function levenshtein as php_levenshtein; - use function metaphone as php_metaphone; - use function strip_tags as php_strip_tags; - use function Tempest\Support\arr; - - /** - * Converts the given string to title case. - */ - function to_title_case(Stringable|string $string): string - { - return mb_convert_case((string) $string, mode: MB_CASE_TITLE, encoding: 'UTF-8'); - } - - /** - * Converts the given string to a naive sentence case. This doesn't detect proper nouns and proper adjectives that should stay capitalized. - */ - function to_sentence_case(Stringable|string $string): string - { - $words = array_map( - callback: fn (string $string) => to_lower_case($string), - array: to_words($string), - ); - - return upper_first(implode(' ', $words)); - } - - /** - * Converts the given string to lower case. - */ - function to_lower_case(Stringable|string $string): string - { - return mb_strtolower((string) $string, encoding: 'UTF-8'); - } - - /** - * Converts the given string to upper case. - */ - function to_upper_case(Stringable|string $string): string - { - return mb_strtoupper((string) $string, encoding: 'UTF-8'); - } - - /** - * Converts the given string to snake case. - * - * @mago-expect lint:require-preg-quote-delimiter - */ - function to_snake_case(Stringable|string $string, Stringable|string $delimiter = '_'): string - { - $string = (string) $string; - $delimiter = (string) $delimiter; - - if (ctype_lower($string)) { - return $string; - } +namespace Tempest\Support\Str; + +use BackedEnum; +use Stringable; +use Tempest\Support\Arr; +use UnitEnum; +use voku\helper\ASCII; + +use function levenshtein as php_levenshtein; +use function metaphone as php_metaphone; +use function strip_tags as php_strip_tags; +use function Tempest\Support\arr; + +/** + * Converts the given string to title case. + */ +function to_title_case(Stringable|string $string): string +{ + return mb_convert_case((string) $string, mode: MB_CASE_TITLE, encoding: 'UTF-8'); +} - $string = preg_replace('/(?<=\p{Ll}|\p{N})(\p{Lu})/u', $delimiter . '$1', $string); - $string = preg_replace('/(?<=\p{Lu})(\p{Lu}\p{Ll})/u', $delimiter . '$1', $string); - $string = preg_replace('![^' . preg_quote($delimiter) . '\pL\pN\s]+!u', $delimiter, mb_strtolower($string, 'UTF-8')); - $string = preg_replace('/\s+/u', $delimiter, $string); - $string = trim($string, $delimiter); +/** + * Converts the given string to a naive sentence case. This doesn't detect proper nouns and proper adjectives that should stay capitalized. + */ +function to_sentence_case(Stringable|string $string): string +{ + $words = array_map( + callback: fn (string $string) => to_lower_case($string), + array: to_words($string), + ); + + return upper_first(implode(' ', $words)); +} - return namespace\deduplicate($string, $delimiter); - } - - /** - * Returns an array of words from the specified string. - * This is more accurate than {@see str_word_count()}. - */ - function to_words(Stringable|string $string): array - { - // Remove 'words' that don't consist of alphanumerical characters or punctuation - $words = trim(preg_replace("#[^(\w|\d|\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]+#", ' ', (string) $string)); - // Remove one-letter 'words' that consist only of punctuation - $words = trim(preg_replace("#\s*[(\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]\s*#", ' ', $words)); - - return array_values(array_filter(explode(' ', namespace\deduplicate($words)))); - } - - /** - * Counts the number of words in the given string. - */ - function word_count(Stringable|string $string): int - { - return count(to_words($string)); - } - - /** - * Converts the given string to kebab case. - */ - function to_kebab_case(Stringable|string $string): string - { - return to_snake_case((string) $string, delimiter: '-'); - } - - /** - * Converts the given string to pascal case. - */ - function to_pascal_case(Stringable|string $string): string - { - $string = (string) $string; - $words = explode(' ', str_replace(['-', '_'], ' ', $string)); - $studlyWords = array_map(mb_ucfirst(...), $words); - - return implode('', $studlyWords); - } - - /** - * Converts the given string to camel case. - */ - function to_camel_case(Stringable|string $string): string - { - return lcfirst(to_pascal_case((string) $string)); - } - - /** - * Converts the given string to an URL-safe slug. - * - * @param bool $replaceSymbols Adds some more replacements e.g. "£" with "pound". - */ - function to_slug(Stringable|string $string, Stringable|string $separator = '-', array $replacements = [], bool $replaceSymbols = true): string - { - return ASCII::to_slugify((string) $string, (string) $separator, replacements: $replacements, replace_extra_symbols: $replaceSymbols); - } - - /** - * Transliterates the given string to ASCII. - * - * @param string $language Language of the source string. Defaults to english. - */ - function to_ascii(Stringable|string $string, Stringable|string $language = 'en'): string - { - return ASCII::to_ascii((string) $string, (string) $language, replace_single_chars_only: false); - } - - /** - * Checks whether the given string is valid ASCII. - */ - function is_ascii(Stringable|string $string): bool - { - return ASCII::is_ascii((string) $string); - } - - /** - * Changes the case of the first letter to uppercase. - */ - function upper_first(Stringable|string $string): string - { - return mb_ucfirst((string) $string); - } - - /** - * Changes the case of the first letter to lowercase. - */ - function lower_first(Stringable|string $string): string - { - return mb_lcfirst((string) $string); - } - - /** - * Replaces consecutive instances of a given character with a single character. - */ - function deduplicate(Stringable|string $string, Stringable|string|iterable $characters = ' '): string - { - $string = (string) $string; - - foreach (Arr\wrap($characters) as $character) { - $string = preg_replace('/' . preg_quote($character, '/') . '+/u', $character, $string); - } +/** + * Converts the given string to lower case. + */ +function to_lower_case(Stringable|string $string): string +{ + return mb_strtolower((string) $string, encoding: 'UTF-8'); +} +/** + * Converts the given string to upper case. + */ +function to_upper_case(Stringable|string $string): string +{ + return mb_strtoupper((string) $string, encoding: 'UTF-8'); +} + +/** + * Converts the given string to snake case. + * + * @mago-expect lint:require-preg-quote-delimiter + */ +function to_snake_case(Stringable|string $string, Stringable|string $delimiter = '_'): string +{ + $string = (string) $string; + $delimiter = (string) $delimiter; + + if (ctype_lower($string)) { return $string; } - /** - * Ensures the given string starts with the specified `$prefix`. - */ - function ensure_starts_with(Stringable|string $string, Stringable|string $prefix): string - { - return $prefix . preg_replace('/^(?:' . preg_quote($prefix, '/') . ')+/u', replacement: '', subject: (string) $string); - } + $string = preg_replace('/(?<=\p{Ll}|\p{N})(\p{Lu})/u', $delimiter . '$1', $string); + $string = preg_replace('/(?<=\p{Lu})(\p{Lu}\p{Ll})/u', $delimiter . '$1', $string); + $string = preg_replace('![^' . preg_quote($delimiter) . '\pL\pN\s]+!u', $delimiter, mb_strtolower($string, 'UTF-8')); + $string = preg_replace('/\s+/u', $delimiter, $string); + $string = trim($string, $delimiter); - /** - * Ensures the given string ends with the specified `$cap`. - */ - function ensure_ends_with(Stringable|string $string, Stringable|string $cap): string - { - return preg_replace('/(?:' . preg_quote((string) $cap, '/') . ')+$/u', replacement: '', subject: (string) $string) . $cap; - } + return namespace\deduplicate($string, $delimiter); +} + +/** + * Returns an array of words from the specified string. + * This is more accurate than {@see str_word_count()}. + */ +function to_words(Stringable|string $string): array +{ + // Remove 'words' that don't consist of alphanumerical characters or punctuation + $words = trim(preg_replace("#[^(\w|\d|\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]+#", ' ', (string) $string)); + // Remove one-letter 'words' that consist only of punctuation + $words = trim(preg_replace("#\s*[(\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]\s*#", ' ', $words)); + + return array_values(array_filter(explode(' ', namespace\deduplicate($words)))); +} - /** - * Returns the remainder of the string after the first occurrence of the given value. - */ - function after_first(Stringable|string $string, Stringable|string|array $search): string - { - $string = (string) $string; - $search = normalize_string($search); +/** + * Counts the number of words in the given string. + */ +function word_count(Stringable|string $string): int +{ + return count(to_words($string)); +} - if ($search === '' || $search === []) { - return $string; - } +/** + * Converts the given string to kebab case. + */ +function to_kebab_case(Stringable|string $string): string +{ + return to_snake_case((string) $string, delimiter: '-'); +} + +/** + * Converts the given string to pascal case. + */ +function to_pascal_case(Stringable|string $string): string +{ + $string = (string) $string; + $words = explode(' ', str_replace(['-', '_'], ' ', $string)); + $studlyWords = array_map(mb_ucfirst(...), $words); - $nearestPosition = mb_strlen($string); // Initialize with a large value - $foundSearch = ''; + return implode('', $studlyWords); +} - foreach (Arr\wrap($search) as $term) { - $position = mb_strpos($string, $term); +/** + * Converts the given string to camel case. + */ +function to_camel_case(Stringable|string $string): string +{ + return lcfirst(to_pascal_case((string) $string)); +} - if ($position !== false && $position < $nearestPosition) { - $nearestPosition = $position; - $foundSearch = $term; - } - } +/** + * Converts the given string to an URL-safe slug. + * + * @param bool $replaceSymbols Adds some more replacements e.g. "£" with "pound". + */ +function to_slug(Stringable|string $string, Stringable|string $separator = '-', array $replacements = [], bool $replaceSymbols = true): string +{ + return ASCII::to_slugify((string) $string, (string) $separator, replacements: $replacements, replace_extra_symbols: $replaceSymbols); +} - if ($nearestPosition === mb_strlen($string)) { - return $string; - } +/** + * Transliterates the given string to ASCII. + * + * @param string $language Language of the source string. Defaults to english. + */ +function to_ascii(Stringable|string $string, Stringable|string $language = 'en'): string +{ + return ASCII::to_ascii((string) $string, (string) $language, replace_single_chars_only: false); +} + +/** + * Checks whether the given string is valid ASCII. + */ +function is_ascii(Stringable|string $string): bool +{ + return ASCII::is_ascii((string) $string); +} + +/** + * Changes the case of the first letter to uppercase. + */ +function upper_first(Stringable|string $string): string +{ + return mb_ucfirst((string) $string); +} + +/** + * Changes the case of the first letter to lowercase. + */ +function lower_first(Stringable|string $string): string +{ + return mb_lcfirst((string) $string); +} + +/** + * Replaces consecutive instances of a given character with a single character. + */ +function deduplicate(Stringable|string $string, Stringable|string|iterable $characters = ' '): string +{ + $string = (string) $string; - return mb_substr($string, $nearestPosition + mb_strlen($foundSearch)); + foreach (Arr\wrap($characters) as $character) { + $string = preg_replace('/' . preg_quote($character, '/') . '+/u', $character, $string); } - /** - * Returns the remainder of the string after the last occurrence of the given value. - */ - function after_last(Stringable|string $string, Stringable|string|array $search): string - { - $string = (string) $string; - $search = normalize_string($search); + return $string; +} - if ($search === '' || $search === []) { - return $string; - } +/** + * Ensures the given string starts with the specified `$prefix`. + */ +function ensure_starts_with(Stringable|string $string, Stringable|string $prefix): string +{ + return $prefix . preg_replace('/^(?:' . preg_quote($prefix, '/') . ')+/u', replacement: '', subject: (string) $string); +} - $farthestPosition = -1; - $foundSearch = null; +/** + * Ensures the given string ends with the specified `$cap`. + */ +function ensure_ends_with(Stringable|string $string, Stringable|string $cap): string +{ + return preg_replace('/(?:' . preg_quote((string) $cap, '/') . ')+$/u', replacement: '', subject: (string) $string) . $cap; +} - foreach (Arr\wrap($search) as $term) { - $position = mb_strrpos($string, $term); +/** + * Returns the remainder of the string after the first occurrence of the given value. + */ +function after_first(Stringable|string $string, Stringable|string|array $search): string +{ + $string = (string) $string; + $search = normalize_string($search); - if ($position !== false && $position > $farthestPosition) { - $farthestPosition = $position; - $foundSearch = $term; - } - } + if ($search === '' || $search === []) { + return $string; + } - if ($farthestPosition === -1 || $foundSearch === null) { - return $string; + $nearestPosition = mb_strlen($string); // Initialize with a large value + $foundSearch = ''; + + foreach (Arr\wrap($search) as $term) { + $position = mb_strpos($string, $term); + + if ($position !== false && $position < $nearestPosition) { + $nearestPosition = $position; + $foundSearch = $term; } + } - return mb_substr($string, $farthestPosition + mb_strlen($foundSearch)); + if ($nearestPosition === mb_strlen($string)) { + return $string; } - /** - * Returns the portion of the string before the first occurrence of the given value. - */ - function before_first(Stringable|string $string, Stringable|string|array $search): string - { - $string = (string) $string; - $search = normalize_string($search); + return mb_substr($string, $nearestPosition + mb_strlen($foundSearch)); +} - if ($search === '' || $search === []) { - return $string; - } +/** + * Returns the remainder of the string after the last occurrence of the given value. + */ +function after_last(Stringable|string $string, Stringable|string|array $search): string +{ + $string = (string) $string; + $search = normalize_string($search); - $nearestPosition = mb_strlen($string); + if ($search === '' || $search === []) { + return $string; + } - foreach (Arr\wrap($search) as $char) { - $position = mb_strpos($string, $char); + $farthestPosition = -1; + $foundSearch = null; - if ($position !== false && $position < $nearestPosition) { - $nearestPosition = $position; - } - } + foreach (Arr\wrap($search) as $term) { + $position = mb_strrpos($string, $term); - if ($nearestPosition === mb_strlen($string)) { - return $string; + if ($position !== false && $position > $farthestPosition) { + $farthestPosition = $position; + $foundSearch = $term; } + } - return mb_substr($string, start: 0, length: $nearestPosition); + if ($farthestPosition === -1 || $foundSearch === null) { + return $string; } - /** - * Returns the portion of the string before the last occurrence of the given value. - */ - function before_last(Stringable|string $string, Stringable|string|array $search): string - { - $string = (string) $string; - $search = normalize_string($search); + return mb_substr($string, $farthestPosition + mb_strlen($foundSearch)); +} - if ($search === '' || $search === []) { - return $string; - } +/** + * Returns the portion of the string before the first occurrence of the given value. + */ +function before_first(Stringable|string $string, Stringable|string|array $search): string +{ + $string = (string) $string; + $search = normalize_string($search); - $farthestPosition = -1; + if ($search === '' || $search === []) { + return $string; + } - foreach (Arr\wrap($search) as $char) { - $position = mb_strrpos($string, $char); + $nearestPosition = mb_strlen($string); - if ($position !== false && $position > $farthestPosition) { - $farthestPosition = $position; - } - } + foreach (Arr\wrap($search) as $char) { + $position = mb_strpos($string, $char); - if ($farthestPosition === -1) { - return $string; + if ($position !== false && $position < $nearestPosition) { + $nearestPosition = $position; } - - return mb_substr($string, start: 0, length: $farthestPosition); } - /** - * Returns the multi-bytes length of the string. - */ - function length(Stringable|string $string): int - { - return mb_strlen((string) $string); + if ($nearestPosition === mb_strlen($string)) { + return $string; } - /** - * Returns the base name of the string, assuming the string is a class name. - */ - function class_basename(Stringable|string $string): string - { - return basename(str_replace('\\', '/', (string) $string)); + return mb_substr($string, start: 0, length: $nearestPosition); +} + +/** + * Returns the portion of the string before the last occurrence of the given value. + */ +function before_last(Stringable|string $string, Stringable|string|array $search): string +{ + $string = (string) $string; + $search = normalize_string($search); + + if ($search === '' || $search === []) { + return $string; } - /** - * Asserts whether the string starts with one of the given needles. - */ - function starts_with(Stringable|string $string, Stringable|string|array $needles): bool - { - $string = (string) $string; + $farthestPosition = -1; + + foreach (Arr\wrap($search) as $char) { + $position = mb_strrpos($string, $char); - if (! is_array($needles)) { - $needles = [$needles]; + if ($position !== false && $position > $farthestPosition) { + $farthestPosition = $position; } + } - return array_any($needles, fn ($needle) => str_starts_with($string, (string) $needle)); + if ($farthestPosition === -1) { + return $string; } - /** - * Asserts whether the string ends with one of the given `$needles`. - */ - function ends_with(Stringable|string $string, Stringable|string|array $needles): bool - { - $string = (string) $string; + return mb_substr($string, start: 0, length: $farthestPosition); +} - if (! is_array($needles)) { - $needles = [$needles]; - } +/** + * Returns the multi-bytes length of the string. + */ +function length(Stringable|string $string): int +{ + return mb_strlen((string) $string); +} + +/** + * Returns the base name of the string, assuming the string is a class name. + */ +function class_basename(Stringable|string $string): string +{ + return basename(str_replace('\\', '/', (string) $string)); +} + +/** + * Asserts whether the string starts with one of the given needles. + */ +function starts_with(Stringable|string $string, Stringable|string|array $needles): bool +{ + $string = (string) $string; + + if (! is_array($needles)) { + $needles = [$needles]; + } - return array_any($needles, static fn ($needle) => str_ends_with($string, (string) $needle)); + return array_any($needles, fn ($needle) => str_starts_with($string, (string) $needle)); +} + +/** + * Asserts whether the string ends with one of the given `$needles`. + */ +function ends_with(Stringable|string $string, Stringable|string|array $needles): bool +{ + $string = (string) $string; + + if (! is_array($needles)) { + $needles = [$needles]; } - /** - * Replaces the first occurrence of `$search` with `$replace`. - */ - function replace_first(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string - { - $string = (string) $string; - $search = normalize_string($search); + return array_any($needles, static fn ($needle) => str_ends_with($string, (string) $needle)); +} - foreach (Arr\wrap($search) as $item) { - if ($item === '') { - continue; - } +/** + * Replaces the first occurrence of `$search` with `$replace`. + */ +function replace_first(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string +{ + $string = (string) $string; + $search = normalize_string($search); - $position = strpos($string, (string) $item); + foreach (Arr\wrap($search) as $item) { + if ($item === '') { + continue; + } - if ($position === false) { - continue; - } + $position = strpos($string, (string) $item); - return substr_replace($string, $replace, $position, strlen($item)); + if ($position === false) { + continue; } - return $string; + return substr_replace($string, $replace, $position, strlen($item)); } - /** - * Replaces the last occurrence of `$search` with `$replace`. - */ - function replace_last(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string - { - $string = (string) $string; - $search = normalize_string($search); + return $string; +} - foreach (Arr\wrap($search) as $item) { - if ($item === '') { - continue; - } +/** + * Replaces the last occurrence of `$search` with `$replace`. + */ +function replace_last(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string +{ + $string = (string) $string; + $search = normalize_string($search); - $position = strrpos($string, (string) $item); + foreach (Arr\wrap($search) as $item) { + if ($item === '') { + continue; + } - if ($position === false) { - continue; - } + $position = strrpos($string, (string) $item); - return substr_replace($string, $replace, $position, strlen($item)); + if ($position === false) { + continue; } - return $string; + return substr_replace($string, $replace, $position, strlen($item)); } - /** - * Replaces `$search` with `$replace` if `$search` is at the end of the string. - */ - function replace_end(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string - { - $string = (string) $string; - $search = normalize_string($search); + return $string; +} - foreach (Arr\wrap($search) as $item) { - if ($item === '') { - continue; - } +/** + * Replaces `$search` with `$replace` if `$search` is at the end of the string. + */ +function replace_end(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string +{ + $string = (string) $string; + $search = normalize_string($search); - if (! ends_with($string, $item)) { - continue; - } + foreach (Arr\wrap($search) as $item) { + if ($item === '') { + continue; + } - return replace_last($string, $item, $replace); + if (! ends_with($string, $item)) { + continue; } - return $string; + return replace_last($string, $item, $replace); } - /** - * Replaces `$search` with `$replace` if `$search` is at the start of the string. - */ - function replace_start(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string - { - $string = (string) $string; + return $string; +} - foreach (Arr\wrap($search) as $item) { - if ($item === '') { - continue; - } +/** + * Replaces `$search` with `$replace` if `$search` is at the start of the string. + */ +function replace_start(Stringable|string $string, array|Stringable|string $search, Stringable|string $replace): string +{ + $string = (string) $string; - if (! starts_with($string, $item)) { - continue; - } + foreach (Arr\wrap($search) as $item) { + if ($item === '') { + continue; + } - return replace_first($string, $item, $replace); + if (! starts_with($string, $item)) { + continue; } - return $string; + return replace_first($string, $item, $replace); } - /** - * Strips the specified `$prefix` from the start of the string. - */ - function strip_start(Stringable|string $string, array|Stringable|string $prefix): string - { - return replace_start($string, $prefix, ''); - } + return $string; +} - /** - * Strips the specified `$suffix` from the end of the string. - */ - function strip_end(Stringable|string $string, array|Stringable|string $suffix): string - { - return replace_end($string, $suffix, ''); +/** + * Strips the specified `$prefix` from the start of the string. + */ +function strip_start(Stringable|string $string, array|Stringable|string $prefix): string +{ + return replace_start($string, $prefix, ''); +} + +/** + * Strips the specified `$suffix` from the end of the string. + */ +function strip_end(Stringable|string $string, array|Stringable|string $suffix): string +{ + return replace_end($string, $suffix, ''); +} + +/** + * Replaces the portion of the specified `$length` at the specified `$position` with the specified `$replace`. + */ +function replace_at(Stringable|string $string, int $position, int $length, Stringable|string $replace): string +{ + $string = (string) $string; + + if ($length < 0) { + $position += $length; + $length = abs($length); } - /** - * Replaces the portion of the specified `$length` at the specified `$position` with the specified `$replace`. - */ - function replace_at(Stringable|string $string, int $position, int $length, Stringable|string $replace): string - { - $string = (string) $string; + return substr_replace($string, (string) $replace, $position, $length); +} - if ($length < 0) { - $position += $length; - $length = abs($length); - } +/** + * Returns the '$haystack' string with all occurrences of the keys of `$replacements` replaced by the corresponding values. + * + * @param array $replacements + */ +function replace_every(Stringable|string $haystack, array $replacements): string +{ + $string = (string) $haystack; - return substr_replace($string, (string) $replace, $position, $length); + foreach ($replacements as $needle => $replacement) { + $string = namespace\replace($string, $needle, (string) $replacement); } - /** - * Returns the '$haystack' string with all occurrences of the keys of `$replacements` replaced by the corresponding values. - * - * @param array $replacements - */ - function replace_every(Stringable|string $haystack, array $replacements): string - { - $string = (string) $haystack; + return $string; +} - foreach ($replacements as $needle => $replacement) { - $string = namespace\replace($string, $needle, (string) $replacement); - } +/** + * Appends the given strings to the string. + */ +function append(Stringable|string $string, string|Stringable ...$append): string +{ + return $string . implode('', $append); +} + +/** + * Prepends the given strings to the string. + */ +function prepend(Stringable|string $string, string|Stringable ...$prepend): string +{ + return implode('', $prepend) . $string; +} +/** + * Returns the portion of the string between the widest possible instances of the given strings. + */ +function between(Stringable|string $string, string|Stringable $from, string|Stringable $to): string +{ + $string = (string) $string; + $from = normalize_string($from); + $to = normalize_string($to); + + if ($from === '' || $to === '') { return $string; } - /** - * Appends the given strings to the string. - */ - function append(Stringable|string $string, string|Stringable ...$append): string - { - return $string . implode('', $append); - } + return before_last(after_first($string, $from), $to); +} - /** - * Prepends the given strings to the string. - */ - function prepend(Stringable|string $string, string|Stringable ...$prepend): string - { - return implode('', $prepend) . $string; - } +/** + * Wraps the string with the given string. If `$after` is specified, it will be appended instead of `$before`. + */ +function wrap(Stringable|string $string, string|Stringable $before, string|Stringable|null $after = null): string +{ + return $before . $string . ($after ??= $before); +} - /** - * Returns the portion of the string between the widest possible instances of the given strings. - */ - function between(Stringable|string $string, string|Stringable $from, string|Stringable $to): string - { - $string = (string) $string; - $from = normalize_string($from); - $to = normalize_string($to); +/** + * Removes the specified `$before` and `$after` from the beginning and the end of the string. + */ +function unwrap(Stringable|string $string, string|Stringable $before, string|Stringable|null $after = null, bool $strict = true): string +{ + $string = (string) $string; - if ($from === '' || $to === '') { - return $string; - } + if ($string === '') { + return $string; + } - return before_last(after_first($string, $from), $to); + if ($after === null) { + $after = $before; } - /** - * Wraps the string with the given string. If `$after` is specified, it will be appended instead of `$before`. - */ - function wrap(Stringable|string $string, string|Stringable $before, string|Stringable|null $after = null): string - { - return $before . $string . ($after ??= $before); + if (! $strict) { + return before_last(after_first($string, $before), $after); } - /** - * Removes the specified `$before` and `$after` from the beginning and the end of the string. - */ - function unwrap(Stringable|string $string, string|Stringable $before, string|Stringable|null $after = null, bool $strict = true): string - { - $string = (string) $string; + if (starts_with($string, $before) && ends_with($string, $after)) { + return before_last(after_first($string, $before), $after); + } - if ($string === '') { - return $string; - } + return $string; +} - if ($after === null) { - $after = $before; - } +/** + * Replaces all occurrences of the given `$search` with `$replace`. + */ +function replace(Stringable|string $string, Stringable|string|array $search, Stringable|string|array $replace): string +{ + $string = (string) $string; + $search = normalize_string($search); + $replace = normalize_string($replace); - if (! $strict) { - return before_last(after_first($string, $before), $after); - } + return str_replace($search, $replace, $string); +} - if (starts_with($string, $before) && ends_with($string, $after)) { - return before_last(after_first($string, $before), $after); - } +/** + * Extracts an excerpt from the string. + */ +function excerpt(Stringable|string $string, int $from, int $to, bool $asArray = false): string|array +{ + $string = (string) $string; + $lines = explode(PHP_EOL, $string); + + $from = max(0, $from - 1); + $to = min($to - 1, count($lines)); + $lines = array_slice($lines, offset: $from, length: $to - $from + 1, preserve_keys: true); + + if ($asArray) { + return arr($lines) + ->mapWithKeys(fn (string $line, int $number) => yield $number + 1 => $line) + ->toArray(); + } + + return implode(PHP_EOL, $lines); +} +/** + * Truncates the string to the specified amount of characters. + */ +function truncate_end(Stringable|string $string, int $characters, Stringable|string $end = ''): string +{ + $string = (string) $string; + $end = (string) $end; + + if (mb_strwidth($string, 'UTF-8') <= $characters) { return $string; } - /** - * Replaces all occurrences of the given `$search` with `$replace`. - */ - function replace(Stringable|string $string, Stringable|string|array $search, Stringable|string|array $replace): string - { - $string = (string) $string; - $search = normalize_string($search); - $replace = normalize_string($replace); - - return str_replace($search, $replace, $string); + if ($characters < 0) { + $characters = mb_strlen($string) + $characters; } - /** - * Extracts an excerpt from the string. - */ - function excerpt(Stringable|string $string, int $from, int $to, bool $asArray = false): string|array - { - $string = (string) $string; - $lines = explode(PHP_EOL, $string); + return rtrim(mb_strimwidth($string, 0, $characters, encoding: 'UTF-8')) . $end; +} + +/** + * Truncates the string to the specified amount of characters from the start. + */ +function truncate_start(Stringable|string $string, int $characters, Stringable|string $start = ''): string +{ + return reverse(truncate_end(reverse((string) $string), $characters, (string) $start)); +} - $from = max(0, $from - 1); - $to = min($to - 1, count($lines)); - $lines = array_slice($lines, offset: $from, length: $to - $from + 1, preserve_keys: true); +/** + * Reverses the string. + */ +function reverse(Stringable|string $string): string +{ + return implode('', array_reverse(mb_str_split((string) $string, length: 1))); +} - if ($asArray) { - return arr($lines) - ->mapWithKeys(fn (string $line, int $number) => yield $number + 1 => $line) - ->toArray(); - } +/** + * Gets parts of the string. + */ +function slice(Stringable|string $string, int $start, ?int $length = null): string +{ + $stringLength = namespace\length($string); - return implode(PHP_EOL, $lines); + if (0 === $start && (null === $length || $stringLength <= $length)) { + return $string; } - /** - * Truncates the string to the specified amount of characters. - */ - function truncate_end(Stringable|string $string, int $characters, Stringable|string $end = ''): string - { - $string = (string) $string; - $end = (string) $end; + return mb_substr((string) $string, $start, $length); +} - if (mb_strwidth($string, 'UTF-8') <= $characters) { - return $string; +/** + * Checks whether the given string contains the specified `$needle`. + */ +function contains(Stringable|string $string, Stringable|string|array $needle): bool +{ + foreach (Arr\wrap($needle) as $item) { + if (str_contains((string) $string, (string) $item)) { + return true; } + } - if ($characters < 0) { - $characters = mb_strlen($string) + $characters; - } + return false; +} - return rtrim(mb_strimwidth($string, 0, $characters, encoding: 'UTF-8')) . $end; - } +/** + * Takes the specified amount of characters. If `$length` is negative, starts from the end. + */ +function take(Stringable|string $string, int $length): string +{ + $string = (string) $string; - /** - * Truncates the string to the specified amount of characters from the start. - */ - function truncate_start(Stringable|string $string, int $characters, Stringable|string $start = ''): string - { - return reverse(truncate_end(reverse((string) $string), $characters, (string) $start)); + if ($length < 0) { + return slice($string, $length); } - /** - * Reverses the string. - */ - function reverse(Stringable|string $string): string - { - return implode('', array_reverse(mb_str_split((string) $string, length: 1))); - } + return slice($string, 0, $length); +} - /** - * Gets parts of the string. - */ - function slice(Stringable|string $string, int $start, ?int $length = null): string - { - $stringLength = namespace\length($string); +/** + * Chunks the string into parts of the specified `$length`. + */ +function chunk(Stringable|string $string, int $length): array +{ + $string = (string) $string; - if (0 === $start && (null === $length || $stringLength <= $length)) { - return $string; - } + if ($length <= 0) { + return []; + } - return mb_substr((string) $string, $start, $length); + if ($string === '') { + return ['']; } - /** - * Checks whether the given string contains the specified `$needle`. - */ - function contains(Stringable|string $string, Stringable|string|array $needle): bool - { - foreach (Arr\wrap($needle) as $item) { - if (str_contains((string) $string, (string) $item)) { - return true; - } - } + $chunks = []; - return false; + foreach (str_split($string, $length) as $chunk) { + $chunks[] = $chunk; } - /** - * Takes the specified amount of characters. If `$length` is negative, starts from the end. - */ - function take(Stringable|string $string, int $length): string - { - $string = (string) $string; + return $chunks; +} - if ($length < 0) { - return slice($string, $length); - } +/** + * Strips HTML and PHP tags from the string. + */ +function strip_tags(Stringable|string $string, null|string|array $allowed = null): string +{ + $string = (string) $string; - return slice($string, 0, $length); - } + $allowed = arr($allowed) + ->map(fn (string $tag) => wrap($tag, '<', '>')) + ->toArray(); - /** - * Chunks the string into parts of the specified `$length`. - */ - function chunk(Stringable|string $string, int $length): array - { - $string = (string) $string; + return php_strip_tags($string, $allowed); +} - if ($length <= 0) { - return []; - } +/** + * Pads the string to the given `$width` and centers the text in it. + */ +function align_center(Stringable|string $string, ?int $width, int $padding = 0): string +{ + $text = trim((string) $string); + $textLength = length($text); + $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); + $leftPadding = (int) floor(($actualWidth - $textLength) / 2); + $rightPadding = $actualWidth - $leftPadding - $textLength; + + return str_repeat(' ', $leftPadding) . $text . str_repeat(' ', $rightPadding); +} - if ($string === '') { - return ['']; - } +/** + * Pads the string to the given `$width` and aligns the text to the right. + */ +function align_right(Stringable|string $string, ?int $width, int $padding = 0): string +{ + $text = trim((string) $string); + $textLength = length($text); + $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); + $leftPadding = $actualWidth - $textLength - $padding; + + return str_repeat(' ', $leftPadding) . $text . str_repeat(' ', $padding); +} - $chunks = []; +/** + * Pads the string to the given `$width` and aligns the text to the left. + */ +function align_left(Stringable|string $string, ?int $width, int $padding = 0): string +{ + $text = trim((string) $string); + $textLength = length($text); + $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); + $rightPadding = $actualWidth - $textLength - $padding; + + return str_repeat(' ', $padding) . $text . str_repeat(' ', $rightPadding); +} - foreach (str_split($string, $length) as $chunk) { - $chunks[] = $chunk; +/** + * Returns the string padded to the total length by appending the `$pad_string` to the left. + * + * If the length of the input string plus the pad string exceeds the total + * length, the pad string will be truncated. If the total length is less than or + * equal to the length of the input string, no padding will occur. + * + * Example: + * pad_left('Ay', 4) + * => ' Ay' + * + * pad_left('ay', 3, 'A') + * => 'Aay' + * + * pad_left('eet', 4, 'Yeeeee') + * => 'Yeet' + * + * pad_left('مرحبا', 8, 'م') + * => 'ممممرحبا' + * + * @param non-empty-string $padString + * @param int<0, max> $totalLength + */ +function pad_left(string $string, int $totalLength, string $padString = ' '): string +{ + do { + $length = namespace\length($string); + + if ($length >= $totalLength) { + return $string; } - return $chunks; - } + /** @var int<0, max> $remaining */ + $remaining = $totalLength - $length; - /** - * Strips HTML and PHP tags from the string. - */ - function strip_tags(Stringable|string $string, null|string|array $allowed = null): string - { - $string = (string) $string; + if ($remaining <= namespace\length($padString)) { + $padString = namespace\slice($padString, 0, $remaining); + } - $allowed = arr($allowed) - ->map(fn (string $tag) => wrap($tag, '<', '>')) - ->toArray(); + $string = $padString . $string; + } while (true); +} - return php_strip_tags($string, $allowed); - } - - /** - * Pads the string to the given `$width` and centers the text in it. - */ - function align_center(Stringable|string $string, ?int $width, int $padding = 0): string - { - $text = trim((string) $string); - $textLength = length($text); - $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); - $leftPadding = (int) floor(($actualWidth - $textLength) / 2); - $rightPadding = $actualWidth - $leftPadding - $textLength; - - return str_repeat(' ', $leftPadding) . $text . str_repeat(' ', $rightPadding); - } - - /** - * Pads the string to the given `$width` and aligns the text to the right. - */ - function align_right(Stringable|string $string, ?int $width, int $padding = 0): string - { - $text = trim((string) $string); - $textLength = length($text); - $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); - $leftPadding = $actualWidth - $textLength - $padding; - - return str_repeat(' ', $leftPadding) . $text . str_repeat(' ', $padding); - } - - /** - * Pads the string to the given `$width` and aligns the text to the left. - */ - function align_left(Stringable|string $string, ?int $width, int $padding = 0): string - { - $text = trim((string) $string); - $textLength = length($text); - $actualWidth = max($width ?? 0, $textLength + (2 * $padding)); - $rightPadding = $actualWidth - $textLength - $padding; - - return str_repeat(' ', $padding) . $text . str_repeat(' ', $rightPadding); - } - - /** - * Returns the string padded to the total length by appending the `$pad_string` to the left. - * - * If the length of the input string plus the pad string exceeds the total - * length, the pad string will be truncated. If the total length is less than or - * equal to the length of the input string, no padding will occur. - * - * Example: - * pad_left('Ay', 4) - * => ' Ay' - * - * pad_left('ay', 3, 'A') - * => 'Aay' - * - * pad_left('eet', 4, 'Yeeeee') - * => 'Yeet' - * - * pad_left('مرحبا', 8, 'م') - * => 'ممممرحبا' - * - * @param non-empty-string $padString - * @param int<0, max> $totalLength - */ - function pad_left(string $string, int $totalLength, string $padString = ' '): string - { - do { - $length = namespace\length($string); - - if ($length >= $totalLength) { - return $string; - } - - /** @var int<0, max> $remaining */ - $remaining = $totalLength - $length; - - if ($remaining <= namespace\length($padString)) { - $padString = namespace\slice($padString, 0, $remaining); - } - - $string = $padString . $string; - } while (true); - } - - /** - * Returns the string padded to the total length by appending the `$pad_string` to the right. - * - * If the length of the input string plus the pad string exceeds the total - * length, the pad string will be truncated. If the total length is less than or - * equal to the length of the input string, no padding will occur. - * - * Example: - * pad_right('Ay', 4) - * => 'Ay ' - * - * pad_right('Ay', 5, 'y') - * => 'Ayyyy' - * - * pad_right('Yee', 4, 't') - * => 'Yeet' - * - * pad_right('مرحبا', 8, 'ا') - * => 'مرحباااا' - * - * @param non-empty-string $padString - * @param int<0, max> $totalLength - */ - function pad_right(string $string, int $totalLength, string $padString = ' '): string - { - do { - $length = namespace\length($string); - - if ($length >= $totalLength) { - return $string; - } - - /** @var int<0, max> $remaining */ - $remaining = $totalLength - $length; - - if ($remaining <= namespace\length($padString)) { - $padString = namespace\slice($padString, 0, $remaining); - } - - $string .= $padString; - } while (true); - } - - /** - * Inserts the specified `$insertion` at the specified `$position`. - */ - function insert_at(Stringable|string $string, int $position, string $insertion): string - { - $string = (string) $string; - - return mb_substr($string, 0, $position) . $insertion . mb_substr($string, $position); - } - - /** - * Calculates the levenshtein difference between two strings. - */ - function levenshtein(Stringable|string $string, string|Stringable $other): int - { - return php_levenshtein((string) $string, (string) $other); - } - - /** - * Calculate the metaphone key of a string. - */ - function metaphone(Stringable|string $string, int $phonemes = 0): string - { - return php_metaphone((string) $string, $phonemes); - } - - /** - * Checks whether a string is empty - */ - function is_empty(Stringable|string $string): bool - { - return (string) $string === ''; - } - - /** - * Asserts whether the string is equal to the given string. - */ - function equals(Stringable|string $string, string|Stringable $other): bool - { - return (string) $string === (string) $other; - } - - /** - * Parses the given value to a string, returning the default value if it is not a string or `Stringable`. - */ - function parse(mixed $string, ?string $default = ''): ?string - { - if (is_string($string)) { +/** + * Returns the string padded to the total length by appending the `$pad_string` to the right. + * + * If the length of the input string plus the pad string exceeds the total + * length, the pad string will be truncated. If the total length is less than or + * equal to the length of the input string, no padding will occur. + * + * Example: + * pad_right('Ay', 4) + * => 'Ay ' + * + * pad_right('Ay', 5, 'y') + * => 'Ayyyy' + * + * pad_right('Yee', 4, 't') + * => 'Yeet' + * + * pad_right('مرحبا', 8, 'ا') + * => 'مرحباااا' + * + * @param non-empty-string $padString + * @param int<0, max> $totalLength + */ +function pad_right(string $string, int $totalLength, string $padString = ' '): string +{ + do { + $length = namespace\length($string); + + if ($length >= $totalLength) { return $string; } - if (is_int($string) || is_float($string)) { - return (string) $string; - } + /** @var int<0, max> $remaining */ + $remaining = $totalLength - $length; - if ($string instanceof Stringable) { - return (string) $string; + if ($remaining <= namespace\length($padString)) { + $padString = namespace\slice($padString, 0, $remaining); } - if ($string instanceof BackedEnum) { - return (string) $string->value; - } + $string .= $padString; + } while (true); +} - if ($string instanceof UnitEnum) { - return $string->name; - } +/** + * Inserts the specified `$insertion` at the specified `$position`. + */ +function insert_at(Stringable|string $string, int $position, string $insertion): string +{ + $string = (string) $string; - if (is_object($string) && method_exists($string, '__toString')) { - return (string) $string; - } + return mb_substr($string, 0, $position) . $insertion . mb_substr($string, $position); +} + +/** + * Calculates the levenshtein difference between two strings. + */ +function levenshtein(Stringable|string $string, string|Stringable $other): int +{ + return php_levenshtein((string) $string, (string) $other); +} + +/** + * Calculate the metaphone key of a string. + */ +function metaphone(Stringable|string $string, int $phonemes = 0): string +{ + return php_metaphone((string) $string, $phonemes); +} + +/** + * Checks whether a string is empty + */ +function is_empty(Stringable|string $string): bool +{ + return (string) $string === ''; +} + +/** + * Asserts whether the string is equal to the given string. + */ +function equals(Stringable|string $string, string|Stringable $other): bool +{ + return (string) $string === (string) $other; +} - return $default; +/** + * Parses the given value to a string, returning the default value if it is not a string or `Stringable`. + */ +function parse(mixed $string, ?string $default = ''): ?string +{ + if (is_string($string)) { + return $string; } - /** - * Normalizes `Stringable` to string, while keeping other values the same. - * - * @internal - */ - function normalize_string(mixed $value): mixed - { - if ($value instanceof Stringable) { - return (string) $value; - } + if (is_int($string) || is_float($string)) { + return (string) $string; + } - return $value; + if ($string instanceof Stringable) { + return (string) $string; } + + if ($string instanceof BackedEnum) { + return (string) $string->value; + } + + if ($string instanceof UnitEnum) { + return $string->name; + } + + if (is_object($string) && method_exists($string, '__toString')) { + return (string) $string; + } + + return $default; +} + +/** + * Normalizes `Stringable` to string, while keeping other values the same. + * + * @internal + */ +function normalize_string(mixed $value): mixed +{ + if ($value instanceof Stringable) { + return (string) $value; + } + + return $value; } diff --git a/packages/support/src/functions.php b/packages/support/src/functions.php index 46470069bf..111c7e03b3 100644 --- a/packages/support/src/functions.php +++ b/packages/support/src/functions.php @@ -2,81 +2,81 @@ declare(strict_types=1); -namespace Tempest\Support { - use Closure; - use Stringable; - use Tempest\Support\Arr\ImmutableArray; - use Tempest\Support\Path\Path; - use Tempest\Support\Str\ImmutableString; +namespace Tempest\Support; - /** - * Creates an instance of {@see \Tempest\Support\Str\ImmutableString} using the given `$string`. - */ - function str(Stringable|int|string|null $string = ''): ImmutableString - { - return new ImmutableString($string); - } +use Closure; +use Stringable; +use Tempest\Support\Arr\ImmutableArray; +use Tempest\Support\Path\Path; +use Tempest\Support\Str\ImmutableString; - /** - * Creates an instance of {@see \Tempest\Support\Arr\ImmutableArray} using the given `$input`. If `$input` is not an array, it will be wrapped in one. - */ - function arr(mixed $input = []): ImmutableArray - { - return new ImmutableArray($input); - } +/** + * Creates an instance of {@see \Tempest\Support\Str\ImmutableString} using the given `$string`. + */ +function str(Stringable|int|string|null $string = ''): ImmutableString +{ + return new ImmutableString($string); +} - /** - * Normalizes the given path without checking it against the filesystem. - */ - function path(Stringable|string ...$parts): Path - { - return new Path(...$parts); - } +/** + * Creates an instance of {@see \Tempest\Support\Arr\ImmutableArray} using the given `$input`. If `$input` is not an array, it will be wrapped in one. + */ +function arr(mixed $input = []): ImmutableArray +{ + return new ImmutableArray($input); +} - /** - * Executes the callback with the given `$value` and returns the same `$value`. - * - * @template T - * - * @param T $value - * @param (callable(T): void) $callback - * - * @return T - */ - function tap(mixed $value, callable $callback): mixed - { - $callback($value); +/** + * Normalizes the given path without checking it against the filesystem. + */ +function path(Stringable|string ...$parts): Path +{ + return new Path(...$parts); +} - return $value; - } +/** + * Executes the callback with the given `$value` and returns the same `$value`. + * + * @template T + * + * @param T $value + * @param (callable(T): void) $callback + * + * @return T + */ +function tap(mixed $value, callable $callback): mixed +{ + $callback($value); - /** - * Returns a tuple containing the result of the `$callback` as the first element and the error message as the second element if there was an error. - * - * @template T - * - * @param (Closure(): T) $callback - * - * @return array{0: T, 1: ?string} - */ - function box(Closure $callback): array - { - $lastMessage = null; + return $value; +} - set_error_handler(static function (int $_type, string $message) use (&$lastMessage): void { // @phpstan-ignore argument.type - $lastMessage = $message; - }); +/** + * Returns a tuple containing the result of the `$callback` as the first element and the error message as the second element if there was an error. + * + * @template T + * + * @param (Closure(): T) $callback + * + * @return array{0: T, 1: ?string} + */ +function box(Closure $callback): array +{ + $lastMessage = null; - try { - $value = $callback(); + set_error_handler(static function (int $_type, string $message) use (&$lastMessage): void { // @phpstan-ignore argument.type + $lastMessage = $message; + }); - if (null !== $lastMessage && Str\contains($lastMessage, '): ')) { - $lastMessage = Str\after_first(Str\to_lower_case($lastMessage), '): '); - } + try { + $value = $callback(); - return [$value, $lastMessage]; - } finally { - restore_error_handler(); + if (null !== $lastMessage && Str\contains($lastMessage, '): ')) { + $lastMessage = Str\after_first(Str\to_lower_case($lastMessage), '): '); } + + return [$value, $lastMessage]; + } finally { + restore_error_handler(); } } diff --git a/packages/support/tests/Filesystem/UnixFunctionsTest.php b/packages/support/tests/Filesystem/UnixFunctionsTest.php index 1ddeafa5b4..d30c3a8a7f 100644 --- a/packages/support/tests/Filesystem/UnixFunctionsTest.php +++ b/packages/support/tests/Filesystem/UnixFunctionsTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\Attributes\PostCondition; use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Tempest\Support\Filesystem; @@ -14,6 +15,7 @@ use Tempest\Support\Filesystem\Exceptions\PathWasNotFound; use Tempest\Support\Filesystem\Exceptions\PathWasNotReadable; use Tempest\Support\Filesystem\Exceptions\RuntimeException; +use Tempest\Support\Filesystem\LockType; final class UnixFunctionsTest extends TestCase { @@ -38,12 +40,18 @@ protected function cleanup(): void return; } + // restore permissions for cleanup + if (Filesystem\exists($this->fixtures)) { + exec(sprintf('chmod -R 0755 %s 2>/dev/null', escapeshellarg($this->fixtures))); + } + Filesystem\delete_directory($this->fixtures); $this->assertFalse(is_dir($this->fixtures)); } - public function test_create_directory(): void + #[Test] + public function create_directory(): void { $directory = $this->fixtures . '/tmp'; @@ -52,7 +60,8 @@ public function test_create_directory(): void $this->assertTrue(is_dir($directory)); } - public function test_create_directory_when_file_exists(): void + #[Test] + public function create_directory_when_file_exists(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/^Failed to create directory.*/'); @@ -64,7 +73,8 @@ public function test_create_directory_when_file_exists(): void Filesystem\create_directory($file); } - public function test_create_directory_for_file(): void + #[Test] + public function create_directory_for_file(): void { $file = $this->fixtures . '/tmp/file.txt'; @@ -73,7 +83,59 @@ public function test_create_directory_for_file(): void $this->assertTrue(is_dir(dirname($file))); } - public function test_create_file(): void + #[Test] + public function create_temporary_directory(): void + { + $tmp = Filesystem\create_temporary_directory(); + + $this->assertNotEmpty($tmp); + $this->assertTrue(is_dir($tmp)); + $this->assertDirectoryIsWritable($tmp); + + Filesystem\delete_directory($tmp); + } + + #[Test] + public function create_temporary_directory_with_prefix(): void + { + $tmp = Filesystem\create_temporary_directory('test_prefix'); + + $this->assertNotEmpty($tmp); + $this->assertTrue(is_dir($tmp)); + $this->assertStringContainsString('test_prefix', basename($tmp)); + + Filesystem\delete_directory($tmp); + } + + #[Test] + public function create_temporary_directory_is_empty(): void + { + $tmp = Filesystem\create_temporary_directory(); + + $files = scandir($tmp); + + $this->assertCount(2, $files); + $this->assertEquals(['.', '..'], $files); + + Filesystem\delete_directory($tmp); + } + + #[Test] + public function create_temporary_directory_is_unique(): void + { + $tmp1 = Filesystem\create_temporary_directory(); + $tmp2 = Filesystem\create_temporary_directory(); + + $this->assertNotEquals($tmp1, $tmp2); + $this->assertTrue(is_dir($tmp1)); + $this->assertTrue(is_dir($tmp2)); + + Filesystem\delete_directory($tmp1); + Filesystem\delete_directory($tmp2); + } + + #[Test] + public function create_file(): void { $file = $this->fixtures . '/tmp/file.txt'; @@ -82,7 +144,8 @@ public function test_create_file(): void $this->assertTrue(is_file($file)); } - public function test_exists(): void + #[Test] + public function exists(): void { $dir = $this->fixtures . '/tmp'; $file = $this->fixtures . '/tmp/file.txt'; @@ -94,7 +157,8 @@ public function test_exists(): void $this->assertTrue(Filesystem\exists($file)); } - public function test_delete(): void + #[Test] + public function delete(): void { $dir = $this->fixtures . '/tmp'; $file = $this->fixtures . '/tmp/file.txt'; @@ -112,7 +176,28 @@ public function test_delete(): void Filesystem\delete($dir . '/non-existent-path'); } - public function test_delete_directory(): void + #[Test] + public function delete_dir_symlink(): void + { + $dir = $this->fixtures . '/tmp'; + $symlink = $this->fixtures . '/tmp-link'; + + mkdir($dir); + symlink($dir, $symlink); + + $this->assertTrue(is_link($symlink)); + $this->assertTrue(is_dir($dir)); + + Filesystem\delete($symlink); + + $this->assertFalse(is_link($symlink)); + $this->assertFalse(is_dir($symlink)); + $this->assertFalse(is_file($symlink)); + $this->assertTrue(is_dir($dir)); + } + + #[Test] + public function delete_directory(): void { $dir = $this->fixtures . '/tmp'; @@ -123,7 +208,8 @@ public function test_delete_directory(): void $this->assertFalse(is_dir($dir)); } - public function test_delete_directory_on_file(): void + #[Test] + public function delete_directory_on_file(): void { $this->expectException(PathWasNotADirectory::class); @@ -134,7 +220,8 @@ public function test_delete_directory_on_file(): void Filesystem\delete_directory($file); } - public function test_delete_directory_recursive(): void + #[Test] + public function delete_directory_recursive(): void { $dir = $this->fixtures . '/tmp'; @@ -148,7 +235,8 @@ public function test_delete_directory_recursive(): void $this->assertFalse(is_dir($dir)); } - public function test_delete_directory_non_recursive(): void + #[Test] + public function delete_directory_non_recursive(): void { $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/.*directory not empty.*/'); @@ -163,7 +251,8 @@ public function test_delete_directory_non_recursive(): void Filesystem\delete_directory($dir, recursive: false); } - public function test_detele_file(): void + #[Test] + public function detele_file(): void { $file = $this->fixtures . '/file.txt'; @@ -174,7 +263,8 @@ public function test_detele_file(): void $this->assertFalse(is_file($file)); } - public function test_detele_file_not_found(): void + #[Test] + public function detele_file_not_found(): void { $this->expectException(PathWasNotFound::class); @@ -183,7 +273,8 @@ public function test_detele_file_not_found(): void Filesystem\delete_file($file); } - public function test_detele_file_on_dir(): void + #[Test] + public function detele_file_on_dir(): void { $this->expectException(PathWasNotAFile::class); @@ -193,7 +284,8 @@ public function test_detele_file_on_dir(): void Filesystem\delete_file($dir); } - public function test_get_permissions(): void + #[Test] + public function get_permissions(): void { $file = $this->fixtures . '/file.txt'; @@ -204,14 +296,16 @@ public function test_get_permissions(): void $this->assertEquals(0o644, $permissions & 0o777); } - public function test_get_permissions_not_found(): void + #[Test] + public function get_permissions_not_found(): void { $this->expectException(PathWasNotFound::class); Filesystem\get_permissions($this->fixtures . '/file.txt'); } - public function test_ensure_directory_empty(): void + #[Test] + public function ensure_directory_empty(): void { $dir = $this->fixtures . '/tmp'; @@ -224,7 +318,8 @@ public function test_ensure_directory_empty(): void $this->assertTrue(is_dir($dir)); } - public function test_ensure_directory_empty_on_file(): void + #[Test] + public function ensure_directory_empty_on_file(): void { $this->expectException(PathWasNotADirectory::class); @@ -235,7 +330,8 @@ public function test_ensure_directory_empty_on_file(): void Filesystem\ensure_directory_empty($file); } - public function test_ensure_directory_empty_keeps_permissions(): void + #[Test] + public function ensure_directory_empty_keeps_permissions(): void { $dir = $this->fixtures . '/tmp'; @@ -249,7 +345,8 @@ public function test_ensure_directory_empty_keeps_permissions(): void $this->assertEquals(0o755, $permissions & 0o777); } - public function test_is_file(): void + #[Test] + public function is_file(): void { $file = $this->fixtures . '/file.txt'; @@ -259,7 +356,8 @@ public function test_is_file(): void $this->assertFalse(Filesystem\is_file($this->fixtures)); } - public function test_is_directory(): void + #[Test] + public function is_directory(): void { $dir = $this->fixtures . '/tmp'; @@ -269,7 +367,8 @@ public function test_is_directory(): void $this->assertFalse(Filesystem\is_directory($this->fixtures . '/file.txt')); } - public function test_is_readable(): void + #[Test] + public function is_readable(): void { $file = $this->fixtures . '/file.txt'; @@ -279,7 +378,8 @@ public function test_is_readable(): void $this->assertTrue(Filesystem\is_readable($this->fixtures)); } - public function test_is_symbolic_link(): void + #[Test] + public function is_symbolic_link(): void { $file = $this->fixtures . '/file.txt'; $link = $this->fixtures . '/link.txt'; @@ -291,7 +391,8 @@ public function test_is_symbolic_link(): void $this->assertFalse(Filesystem\is_symbolic_link($file)); } - public function test_is_writable(): void + #[Test] + public function is_writable(): void { $file = $this->fixtures . '/file.txt'; @@ -301,7 +402,8 @@ public function test_is_writable(): void $this->assertTrue(Filesystem\is_writable($this->fixtures)); } - public function test_list_directory(): void + #[Test] + public function list_directory(): void { $dir = $this->fixtures . '/tmp'; @@ -320,7 +422,8 @@ public function test_list_directory(): void } } - public function test_list_directory_on_non_directory(): void + #[Test] + public function list_directory_on_non_directory(): void { $this->expectException(PathWasNotADirectory::class); @@ -331,7 +434,8 @@ public function test_list_directory_on_non_directory(): void Filesystem\list_directory($file); } - public function test_read_symbolic_link(): void + #[Test] + public function read_symbolic_link(): void { $file = $this->fixtures . '/file.txt'; $link = $this->fixtures . '/link.txt'; @@ -344,7 +448,8 @@ public function test_read_symbolic_link(): void $this->assertEquals(realpath($file), $target); } - public function test_read_symbolic_link_on_non_symlink(): void + #[Test] + public function read_symbolic_link_on_non_symlink(): void { $this->expectException(PathWasNotASymbolicLink::class); @@ -355,7 +460,8 @@ public function test_read_symbolic_link_on_non_symlink(): void Filesystem\read_symbolic_link($file); } - public function test_get_directory(): void + #[Test] + public function get_directory(): void { $file = $this->fixtures . '/file.txt'; @@ -366,7 +472,8 @@ public function test_get_directory(): void $this->assertEquals(realpath($this->fixtures), realpath($directory)); } - public function test_copy(): void + #[Test] + public function copy(): void { $source = $this->fixtures . '/file.txt'; $destination = $this->fixtures . '/tmp/file.txt'; @@ -378,20 +485,43 @@ public function test_copy(): void $this->assertTrue(is_file($destination)); } - public function test_copy_directory(): void + #[Test] + public function copy_delegates_to_copy_file(): void { - $this->expectException(PathWasNotAFile::class); + $source = $this->fixtures . '/file.txt'; + $destination = $this->fixtures . '/file_copy.txt'; - $source = $this->fixtures . '/tmp'; - $destination = $this->fixtures . '/tmp2'; + file_put_contents($source, 'Hello'); + + Filesystem\copy($source, $destination); + + $this->assertTrue(is_file($destination)); + $this->assertEquals('Hello', file_get_contents($destination)); + } + + #[Test] + public function copy_delegates_to_copy_directory(): void + { + $source = $this->fixtures . '/source'; + $destination = $this->fixtures . '/destination'; mkdir($source); - file_put_contents($source . '/file.txt', ''); + file_put_contents($source . '/file.txt', 'Hello'); + mkdir($source . '/subdir'); + file_put_contents($source . '/subdir/nested.txt', 'World'); - Filesystem\copy_file($source, $destination); + Filesystem\copy($source, $destination); + + $this->assertTrue(is_dir($destination)); + $this->assertTrue(is_file($destination . '/file.txt')); + $this->assertEquals('Hello', file_get_contents($destination . '/file.txt')); + $this->assertTrue(is_dir($destination . '/subdir')); + $this->assertTrue(is_file($destination . '/subdir/nested.txt')); + $this->assertEquals('World', file_get_contents($destination . '/subdir/nested.txt')); } - public function test_copy_non_existing_file(): void + #[Test] + public function copy_non_existing_file(): void { $this->expectException(PathWasNotFound::class); @@ -401,7 +531,8 @@ public function test_copy_non_existing_file(): void Filesystem\copy_file($source, $destination); } - public function test_copy_non_readable_file(): void + #[Test] + public function copy_non_readable_file(): void { $this->expectException(PathWasNotReadable::class); @@ -414,7 +545,8 @@ public function test_copy_non_readable_file(): void Filesystem\copy_file($source, $destination); } - public function test_copy_overwrite(): void + #[Test] + public function copy_overwrite(): void { $source = $this->fixtures . '/file.txt'; $destination = $this->fixtures . '/file2.txt'; @@ -427,7 +559,114 @@ public function test_copy_overwrite(): void $this->assertEquals('Hello', file_get_contents($destination)); } - public function test_move(): void + #[Test] + public function copy_directory(): void + { + $source = $this->fixtures . '/source'; + $destination = $this->fixtures . '/destination'; + + mkdir($source); + file_put_contents($source . '/file.txt', 'Hello'); + mkdir($source . '/subdir'); + file_put_contents($source . '/subdir/nested.txt', 'World'); + + Filesystem\copy_directory($source, $destination); + + $this->assertTrue(is_dir($destination)); + $this->assertTrue(is_file($destination . '/file.txt')); + $this->assertEquals('Hello', file_get_contents($destination . '/file.txt')); + $this->assertTrue(is_dir($destination . '/subdir')); + $this->assertTrue(is_file($destination . '/subdir/nested.txt')); + $this->assertEquals('World', file_get_contents($destination . '/subdir/nested.txt')); + } + + #[Test] + public function copy_directory_non_existing(): void + { + $this->expectException(PathWasNotFound::class); + + $source = $this->fixtures . '/non-existing'; + $destination = $this->fixtures . '/destination'; + + Filesystem\copy_directory($source, $destination); + } + + #[Test] + public function copy_directory_file_as_source(): void + { + $this->expectException(PathWasNotADirectory::class); + + $source = $this->fixtures . '/file.txt'; + $destination = $this->fixtures . '/destination'; + + file_put_contents($source, ''); + + Filesystem\copy_directory($source, $destination); + } + + #[Test] + public function copy_directory_non_readable(): void + { + $this->expectException(PathWasNotReadable::class); + + $source = $this->fixtures . '/source'; + $destination = $this->fixtures . '/destination'; + + mkdir($source); + chmod($source, 0o000); + + Filesystem\copy_directory($source, $destination); + } + + #[Test] + public function copy_directory_no_overwrite(): void + { + $source = $this->fixtures . '/source'; + $destination = $this->fixtures . '/destination'; + + mkdir($source); + file_put_contents($source . '/file.txt', 'Hello'); + mkdir($destination); + file_put_contents($destination . '/existing.txt', 'World'); + + Filesystem\copy_directory($source, $destination, overwrite: false); + + $this->assertFalse(is_file($destination . '/file.txt')); + $this->assertTrue(is_file($destination . '/existing.txt')); + } + + #[Test] + public function copy_directory_overwrite(): void + { + $source = $this->fixtures . '/source'; + $destination = $this->fixtures . '/destination'; + + mkdir($source); + file_put_contents($source . '/file.txt', 'New'); + mkdir($destination); + file_put_contents($destination . '/file.txt', 'Old'); + + Filesystem\copy_directory($source, $destination, overwrite: true); + + $this->assertEquals('New', file_get_contents($destination . '/file.txt')); + } + + #[Test] + public function copy_file_throws_when_source_is_directory(): void + { + $this->expectException(PathWasNotAFile::class); + + $source = $this->fixtures . '/tmp'; + $destination = $this->fixtures . '/tmp2'; + + mkdir($source); + file_put_contents($source . '/file.txt', ''); + + Filesystem\copy_file($source, $destination); + } + + #[Test] + public function move(): void { $source = $this->fixtures . '/file.txt'; $destination = $this->fixtures . '/tmp/file.txt'; @@ -440,7 +679,8 @@ public function test_move(): void $this->assertFalse(is_file($source)); } - public function test_move_overwrite(): void + #[Test] + public function move_overwrite(): void { $source = $this->fixtures . '/file.txt'; $destination = $this->fixtures . '/tmp/file.txt'; @@ -454,7 +694,8 @@ public function test_move_overwrite(): void $this->assertFalse(is_file($source)); } - public function test_move_no_overwrite(): void + #[Test] + public function move_no_overwrite(): void { $source = $this->fixtures . '/file.txt'; $destination = $this->fixtures . '/tmp/file.txt'; @@ -468,7 +709,8 @@ public function test_move_no_overwrite(): void $this->assertEquals('World', file_get_contents($destination)); } - public function test_move_directory(): void + #[Test] + public function move_directory(): void { $source = $this->fixtures . '/tmp'; $destination = $this->fixtures . '/tmp2'; @@ -483,7 +725,8 @@ public function test_move_directory(): void $this->assertFalse(is_dir($source)); } - public function test_move_directory_overwrite(): void + #[Test] + public function move_directory_overwrite(): void { $source = $this->fixtures . '/tmp'; $destination = $this->fixtures . '/tmp2'; @@ -500,7 +743,8 @@ public function test_move_directory_overwrite(): void $this->assertFalse(is_dir($source)); } - public function test_move_directory_no_overwrite(): void + #[Test] + public function move_directory_no_overwrite(): void { $source = $this->fixtures . '/tmp'; $destination = $this->fixtures . '/tmp2'; @@ -518,7 +762,8 @@ public function test_move_directory_no_overwrite(): void $this->assertSame('world', file_get_contents($destination . '/file.txt')); } - public function test_move_not_readable(): void + #[Test] + public function move_not_readable(): void { $this->expectException(PathWasNotReadable::class); @@ -531,7 +776,8 @@ public function test_move_not_readable(): void Filesystem\move($source, $destination); } - public function test_rename(): void + #[Test] + public function rename(): void { $source = $this->fixtures . '/file.txt'; $newName = 'renamed-file.txt'; @@ -544,7 +790,8 @@ public function test_rename(): void $this->assertFalse(is_file($source)); } - public function test_rename_overwrite(): void + #[Test] + public function rename_overwrite(): void { $source = $this->fixtures . '/file.txt'; $newName = 'renamed-file.txt'; @@ -558,7 +805,8 @@ public function test_rename_overwrite(): void $this->assertFalse(is_file($source)); } - public function test_rename_with_full_path(): void + #[Test] + public function rename_with_full_path(): void { $this->expectException(NameWasInvalid::class); @@ -573,7 +821,8 @@ public function test_rename_with_full_path(): void $this->assertFalse(is_file($source)); } - public function test_write_file(): void + #[Test] + public function write_file(): void { $file = $this->fixtures . '/tmp/file.txt'; @@ -584,8 +833,9 @@ public function test_write_file(): void #[TestWith([['key' => 'value'], '{"key":"value"}'])] #[TestWith(['basic string', '"basic string"'])] - #[TestWith(['{"foo": "bar"}', '{"foo":"bar"}'])] - public function test_write_json(mixed $data, string $expected): void + #[TestWith(['{"f + #[Test]oo": "bar"}', '{"foo":"bar"}'])] + public function write_json(mixed $data, string $expected): void { $file = $this->fixtures . '/tmp/file.json'; @@ -594,7 +844,8 @@ public function test_write_json(mixed $data, string $expected): void $this->assertEquals($expected, file_get_contents($file)); } - public function test_write_json_serializable(): void + #[Test] + public function write_json_serializable(): void { $file = $this->fixtures . '/tmp/file.json'; @@ -610,7 +861,8 @@ public function jsonSerialize(): array $this->assertEquals('{"key":"value"}', file_get_contents($file)); } - public function test_write_non_writable_file(): void + #[Test] + public function write_non_writable_file(): void { $this->expectException(RuntimeException::class); @@ -622,7 +874,8 @@ public function test_write_non_writable_file(): void Filesystem\write_file($file, 'Hello'); } - public function test_read_json(): void + #[Test] + public function read_json(): void { $file = $this->fixtures . '/tmp/file.json'; @@ -633,7 +886,8 @@ public function test_read_json(): void $this->assertEquals(['key' => 'value'], $data); } - public function test_read_file(): void + #[Test] + public function read_file(): void { $file = $this->fixtures . '/file.txt'; @@ -644,7 +898,8 @@ public function test_read_file(): void $this->assertEquals('Hello', $content); } - public function test_read_file_non_readable_file(): void + #[Test] + public function read_file_non_readable_file(): void { $this->expectException(PathWasNotReadable::class); @@ -656,7 +911,8 @@ public function test_read_file_non_readable_file(): void Filesystem\read_file($file); } - public function test_read_file_not_found(): void + #[Test] + public function read_file_not_found(): void { $this->expectException(PathWasNotFound::class); @@ -665,7 +921,55 @@ public function test_read_file_not_found(): void Filesystem\read_file($file); } - public function test_ensure_directory_exists(): void + #[Test] + public function read_file_locked(): void + { + $file = $this->fixtures . '/file.txt'; + + file_put_contents($file, 'Hello'); + + $content = Filesystem\read_locked_file($file); + + $this->assertEquals('Hello', $content); + } + + #[Test] + public function read_file_locked_with_exclusive_lock(): void + { + $file = $this->fixtures . '/file.txt'; + + file_put_contents($file, 'World'); + + $content = Filesystem\read_locked_file($file, LockType::EXCLUSIVE); + + $this->assertEquals('World', $content); + } + + #[Test] + public function read_file_locked_not_found(): void + { + $this->expectException(PathWasNotFound::class); + + $file = $this->fixtures . '/file.txt'; + + Filesystem\read_locked_file($file); + } + + #[Test] + public function read_file_locked_non_readable(): void + { + $this->expectException(PathWasNotReadable::class); + + $file = $this->fixtures . '/file.txt'; + + file_put_contents($file, 'Hello'); + chmod($file, 0o000); + + Filesystem\read_locked_file($file); + } + + #[Test] + public function ensure_directory_exists(): void { $dir = $this->fixtures . '/tmp'; @@ -674,7 +978,8 @@ public function test_ensure_directory_exists(): void $this->assertTrue(is_dir($dir)); } - public function test_ensure_directory_exists_on_existent_directory(): void + #[Test] + public function ensure_directory_exists_on_existent_directory(): void { $dir = $this->fixtures . '/tmp'; @@ -685,7 +990,8 @@ public function test_ensure_directory_exists_on_existent_directory(): void $this->assertTrue(is_dir($dir)); } - public function test_delete_file_for_invalid_symlink(): void + #[Test] + public function delete_file_for_invalid_symlink(): void { $file = $this->fixtures . '/file.txt'; \file_put_contents($file, 'hello'); @@ -699,7 +1005,8 @@ public function test_delete_file_for_invalid_symlink(): void $this->assertFalse(is_link($link)); } - public function test_normalize_path_in_phar(): void + #[Test] + public function normalize_path_in_phar(): void { if (\Phar::canWrite() === false) { $this->markTestSkipped('phar.readonly is enabled in php.ini.'); diff --git a/packages/upgrade/composer.json b/packages/upgrade/composer.json index 7bebcfc546..81c5431aad 100644 --- a/packages/upgrade/composer.json +++ b/packages/upgrade/composer.json @@ -4,8 +4,8 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", - "rector/rector": "^2.2.5" + "php": "^8.5", + "rector/rector": "^2.3.2" }, "autoload": { "psr-4": { diff --git a/packages/upgrade/config/sets/level/up-to-tempest-30.php b/packages/upgrade/config/sets/level/up-to-tempest-30.php new file mode 100644 index 0000000000..d45ac32b38 --- /dev/null +++ b/packages/upgrade/config/sets/level/up-to-tempest-30.php @@ -0,0 +1,14 @@ +sets([ + TempestSetList::TEMPEST_20, + TempestSetList::TEMPEST_28, + TempestSetList::TEMPEST_30, + ]); +}; diff --git a/packages/upgrade/config/sets/tempest30.php b/packages/upgrade/config/sets/tempest30.php new file mode 100644 index 0000000000..ae7af048d2 --- /dev/null +++ b/packages/upgrade/config/sets/tempest30.php @@ -0,0 +1,29 @@ +rule(UpdateArrMapFunctionRector::class); + $config->rule(UpdateCommandFunctionImportsRector::class); + $config->rule(UpdateContainerFunctionImportsRector::class); + $config->rule(UpdateEventFunctionImportsRector::class); + $config->rule(UpdateMapperFunctionImportsRector::class); + $config->rule(UpdateReflectionFunctionImportsRector::class); + $config->rule(UpdateViewFunctionImportsRector::class); + $config->rule(UpdateExceptionProcessorRector::class); + $config->rule(UpdateHasContextRector::class); +}; diff --git a/packages/upgrade/src/Set/TempestLevelSetList.php b/packages/upgrade/src/Set/TempestLevelSetList.php index 0afa4288b0..859b32034c 100644 --- a/packages/upgrade/src/Set/TempestLevelSetList.php +++ b/packages/upgrade/src/Set/TempestLevelSetList.php @@ -8,4 +8,5 @@ final class TempestLevelSetList { public const string UP_TO_TEMPEST_20 = __DIR__ . '/../../config/sets/level/up-to-tempest-20.php'; public const string UP_TO_TEMPEST_28 = __DIR__ . '/../../config/sets/level/up-to-tempest-28.php'; + public const string UP_TO_TEMPEST_30 = __DIR__ . '/../../config/sets/level/up-to-tempest-30.php'; } diff --git a/packages/upgrade/src/Set/TempestSetList.php b/packages/upgrade/src/Set/TempestSetList.php index 4769ce6b04..8c4bcaa293 100644 --- a/packages/upgrade/src/Set/TempestSetList.php +++ b/packages/upgrade/src/Set/TempestSetList.php @@ -8,4 +8,5 @@ final class TempestSetList { public const string TEMPEST_20 = __DIR__ . '/../../config/sets/tempest20.php'; public const string TEMPEST_28 = __DIR__ . '/../../config/sets/tempest28.php'; + public const string TEMPEST_30 = __DIR__ . '/../../config/sets/tempest30.php'; } diff --git a/packages/upgrade/src/Tempest3/UpdateArrMapFunctionRector.php b/packages/upgrade/src/Tempest3/UpdateArrMapFunctionRector.php new file mode 100644 index 0000000000..1433c62e41 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateArrMapFunctionRector.php @@ -0,0 +1,40 @@ +name->toString() === 'Tempest\Support\Arr\map_iterable') { + $node->name = new Node\Name('Tempest\Support\Arr\map'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\Support\Arr\map_iterable') { + $node->name = new Node\Name\FullyQualified('Tempest\Support\Arr\map'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateCommandFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateCommandFunctionImportsRector.php new file mode 100644 index 0000000000..9aad37e1a0 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateCommandFunctionImportsRector.php @@ -0,0 +1,40 @@ +name->toString() === 'Tempest\command') { + $node->name = new Node\Name('Tempest\CommandBus\command'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\command') { + $node->name = new Node\Name\FullyQualified('Tempest\CommandBus\command'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateContainerFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateContainerFunctionImportsRector.php new file mode 100644 index 0000000000..106193e620 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateContainerFunctionImportsRector.php @@ -0,0 +1,50 @@ +name->toString() === 'Tempest\get') { + $node->name = new Node\Name('Tempest\Container\get'); + } + + if ($node->name->toString() === 'Tempest\invoke') { + $node->name = new Node\Name('Tempest\Container\invoke'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\get') { + $node->name = new Node\Name\FullyQualified('Tempest\Container\get'); + + return null; + } + + if ($functionName === 'Tempest\invoke') { + $node->name = new Node\Name\FullyQualified('Tempest\Container\invoke'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateEventFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateEventFunctionImportsRector.php new file mode 100644 index 0000000000..30962f6b53 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateEventFunctionImportsRector.php @@ -0,0 +1,50 @@ +name->toString() === 'Tempest\event') { + $node->name = new Node\Name('Tempest\EventBus\event'); + } + + if ($node->name->toString() === 'Tempest\listen') { + $node->name = new Node\Name('Tempest\EventBus\listen'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\event') { + $node->name = new Node\Name\FullyQualified('Tempest\EventBus\event'); + + return null; + } + + if ($functionName === 'Tempest\listen') { + $node->name = new Node\Name\FullyQualified('Tempest\EventBus\listen'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateExceptionProcessorRector.php b/packages/upgrade/src/Tempest3/UpdateExceptionProcessorRector.php new file mode 100644 index 0000000000..26bc1f9ab3 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateExceptionProcessorRector.php @@ -0,0 +1,61 @@ +name->toString(); + + if ($name === 'Tempest\Core\ExceptionProcessor' || $name === 'ExceptionProcessor') { + $node->name = new Node\Name('Tempest\Core\Exceptions\ExceptionReporter'); + } + + return; + } + + if (! $node instanceof Node\Stmt\Class_) { + return; + } + + $implements = $node->implements; + + $implementsExceptionProcessor = array_find_key( + array: $implements, + callback: static fn (Node\Name $name) => $name->toString() === 'Tempest\Core\ExceptionProcessor' || $name->toString() === 'ExceptionProcessor', + ); + + if ($implementsExceptionProcessor === null) { + return; + } + + $implements[$implementsExceptionProcessor] = new Node\Name('\Tempest\Core\Exceptions\ExceptionReporter'); + $node->implements = $implements; + + foreach ($node->stmts as $statement) { + if (! $statement instanceof ClassMethod) { + continue; + } + + if ($statement->name->toString() === 'process') { + $statement->name = new Node\Identifier('report'); + break; + } + } + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateHasContextRector.php b/packages/upgrade/src/Tempest3/UpdateHasContextRector.php new file mode 100644 index 0000000000..2547057a01 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateHasContextRector.php @@ -0,0 +1,48 @@ +name->toString(); + + if ($name === 'Tempest\Core\HasContext' || $name === 'HasContext') { + $node->name = new Node\Name('Tempest\Core\ProvidesContext'); + } + + return; + } + + if (! $node instanceof Node\Stmt\Class_) { + return; + } + + $implements = $node->implements; + + $implementsHasContext = array_find_key( + array: $implements, + callback: static fn (Node\Name $name) => $name->toString() === 'Tempest\Core\HasContext' || $name->toString() === 'HasContext', + ); + + if ($implementsHasContext === null) { + return; + } + + $implements[$implementsHasContext] = new Node\Name('\Tempest\Core\ProvidesContext'); + $node->implements = $implements; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateMapperFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateMapperFunctionImportsRector.php new file mode 100644 index 0000000000..310bc19295 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateMapperFunctionImportsRector.php @@ -0,0 +1,50 @@ +name->toString() === 'Tempest\map') { + $node->name = new Node\Name('Tempest\Mapper\map'); + } + + if ($node->name->toString() === 'Tempest\make') { + $node->name = new Node\Name('Tempest\Mapper\make'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\map') { + $node->name = new Node\Name\FullyQualified('Tempest\Mapper\map'); + + return null; + } + + if ($functionName === 'Tempest\make') { + $node->name = new Node\Name\FullyQualified('Tempest\Mapper\make'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateReflectionFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateReflectionFunctionImportsRector.php new file mode 100644 index 0000000000..1d12c45eac --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateReflectionFunctionImportsRector.php @@ -0,0 +1,40 @@ +name->toString() === 'Tempest\reflect') { + $node->name = new Node\Name('Tempest\Reflection\reflect'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\reflect') { + $node->name = new Node\Name\FullyQualified('Tempest\Reflection\reflect'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest3/UpdateViewFunctionImportsRector.php b/packages/upgrade/src/Tempest3/UpdateViewFunctionImportsRector.php new file mode 100644 index 0000000000..fdef676f55 --- /dev/null +++ b/packages/upgrade/src/Tempest3/UpdateViewFunctionImportsRector.php @@ -0,0 +1,40 @@ +name->toString() === 'Tempest\view') { + $node->name = new Node\Name('Tempest\View\view'); + } + + return null; + } + + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + $functionName = $node->name->toString(); + + if ($functionName === 'Tempest\view') { + $node->name = new Node\Name\FullyQualified('Tempest\View\view'); + + return null; + } + } + + return null; + } +} diff --git a/packages/upgrade/tests/RectorTester.php b/packages/upgrade/tests/RectorTester.php index b4925df5d0..1bb8fecbfc 100644 --- a/packages/upgrade/tests/RectorTester.php +++ b/packages/upgrade/tests/RectorTester.php @@ -96,4 +96,12 @@ private function cleanDiff(string $diff): string return trim($diff); } + + /** + * @mago-expect lint:no-debug-symbols + */ + public function dd(): never + { + dd($this->actual); + } } diff --git a/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorFullyQualified.input.php b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorFullyQualified.input.php new file mode 100644 index 0000000000..d9f28ae69c --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorFullyQualified.input.php @@ -0,0 +1,11 @@ +getMessage()); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorImportedOnly.input.php b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorImportedOnly.input.php new file mode 100644 index 0000000000..2a43fedb33 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorImportedOnly.input.php @@ -0,0 +1,13 @@ +getMessage(); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorWithConstructor.input.php b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorWithConstructor.input.php new file mode 100644 index 0000000000..318a35a33e --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/ExceptionProcessorWithConstructor.input.php @@ -0,0 +1,17 @@ +logger->error($exception->getMessage(), [ + 'exception' => $exception, + ]); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMakeCall.input.php b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMakeCall.input.php new file mode 100644 index 0000000000..60071e9f76 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMakeCall.input.php @@ -0,0 +1,9 @@ +from(['name' => 'Jon Doe']); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapCall.input.php b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapCall.input.php new file mode 100644 index 0000000000..2087237ae5 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapCall.input.php @@ -0,0 +1,9 @@ +to(Author::class); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapIterableCall.input.php b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapIterableCall.input.php new file mode 100644 index 0000000000..f563b7bf34 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedMapIterableCall.input.php @@ -0,0 +1,11 @@ + $item * 2); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedViewCall.input.php b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedViewCall.input.php new file mode 100644 index 0000000000..c034b8c28a --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/FullyQualifiedViewCall.input.php @@ -0,0 +1,9 @@ + 'John Doe', + 'role' => 'admin', + ]; + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/MakeNamespaceChange.input.php b/packages/upgrade/tests/Tempest3/Fixtures/MakeNamespaceChange.input.php new file mode 100644 index 0000000000..f90744f5eb --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/MakeNamespaceChange.input.php @@ -0,0 +1,11 @@ +from(['name' => 'Jon Doe']); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/MapIterableNamespaceChange.input.php b/packages/upgrade/tests/Tempest3/Fixtures/MapIterableNamespaceChange.input.php new file mode 100644 index 0000000000..2027726c3e --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/MapIterableNamespaceChange.input.php @@ -0,0 +1,11 @@ + $item * 2); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/MapNamespaceChange.input.php b/packages/upgrade/tests/Tempest3/Fixtures/MapNamespaceChange.input.php new file mode 100644 index 0000000000..8ed91be7f4 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/MapNamespaceChange.input.php @@ -0,0 +1,11 @@ +to(Author::class); + } +} diff --git a/packages/upgrade/tests/Tempest3/Fixtures/ViewNamespaceChange.input.php b/packages/upgrade/tests/Tempest3/Fixtures/ViewNamespaceChange.input.php new file mode 100644 index 0000000000..b848c64963 --- /dev/null +++ b/packages/upgrade/tests/Tempest3/Fixtures/ViewNamespaceChange.input.php @@ -0,0 +1,11 @@ + new RectorTester(__DIR__ . '/tempest30_rector.php'); + } + + public function test_map_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/MapNamespaceChange.input.php') + ->assertContains('use function Tempest\Mapper\map;') + ->assertNotContains('use function Tempest\map;'); + } + + public function test_make_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/MakeNamespaceChange.input.php') + ->assertContains('use function Tempest\Mapper\make;') + ->assertNotContains('use function Tempest\make;'); + } + + public function test_fully_qualified_map_call(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/FullyQualifiedMapCall.input.php') + ->assertContains('use Tempest\Mapper\map;') + ->assertContains('return map($data)->to(Author::class);'); + } + + public function test_fully_qualified_make_call(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/FullyQualifiedMakeCall.input.php') + ->assertContains('use Tempest\Mapper\make;') + ->assertContains('return make(Author::class)'); + } + + public function test_exception_processor_to_exception_reporter_fully_qualified(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ExceptionProcessorFullyQualified.input.php') + ->assertContains('use Tempest\Core\Exceptions\ExceptionReporter;') + ->assertContains('implements \Tempest\Core\Exceptions\ExceptionReporter') + ->assertContains('public function report(') + ->assertNotContains('use Tempest\Core\ExceptionProcessor') + ->assertNotContains('public function process('); + } + + public function test_exception_processor_to_exception_reporter_with_constructor(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ExceptionProcessorWithConstructor.input.php') + ->assertContains('use Tempest\Core\Exceptions\ExceptionReporter;') + ->assertContains('implements \Tempest\Core\Exceptions\ExceptionReporter') + ->assertContains('public function report(') + ->assertNotContains('use Tempest\Core\ExceptionProcessor') + ->assertNotContains('public function process('); + } + + public function test_exception_processor_to_exception_reporter_imported_only(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ExceptionProcessorImportedOnly.input.php') + ->assertContains('use Tempest\Core\Exceptions\ExceptionReporter;') + ->assertContains('implements \Tempest\Core\Exceptions\ExceptionReporter') + ->assertContains('public function report(') + ->assertNotContains('use ExceptionProcessor') + ->assertNotContains('public function process('); + } + + public function test_has_context_to_provides_context_fully_qualified(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/HasContextFullyQualified.input.php') + ->assertContains('use Tempest\Core\ProvidesContext;') + ->assertContains('implements \Tempest\Core\ProvidesContext') + ->assertNotContains('use Tempest\Core\HasContext'); + } + + public function test_view_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ViewNamespaceChange.input.php') + ->assertContains('use function Tempest\View\view;') + ->assertNotContains('use function Tempest\view;'); + } + + public function test_fully_qualified_view_call(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/FullyQualifiedViewCall.input.php') + ->assertContains('use Tempest\View\view;') + ->assertContains('return view($template);'); + } + + public function test_map_iterable_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/MapIterableNamespaceChange.input.php') + ->assertContains('use function Tempest\Support\Arr\map;') + ->assertNotContains('use function Tempest\Support\Arr\map_iterable;'); + } + + public function test_fully_qualified_map_iterable_call(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/FullyQualifiedMapIterableCall.input.php') + ->assertContains('use Tempest\Support\Arr\map;') + ->assertContains('return map($data, fn ($item) => $item * 2);'); + } +} diff --git a/packages/upgrade/tests/Tempest3/tempest30_rector.php b/packages/upgrade/tests/Tempest3/tempest30_rector.php new file mode 100644 index 0000000000..70fb86f30e --- /dev/null +++ b/packages/upgrade/tests/Tempest3/tempest30_rector.php @@ -0,0 +1,9 @@ +withSets([TempestSetList::TEMPEST_30]); diff --git a/packages/validation/composer.json b/packages/validation/composer.json index a04f34d076..5f0a5f2f4f 100644 --- a/packages/validation/composer.json +++ b/packages/validation/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "egulias/email-validator": "^4.0.2", "giggsey/libphonenumber-for-php-lite": "^9.0", "tempest/reflection": "dev-main", diff --git a/packages/validation/src/Exceptions/PropertyValidationFailed.php b/packages/validation/src/Exceptions/PropertyValidationFailed.php deleted file mode 100644 index b709099861..0000000000 --- a/packages/validation/src/Exceptions/PropertyValidationFailed.php +++ /dev/null @@ -1,35 +0,0 @@ -failingRules as $key => $failingRulesForProperty) { - foreach ($failingRulesForProperty as $failingRule) { - $messages[$key][] = arr($failingRule->getTranslationVariables())->join()->toString(); - } - } - - parent::__construct($this->property->getClass()->getName() . '::' . $this->property->getName() . PHP_EOL . Json\encode($messages, pretty: true)); - - parent::__construct($this->property->getName()); - } -} diff --git a/packages/validation/src/Exceptions/TranslatorWasRequired.php b/packages/validation/src/Exceptions/TranslatorWasRequired.php new file mode 100644 index 0000000000..0a5acc6343 --- /dev/null +++ b/packages/validation/src/Exceptions/TranslatorWasRequired.php @@ -0,0 +1,15 @@ + $failingRules + * @param array $failingRules * @param array $errorMessages * @param class-string|null $targetClass */ public function __construct( - public readonly array $failingRules, - public readonly null|object|string $subject = null, - public readonly array $errorMessages = [], - public readonly ?string $targetClass = null, + private(set) array $failingRules, + private(set) null|object|string $subject = null, + private(set) array $errorMessages = [], + private(set) ?string $targetClass = null, ) { parent::__construct(match (true) { is_null($subject) => 'Validation failed.', diff --git a/packages/validation/src/Exceptions/ValueWasInvalid.php b/packages/validation/src/Exceptions/ValueWasInvalid.php deleted file mode 100644 index f54fb95823..0000000000 --- a/packages/validation/src/Exceptions/ValueWasInvalid.php +++ /dev/null @@ -1,16 +0,0 @@ -rule, + field: $field, + value: $this->value, + key: $this->key, + ); + } + + /** + * @param null|string $key An optional key associated with the value, used for localization. + */ + public function withKey(?string $key): self + { + return new self( + rule: $this->rule, + field: $this->field, + value: $this->value, + key: $key, + ); + } + + /** + * @param null|mixed $value The value that was validated and caused the failure. + */ + public function withValue(mixed $value): self + { + return new self( + rule: $this->rule, + field: $this->field, + value: $value, + key: $this->key, + ); + } +} diff --git a/packages/validation/src/Rules/ValidateWith.php b/packages/validation/src/Rules/ValidateWith.php new file mode 100644 index 0000000000..28a1c9ff4e --- /dev/null +++ b/packages/validation/src/Rules/ValidateWith.php @@ -0,0 +1,45 @@ +callback = $callback; + + $reflection = new ReflectionFunction($callback); + + // Must be static + if (! $reflection->isStatic()) { + throw new InvalidArgumentException('Validation closures must be static'); + } + + // Must not capture variables + if ($reflection->getStaticVariables() !== []) { + throw new InvalidArgumentException('Validation closures may not capture variables.'); + } + } + + public function isValid(mixed $value): bool + { + return ($this->callback)($value); + } +} diff --git a/packages/validation/src/TranslationKey.php b/packages/validation/src/TranslationKey.php new file mode 100644 index 0000000000..5d49c00ff2 --- /dev/null +++ b/packages/validation/src/TranslationKey.php @@ -0,0 +1,13 @@ + $failingRules + * @param array $failingRules * @param class-string|null $targetClass */ public function createValidationFailureException(array $failingRules, null|object|string $subject = null, ?string $targetClass = null): ValidationFailed { return new ValidationFailed( - $failingRules, - $subject, - Arr\map_iterable($failingRules, function (array $rules, string $field) { - return Arr\map_iterable($rules, fn (Rule $rule) => $this->getErrorMessage($rule, $field)); + failingRules: $failingRules, + subject: $subject, + errorMessages: Arr\map($failingRules, function (array $rules, string $field) { + return Arr\map($rules, fn (FailingRule $rule) => $this->getErrorMessage($rule, $field)); }), - $targetClass, + targetClass: $targetClass, ); } @@ -74,7 +75,7 @@ public function createValidationFailureException(array $failingRules, null|objec * Validates the specified `$values` for the corresponding public properties on the specified `$class`, using built-in PHP types and attribute rules. * * @param ClassReflector|class-string $class - * @return Rule[] + * @return array */ public function validateValuesForClass(ClassReflector|string $class, ?array $values, string $prefix = ''): array { @@ -125,7 +126,7 @@ class: $property->getType()->asClass(), /** * Validates `$value` against the specified `$property`, using built-in PHP types and attribute rules. * - * @return Rule[] + * @return FailingRule[] */ public function validateValueForProperty(PropertyReflector $property, mixed $value): array { @@ -148,14 +149,19 @@ public function validateValueForProperty(PropertyReflector $property, mixed $val $rules[] = new IsEnum(enum: $property->getType()->getName(), orNull: $property->isNullable()); } - return $this->validateValue($value, $rules); + $key = $property->getAttribute(TranslationKey::class)?->key; + + return Arr\map( + array: $this->validateValue($value, $rules), + map: fn (FailingRule $rule) => $rule->withKey($key), + ); } /** * Validates the specified `$value` against the specified set of `$rules`. If a rule is a closure, it may return a string as a validation error. * * @param Rule|array|(Closure(mixed $value):string|false) $rules - * @return Rule[] + * @return FailingRule[] */ public function validateValue(mixed $value, Closure|Rule|array $rules): array { @@ -169,7 +175,7 @@ public function validateValue(mixed $value, Closure|Rule|array $rules): array $rule = $this->convertToRule($rule, $value); if (! $rule->isValid($value)) { - $failingRules[] = $rule; + $failingRules[] = new FailingRule($rule, value: $value); } } @@ -206,21 +212,22 @@ public function validateValues(iterable $values, array $rules): array /** * Gets a localized validation error message for the specified rule. */ - public function getErrorMessage(Rule $rule, ?string $field = null): string + public function getErrorMessage(Rule|FailingRule $rule, ?string $field = null): string { + if (is_null($this->translator)) { + throw new TranslatorWasRequired(); + } + if ($rule instanceof HasErrorMessage) { return $rule->getErrorMessage(); } - $ruleTranslationKey = str($rule::class) - ->classBasename() - ->snake() - ->replaceEvery([ // those are snake case issues that we manually fix for consistency - 'i_pv6' => 'ipv6', - 'i_pv4' => 'ipv4', - 'reg_ex' => 'regex', - ]) - ->toString(); + $ruleTranslationKey = $this->getTranslationKey($rule); + + if ($rule instanceof FailingRule) { + $field ??= $rule->field; + $rule = $rule->rule; + } $variables = [ 'field' => $this->getFieldName($ruleTranslationKey, $field), @@ -233,9 +240,33 @@ public function getErrorMessage(Rule $rule, ?string $field = null): string return $this->translator->translate("validation_error.{$ruleTranslationKey}", ...$variables); } + private function getTranslationKey(Rule|FailingRule $rule): string + { + $key = ''; + + if ($rule instanceof FailingRule && $rule->key) { + $key .= $rule->key; + } + + if ($rule instanceof FailingRule) { + $rule = $rule->rule; + } + + return str($rule::class) + ->classBasename() + ->snake() + ->replaceEvery([ // those are snake case issues that we manually fix for consistency + 'i_pv6' => 'ipv6', + 'i_pv4' => 'ipv4', + 'reg_ex' => 'regex', + ]) + ->when($key !== '', fn ($s) => $s->append('.', $key)) + ->toString(); + } + private function getFieldName(string $key, ?string $field = null): string { - $translatedField = $this->translator->translate("validation_field.{$key}"); + $translatedField = $this->translator?->translate("validation_field.{$key}"); if ($translatedField === "validation_field.{$key}") { return $field ?? 'Value'; diff --git a/packages/validation/tests/Fixtures/ValidateWithObject.php b/packages/validation/tests/Fixtures/ValidateWithObject.php new file mode 100644 index 0000000000..3b21125d9f --- /dev/null +++ b/packages/validation/tests/Fixtures/ValidateWithObject.php @@ -0,0 +1,15 @@ +getAttributes(ValidateWith::class); + + $this->assertCount(1, $attributes); + + $rule = $attributes[0]->newInstance(); + $this->assertTrue($rule->isValid('user@example')); + $this->assertFalse($rule->isValid('invalid-prop')); + } + + public function test_closure_validation_passes(): void + { + $rule = new ValidateWith(static fn (mixed $value): bool => str_contains((string) $value, '@')); + $this->assertTrue($rule->isValid('user@example.com')); + $this->assertTrue($rule->isValid('test@domain.org')); + } + + public function test_closure_validation_fails(): void + { + $rule = new ValidateWith(static fn (mixed $value): bool => str_contains((string) $value, '@')); + + $this->assertFalse($rule->isValid('username')); + $this->assertFalse($rule->isValid('example.com')); + } + + public function test_non_string_value_fails(): void + { + $rule = new ValidateWith(static fn (mixed $value): bool => str_contains((string) $value, '@')); + + $this->assertFalse($rule->isValid(12345)); + $this->assertFalse($rule->isValid(null)); + $this->assertFalse($rule->isValid(false)); + } + + public function test_static_closure_required(): void + { + $this->expectException(\InvalidArgumentException::class); + + new ValidateWith(fn (mixed $value): bool => str_contains((string) $value, '@')); + } +} diff --git a/packages/validation/tests/ValidatorTest.php b/packages/validation/tests/ValidatorTest.php index 1c460e985f..8b47f9117b 100644 --- a/packages/validation/tests/ValidatorTest.php +++ b/packages/validation/tests/ValidatorTest.php @@ -64,7 +64,7 @@ public function test_closure_fails_with_string_response(): void }); $this->assertCount(1, $failingRules); - $this->assertSame('I expected b', $failingRules[0]->message); + $this->assertSame('I expected b', $failingRules[0]->rule->message); } public function test_closure_passes_with_null_response(): void @@ -115,8 +115,8 @@ public function test_nested_property_validation(): void $failingRules = $validator->validateValuesForClass($class, []); $this->assertCount(7, $failingRules); - $this->assertInstanceOf(IsNotNull::class, $failingRules['b'][0]); - $this->assertInstanceOf(IsString::class, $failingRules['title'][0]); + $this->assertInstanceOf(IsNotNull::class, $failingRules['b'][0]->rule); + $this->assertInstanceOf(IsString::class, $failingRules['title'][0]->rule); $failingRules = $validator->validateValuesForClass($class, [ 'b' => [ @@ -183,7 +183,7 @@ public function test_validation_infers_string_rule_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithStringProperty::class, ['prop' => (object) []]); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsString::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsString::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_int_rule_from_property_type(): void @@ -191,7 +191,7 @@ public function test_validation_infers_int_rule_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithIntProp::class, ['prop' => 'a']); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsInteger::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsInteger::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_float_rule_from_property_type(): void @@ -199,7 +199,7 @@ public function test_validation_infers_float_rule_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithFloatProp::class, ['prop' => 'a']); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsFloat::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsFloat::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_bool_rule_from_property_type(): void @@ -207,7 +207,7 @@ public function test_validation_infers_bool_rule_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithBoolProp::class, ['prop' => 'a']); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsBoolean::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsBoolean::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_enum_rule_from_property_type(): void @@ -215,7 +215,7 @@ public function test_validation_infers_enum_rule_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithEnumProp::class, ['prop' => 'a']); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsEnum::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsEnum::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_not_null_from_scalar_property_type(): void @@ -223,7 +223,7 @@ public function test_validation_infers_not_null_from_scalar_property_type(): voi $failingRules = $this->validator->validateValuesForClass(ObjectWithStringProperty::class, ['prop' => null]); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsString::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsString::class, $failingRules['prop'][0]->rule); } public function test_validation_infers_not_null_from_property_type(): void @@ -231,7 +231,7 @@ public function test_validation_infers_not_null_from_property_type(): void $failingRules = $this->validator->validateValuesForClass(ObjectWithObjectProperty::class, ['prop' => null]); $this->assertCount(1, $failingRules['prop']); - $this->assertInstanceOf(IsNotNull::class, $failingRules['prop'][0]); + $this->assertInstanceOf(IsNotNull::class, $failingRules['prop'][0]->rule); } public function test_skip_validation_attribute(): void @@ -257,7 +257,7 @@ public function test_validate_values_some_invalid(): void ); $this->assertCount(1, $failingRules); - $this->assertInstanceOf(IsEmail::class, $failingRules['email'][0]); + $this->assertInstanceOf(IsEmail::class, $failingRules['email'][0]->rule); } public function test_validate_values_all_valid(): void diff --git a/packages/view/composer.json b/packages/view/composer.json index e43cbe96f8..342e8ed26e 100644 --- a/packages/view/composer.json +++ b/packages/view/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4", + "php": "^8.5", "tempest/core": "dev-main", "tempest/container": "dev-main", "tempest/validation": "dev-main", diff --git a/packages/view/src/Commands/MakeViewCommand.php b/packages/view/src/Commands/MakeViewCommand.php index fe0e204e67..ecb515cf66 100644 --- a/packages/view/src/Commands/MakeViewCommand.php +++ b/packages/view/src/Commands/MakeViewCommand.php @@ -8,10 +8,10 @@ use Tempest\Console\ConsoleArgument; use Tempest\Console\ConsoleCommand; use Tempest\Core\PublishesFiles; -use Tempest\Generation\DataObjects\StubFile; -use Tempest\Generation\Enums\StubFileType; -use Tempest\Generation\Exceptions\FileGenerationFailedException; -use Tempest\Generation\Exceptions\FileGenerationWasAborted; +use Tempest\Generation\Php\DataObjects\StubFile; +use Tempest\Generation\Php\Exceptions\FileGenerationFailedException; +use Tempest\Generation\Php\Exceptions\FileGenerationWasAborted; +use Tempest\Generation\Php\StubFileType; use Tempest\View\Enums\ViewType; use Tempest\View\Stubs\ViewStub; diff --git a/packages/view/src/Components/x-component.view.php b/packages/view/src/Components/x-component.view.php index 2102c6fdae..fdf0baa252 100644 --- a/packages/view/src/Components/x-component.view.php +++ b/packages/view/src/Components/x-component.view.php @@ -7,8 +7,8 @@ use Tempest\View\Renderers\TempestViewRenderer; use Tempest\View\Slot; -use function Tempest\get; -use function Tempest\view; +use function Tempest\Container\get; +use function Tempest\View\view; $attributeString = $attributes ->map(fn (string $value, string $key) => "{$key}=\"{$value}\"") diff --git a/packages/view/src/Components/x-form.view.php b/packages/view/src/Components/x-form.view.php index cc0bd2b245..95bd8ee2a4 100644 --- a/packages/view/src/Components/x-form.view.php +++ b/packages/view/src/Components/x-form.view.php @@ -19,8 +19,6 @@ ?>
- - diff --git a/packages/view/src/Components/x-icon.view.php b/packages/view/src/Components/x-icon.view.php index 4c01640456..67905ea976 100644 --- a/packages/view/src/Components/x-icon.view.php +++ b/packages/view/src/Components/x-icon.view.php @@ -4,15 +4,15 @@ * @var string|null $class */ -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; use Tempest\Icon\Icon; -use function Tempest\get; +use function Tempest\Container\get; use function Tempest\Support\str; $class ??= null; $name ??= null; -$appConfig = get(AppConfig::class); +$environment = get(Environment::class); if ($name) { $svg = get(Icon::class)->render($name); @@ -20,7 +20,7 @@ $svg = null; } -if ($svg === null && $appConfig->environment->isLocal()) { +if ($svg === null && $environment->isLocal()) { $svg = ''; } diff --git a/packages/view/src/Components/x-input.view.php b/packages/view/src/Components/x-input.view.php index f9bd1f971c..f1ddb3835b 100644 --- a/packages/view/src/Components/x-input.view.php +++ b/packages/view/src/Components/x-input.view.php @@ -7,14 +7,14 @@ * @var string|null $default */ -use Tempest\Http\Session\Session; +use Tempest\Http\Session\FormSession; use Tempest\Validation\Validator; -use function Tempest\get; +use function Tempest\Container\get; use function Tempest\Support\str; -/** @var Session $session */ -$session = get(Session::class); +/** @var FormSession $formSession */ +$formSession = get(FormSession::class); /** @var Validator $validator */ $validator = get(Validator::class); @@ -24,8 +24,8 @@ $type ??= 'text'; $default ??= null; -$errors = $session->getErrorsFor($name); -$original = $session->getOriginalValueFor($name, $default); +$errors = $formSession->getErrorsFor($name); +$original = $formSession->getOriginalValueFor($name, $default); ?>
diff --git a/packages/view/src/Components/x-markdown.view.php b/packages/view/src/Components/x-markdown.view.php index 083799365d..56c75b8bb7 100644 --- a/packages/view/src/Components/x-markdown.view.php +++ b/packages/view/src/Components/x-markdown.view.php @@ -7,7 +7,7 @@ use League\CommonMark\MarkdownConverter; use Tempest\View\Slot; -use function Tempest\get; +use function Tempest\Container\get; $content ??= $slots[Slot::DEFAULT]->content ?? ''; $markdown = get(MarkdownConverter::class); diff --git a/packages/view/src/Elements/ElementFactory.php b/packages/view/src/Elements/ElementFactory.php index bf8e9dcf1e..4d63a88fa7 100644 --- a/packages/view/src/Elements/ElementFactory.php +++ b/packages/view/src/Elements/ElementFactory.php @@ -69,6 +69,10 @@ private function makeElement(Token $token, ?Element $parent): ?Element return new TextElement(text: $text); } + if ($token->type === TokenType::WHITESPACE) { + return new WhitespaceElement($token->content); + } + if (! $token->tag || $token->type === TokenType::COMMENT || $token->type === TokenType::PHP) { return new RawElement(token: $token, tag: null, content: $token->compile()); } diff --git a/packages/view/src/Elements/IsElement.php b/packages/view/src/Elements/IsElement.php index 5e31977e81..7aa66e0307 100644 --- a/packages/view/src/Elements/IsElement.php +++ b/packages/view/src/Elements/IsElement.php @@ -60,11 +60,7 @@ public function getAttribute(string $name): ?string { $attributes = $this->getAttributes(); - $originalName = $name; - - $name = ltrim($name, ':'); - - return $attributes[$originalName] ?? $this->attributes[":{$name}"] ?? $this->attributes[$name] ?? null; + return $attributes[$name] ?? null; } public function setAttribute(string $name, string $value): self @@ -114,6 +110,10 @@ public function setPrevious(?Element $previous): self public function getPrevious(): ?Element { + if ($this->previous instanceof WhitespaceElement) { + return $this->previous->getPrevious(); + } + return $this->previous; } diff --git a/packages/view/src/Elements/ViewComponentElement.php b/packages/view/src/Elements/ViewComponentElement.php index 85f97735e5..dbe953dd59 100644 --- a/packages/view/src/Elements/ViewComponentElement.php +++ b/packages/view/src/Elements/ViewComponentElement.php @@ -40,7 +40,9 @@ public function __construct( array $attributes, ) { $this->attributes = $attributes; - $this->viewComponentAttributes = arr($attributes); + + $this->viewComponentAttributes = arr($attributes) + ->mapWithKeys(fn (string $value, string $key) => yield str($key)->ltrim(':')->toString() => $value); $this->dataAttributes = arr($attributes) ->filter(fn (string $_, string $key) => ! str_starts_with($key, ':')) @@ -96,62 +98,21 @@ public function compile(): string $compiled = str($this->viewComponent->contents); - // Fallthrough attributes - $compiled = $compiled - ->replaceRegex( - regex: '/^<(?[\w-]+)(.*?["\s])?>/', // Match the very first opening tag, this will never fail. - replace: function ($matches) { - /** @var \Tempest\View\Parser\Token $token */ - $token = TempestViewParser::ast($matches[0])[0]; - - $attributes = arr($token->htmlAttributes)->map(fn (string $value) => new MutableString($value)); - - foreach (['class', 'style', 'id'] as $attributeName) { - if (! isset($this->dataAttributes[$attributeName])) { - continue; - } - - $attributes[$attributeName] ??= new MutableString(); - - if ($attributeName === 'id') { - $attributes[$attributeName] = new MutableString(' ' . $this->dataAttributes[$attributeName]); - } else { - $attributes[$attributeName]->append(' ' . $this->dataAttributes[$attributeName]); - } - } - - return sprintf( - '<%s%s>', - $matches['tag'], - $attributes - ->map(function (MutableString $value, string $key) { - return sprintf('%s="%s"', $key, $value->trim()); - }) - ->implode(' ') - ->when( - fn (ImmutableString $string) => $string->isNotEmpty(), - fn (ImmutableString $string) => $string->prepend(' '), - ), - ); - }, - ); + $compiled = $this->applyFallthroughAttributes($compiled); - // Add scoped variables $compiled = $compiled ->prepend( - // Open the current scope sprintf( - '', + '', $this->dataAttributes->isNotEmpty() ? ', ' . $this->dataAttributes->map(fn (string $_value, string $key) => "\${$key}")->implode(', ') : '', $this->expressionAttributes->isNotEmpty() ? ', ' . $this->expressionAttributes->map(fn (string $_value, string $key) => "\${$key}")->implode(', ') : '', $this->scopedVariables->isNotEmpty() ? ', ' . $this->scopedVariables->map(fn (string $name) => "\${$name}")->implode(', ') : '', ), ) ->append( - // Close and call the current scope sprintf( - 'currentView?->data ?? []) %s %s) ?>', - ViewObjectExporter::export($this->viewComponentAttributes), + 'currentView?->data ?? []) %s %s %s) ?>', + $this->exportAttributesArray(), ViewObjectExporter::export($slots), $this->scopedVariables->isNotEmpty() ? $this->scopedVariables->map(fn (string $name) => "'{$name}' => \${$name}")->implode(', ') @@ -162,10 +123,12 @@ public function compile(): string $this->expressionAttributes->isNotEmpty() ? ', ' . $this->expressionAttributes->map(fn (mixed $value, string $key) => "{$key}: " . $value)->implode(', ') : '', + $this->scopedVariables->isNotEmpty() + ? ', ' . $this->scopedVariables->map(fn (string $name) => "{$name}: \${$name}")->implode(', ') + : '', ), ); - // Compile slots $compiled = $compiled->replaceRegex( regex: '/[\w-]+)")?((\s*\/>)|>(?(.|\n)*?)<\/x-slot>)/', replace: function ($matches) use ($slots) { @@ -183,7 +146,7 @@ public function compile(): string // A slot doesn't have any content, so we'll comment it out. // This is to prevent DOM parsing errors (slots in tags is one example, see #937) - return $this->environment->isProduction() ? '' : ''; + return $this->environment->isLocal() ? '' : ''; } $slotElement = $this->getSlotElement($slot->name); @@ -222,4 +185,77 @@ private function getSlotElement(string $name): SlotElement|CollectionElement|nul return null; } + + private function applyFallthroughAttributes(ImmutableString $compiled): ImmutableString + { + return $compiled->replaceRegex( + regex: '/^<(?[\w-]+)(.*?["\s])?>/', + replace: function (array $matches): string { + /** @var Token $token */ + $token = TempestViewParser::ast($matches[0])[0]; + + $attributes = arr($token->htmlAttributes) + ->map(fn (string $value) => new MutableString($value)); + + foreach (['class', 'style', 'id'] as $name) { + $attributes = $this->applyFallthroughAttribute($attributes, $name); + } + + $attributeString = $attributes + ->map(fn (MutableString $value, string $key) => sprintf('%s="%s"', $key, $value->trim())) + ->implode(' ') + ->when( + fn (ImmutableString $s) => $s->isNotEmpty(), + fn (ImmutableString $s) => $s->prepend(' '), + ); + + return sprintf('<%s%s>', $matches['tag'], $attributeString); + }, + ); + } + + private function applyFallthroughAttribute(ImmutableArray $attributes, string $name): ImmutableArray + { + $hasDataAttribute = isset($this->dataAttributes[$name]); + $hasExpressionAttribute = isset($this->expressionAttributes[$name]); + + if (! $hasDataAttribute && ! $hasExpressionAttribute) { + return $attributes; + } + + $attributes[$name] ??= new MutableString(); + + if ($name === 'id') { + if ($hasDataAttribute) { + $attributes[$name] = new MutableString($this->dataAttributes[$name]); + } elseif ($hasExpressionAttribute) { + $attributes[$name] = new MutableString(sprintf('', $name)); + } + } else { + if ($hasDataAttribute) { + $attributes[$name]->append(' ' . $this->dataAttributes[$name]); + } + if ($hasExpressionAttribute) { + $attributes[$name]->append(sprintf(' ', $name)); + } + } + + return $attributes; + } + + private function exportAttributesArray(): string + { + $entries = []; + + foreach ($this->viewComponentAttributes as $key => $value) { + $camelKey = str($key)->camel()->toString(); + $isExpression = isset($this->expressionAttributes[$camelKey]); + + $entries[] = $isExpression + ? sprintf("'%s' => %s", $key, $value ?: 'true') + : sprintf("'%s' => %s", $key, ViewObjectExporter::exportValue($value)); + } + + return sprintf('new \%s([%s])', ImmutableArray::class, implode(', ', $entries)); + } } diff --git a/packages/view/src/Elements/WhitespaceElement.php b/packages/view/src/Elements/WhitespaceElement.php new file mode 100644 index 0000000000..148e409e87 --- /dev/null +++ b/packages/view/src/Elements/WhitespaceElement.php @@ -0,0 +1,21 @@ +content; + } +} diff --git a/packages/view/src/Exceptions/ViewCompilationFailed.php b/packages/view/src/Exceptions/ViewCompilationFailed.php index 22572c8910..7cfeb24c25 100644 --- a/packages/view/src/Exceptions/ViewCompilationFailed.php +++ b/packages/view/src/Exceptions/ViewCompilationFailed.php @@ -5,49 +5,26 @@ namespace Tempest\View\Exceptions; use Exception; +use Tempest\Core\ProvidesContext; use Throwable; -use function Tempest\Support\str; - -final class ViewCompilationFailed extends Exception +final class ViewCompilationFailed extends Exception implements ProvidesContext { - public function __construct(string $path, string $content, Throwable $previous) - { - $excerpt = str($content) - ->excerpt( - $previous->getLine() - 5, - $previous->getLine() + 5, - asArray: true, - ) - ->map(function (string $line, int $number) use ($previous) { - return sprintf( - '%s%s | %s', - $number === $previous->getLine() ? '> ' : ' ', - $number, - $line, - ); - }) - ->implode(PHP_EOL); - - $message = sprintf( - '%s -%s -%s - -%s - -In %s -', - str_repeat('-', strlen($previous->getMessage())), - $previous->getMessage(), - str_repeat('-', strlen($previous->getMessage())), - $excerpt, - $path, - ); - + public function __construct( + private(set) string $path, + private(set) string $content, + Throwable $previous, + ) { parent::__construct( - message: $message, + message: sprintf('View could not be compiled: %s.', lcfirst($previous->getMessage())), previous: $previous, ); } + + public function context(): array + { + return [ + 'path' => $this->path, + ]; + } } diff --git a/packages/view/src/Initializers/ElementFactoryInitializer.php b/packages/view/src/Initializers/ElementFactoryInitializer.php index b5f0fd4695..f6eb55b6a0 100644 --- a/packages/view/src/Initializers/ElementFactoryInitializer.php +++ b/packages/view/src/Initializers/ElementFactoryInitializer.php @@ -5,7 +5,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; use Tempest\View\Elements\ElementFactory; use Tempest\View\ViewConfig; @@ -15,8 +15,8 @@ final class ElementFactoryInitializer implements Initializer public function initialize(Container $container): ElementFactory { return new ElementFactory( - $container->get(ViewConfig::class), - $container->get(AppConfig::class)->environment, + viewConfig: $container->get(ViewConfig::class), + environment: $container->get(Environment::class), ); } } diff --git a/packages/view/src/Initializers/ViewCacheInitializer.php b/packages/view/src/Initializers/ViewCacheInitializer.php index 0d2280f15d..14befdca9b 100644 --- a/packages/view/src/Initializers/ViewCacheInitializer.php +++ b/packages/view/src/Initializers/ViewCacheInitializer.php @@ -5,7 +5,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; use Tempest\View\ViewCache; use function Tempest\env; @@ -16,20 +16,18 @@ final class ViewCacheInitializer implements Initializer public function initialize(Container $container): ViewCache { $viewCache = new ViewCache( - enabled: $this->shouldCacheBeEnabled( - $container->get(AppConfig::class)->environment->isProduction(), - ), + enabled: $this->shouldCacheBeEnabled(), ); return $viewCache; } - private function shouldCacheBeEnabled(bool $isProduction): bool + private function shouldCacheBeEnabled(): bool { if (env('INTERNAL_CACHES') === false) { return false; } - return (bool) env('VIEW_CACHE', default: $isProduction); + return (bool) env('VIEW_CACHE', default: Environment::guessFromEnvironment()->requiresCaution()); } } diff --git a/packages/view/src/Parser/TempestViewLexer.php b/packages/view/src/Parser/TempestViewLexer.php index 4a8d275514..9b38a66c3a 100644 --- a/packages/view/src/Parser/TempestViewLexer.php +++ b/packages/view/src/Parser/TempestViewLexer.php @@ -22,7 +22,7 @@ public function lex(): TokenCollection { $tokens = []; - while ($this->current) { + while ($this->current !== null) { if ($this->comesNext('lexXml(); } elseif ($this->comesNext('lexCharacterData()]; } elseif ($this->comesNext('<')) { $tokens = [...$tokens, ...$this->lexTag()]; + } elseif ($this->comesNext(' ') || $this->comesNext(PHP_EOL)) { + $tokens[] = $this->lexWhitespace(); } else { $tokens[] = $this->lexContent(); } @@ -214,6 +216,23 @@ private function lexDoctype(): Token return new Token($buffer, TokenType::DOCTYPE); } + private function lexWhitespace(): Token + { + $buffer = ''; + + while ($this->current !== null) { + $seek = $this->seek(); + + if ($seek !== ' ' && $seek !== PHP_EOL) { + break; + } + + $buffer .= $this->consume(); + } + + return new Token($buffer, TokenType::WHITESPACE); + } + private function lexCharacterData(): array { $tokens = [ diff --git a/packages/view/src/Parser/TempestViewParser.php b/packages/view/src/Parser/TempestViewParser.php index af0452aeb1..77309107bc 100644 --- a/packages/view/src/Parser/TempestViewParser.php +++ b/packages/view/src/Parser/TempestViewParser.php @@ -9,7 +9,7 @@ final class TempestViewParser private array $scope = []; private ?Token $currentScope { - get => $this->scope[array_key_last($this->scope) ?? ''] ?? null; + get => array_last($this->scope); } public function __construct( diff --git a/packages/view/src/Parser/TokenCollection.php b/packages/view/src/Parser/TokenCollection.php index a571fb3960..b2f63a95a4 100644 --- a/packages/view/src/Parser/TokenCollection.php +++ b/packages/view/src/Parser/TokenCollection.php @@ -40,7 +40,7 @@ public function __debugInfo(): array public function offsetExists(mixed $offset): bool { - return $this->tokens[$offset] ?? false; + return isset($this->tokens[$offset]); } public function offsetGet(mixed $offset): mixed diff --git a/packages/view/src/Parser/TokenType.php b/packages/view/src/Parser/TokenType.php index 0a433b5485..2938ea76f2 100644 --- a/packages/view/src/Parser/TokenType.php +++ b/packages/view/src/Parser/TokenType.php @@ -18,4 +18,5 @@ enum TokenType case DOCTYPE; case CHARACTER_DATA_OPEN; case CHARACTER_DATA_CLOSE; + case WHITESPACE; } diff --git a/packages/view/src/Renderers/TempestViewRenderer.php b/packages/view/src/Renderers/TempestViewRenderer.php index 01c239a1e9..26429037eb 100644 --- a/packages/view/src/Renderers/TempestViewRenderer.php +++ b/packages/view/src/Renderers/TempestViewRenderer.php @@ -51,7 +51,7 @@ public static function make( $elementFactory->setViewCompiler($compiler); - $viewCache ??= ViewCache::disabled(); + $viewCache ??= ViewCache::create(enabled: false); return new self( compiler: $compiler, @@ -117,6 +117,8 @@ private function renderCompiled(View $_view, string $_path): string try { include $_path; } catch (Throwable $throwable) { + ob_end_clean(); // clean buffer before rendering exception + throw new ViewCompilationFailed( path: $_path, content: Filesystem\read_file($_path), @@ -135,7 +137,11 @@ public function escape(null|string|HtmlString|Stringable $value): string return (string) $value; } - return htmlentities((string) $value); + return htmlentities( + string: (string) $value, + flags: ENT_QUOTES | ENT_SUBSTITUTE, + encoding: 'UTF-8', + ); } private function validateView(View $view): void diff --git a/packages/view/src/Slot.php b/packages/view/src/Slot.php index 86a5b0b8b6..6d65f56f87 100644 --- a/packages/view/src/Slot.php +++ b/packages/view/src/Slot.php @@ -22,7 +22,7 @@ public function __construct( } public string $content { - get => base64_decode($this->content, true); + get => ($d = base64_decode($this->content, strict: true)) === false ? '' : $d; } public ImmutableArray $exportData { diff --git a/packages/view/src/ViewCache.php b/packages/view/src/ViewCache.php index a612b08d53..fe0db3e46d 100644 --- a/packages/view/src/ViewCache.php +++ b/packages/view/src/ViewCache.php @@ -5,6 +5,7 @@ namespace Tempest\View; use Closure; +use Throwable; use function Tempest\internal_storage_path; use function Tempest\Support\path; @@ -16,23 +17,15 @@ public function __construct( private ?ViewCachePool $pool = null, ) { $this->pool ??= new ViewCachePool( - directory: internal_storage_path('cache/views'), + directory: self::getCachePath(), ); } - public static function enabled(?string $path = null): self + public static function create(bool $enabled = true, ?string $path = null): self { return new self( - enabled: true, - pool: new ViewCachePool($path ?? __DIR__ . '/../.tempest/cache'), - ); - } - - public static function disabled(?string $path = null): self - { - return new self( - enabled: false, - pool: new ViewCachePool($path ?? __DIR__ . '/../.tempest/cache'), + enabled: $enabled, + pool: new ViewCachePool($path ?? self::getCachePath()), ); } @@ -43,7 +36,7 @@ public function clear(): void public function getCachedViewPath(string $path, Closure $compiledView): string { - $cacheKey = (string) crc32($path); + $cacheKey = hash('xxh64', $path); $cacheItem = $this->pool->getItem($cacheKey); @@ -55,4 +48,13 @@ public function getCachedViewPath(string $path, Closure $compiledView): string return path($this->pool->directory, $cacheItem->getKey() . '.php')->toString(); } + + private static function getCachePath(): string + { + try { + return internal_storage_path('cache/views'); + } catch (Throwable) { + return __DIR__ . '/../.tempest/cache'; + } + } } diff --git a/packages/view/src/functions.php b/packages/view/src/functions.php index c927dd38c5..49c43d5f08 100644 --- a/packages/view/src/functions.php +++ b/packages/view/src/functions.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace Tempest { - use Tempest\View\GenericView; - use Tempest\View\View; +namespace Tempest\View; - /** - * Returns a {@see View} instance for the specified `$path`. - */ - function view(string $path, mixed ...$params): View - { - return new GenericView($path)->data(...$params); - } +use Tempest\View\GenericView; +use Tempest\View\View; + +/** + * Returns a {@see View} instance for the specified `$path`. + */ +function view(string $path, mixed ...$params): View +{ + return new GenericView($path)->data(...$params); } diff --git a/packages/view/tests/FallthroughAttributesTest.php b/packages/view/tests/FallthroughAttributesTest.php new file mode 100644 index 0000000000..89a6f84751 --- /dev/null +++ b/packages/view/tests/FallthroughAttributesTest.php @@ -0,0 +1,38 @@ +addViewComponents( + __DIR__ . '/Fixtures/x-fallthrough-test.view.php', + __DIR__ . '/Fixtures/x-fallthrough-dynamic-test.view.php', + ); + + $renderer = + TempestViewRenderer::make( + viewConfig: $viewConfig, + ); + + $html = $renderer->render( + view(__DIR__ . '/Fixtures/fallthrough.view.php'), + ); + + $this->assertEquals(str_replace([' ', PHP_EOL], '', <<<'HTML' +
+
+
+
+ HTML), str_replace([' ', PHP_EOL], '', $html)); + } +} diff --git a/packages/view/tests/Fixtures/fallthrough.view.php b/packages/view/tests/Fixtures/fallthrough.view.php new file mode 100644 index 0000000000..bdb7a77946 --- /dev/null +++ b/packages/view/tests/Fixtures/fallthrough.view.php @@ -0,0 +1,7 @@ + + + + diff --git a/packages/view/tests/Fixtures/x-fallthrough-dynamic-test.view.php b/packages/view/tests/Fixtures/x-fallthrough-dynamic-test.view.php new file mode 100644 index 0000000000..1ccf85821b --- /dev/null +++ b/packages/view/tests/Fixtures/x-fallthrough-dynamic-test.view.php @@ -0,0 +1 @@ +
diff --git a/packages/view/tests/Fixtures/x-fallthrough-test.view.php b/packages/view/tests/Fixtures/x-fallthrough-test.view.php new file mode 100644 index 0000000000..58d8adfe06 --- /dev/null +++ b/packages/view/tests/Fixtures/x-fallthrough-test.view.php @@ -0,0 +1 @@ +
diff --git a/packages/view/tests/StandaloneViewRendererTest.php b/packages/view/tests/StandaloneViewRendererTest.php index 71e4eedee4..dc39898f44 100644 --- a/packages/view/tests/StandaloneViewRendererTest.php +++ b/packages/view/tests/StandaloneViewRendererTest.php @@ -11,7 +11,7 @@ use Tempest\View\ViewComponent; use Tempest\View\ViewConfig; -use function Tempest\view; +use function Tempest\View\view; final class StandaloneViewRendererTest extends TestCase { @@ -71,7 +71,7 @@ public function test_invalid_view_component_paths_within_config(): void public function test_with_cache_enabled(): void { - $viewCache = ViewCache::enabled(); + $viewCache = ViewCache::create(); $viewCache->clear(); $renderer = @@ -93,7 +93,7 @@ public function test_with_cache_enabled(): void public function test_with_cache_disabled(): void { $renderer = TempestViewRenderer::make( - viewCache: ViewCache::disabled(), + viewCache: ViewCache::create(enabled: false), ); $html = $renderer->render( diff --git a/packages/view/tests/TempestViewLexerTest.php b/packages/view/tests/TempestViewLexerTest.php index 9d533d5351..0ba62e2882 100644 --- a/packages/view/tests/TempestViewLexerTest.php +++ b/packages/view/tests/TempestViewLexerTest.php @@ -173,13 +173,40 @@ class=', TokenType::ATTRIBUTE_NAME), new Token("\n>", TokenType::OPEN_TAG_END), new Token(' -', TokenType::CONTENT), +', TokenType::WHITESPACE), new Token('
', TokenType::CLOSING_TAG), ], actual: $tokens, ); } + public function test_whitespace(): void + { + $html = <<<'HTML' +

Test Test

+ HTML; + + $tokens = new TempestViewLexer($html)->lex(); + + $this->assertTokens( + expected: [ + new Token('', TokenType::OPEN_TAG_END), + new Token('', TokenType::OPEN_TAG_END), + new Token('Test', TokenType::CONTENT), + new Token('', TokenType::CLOSING_TAG), + new Token(' ', TokenType::WHITESPACE), + new Token('', TokenType::OPEN_TAG_END), + new Token('Test', TokenType::CONTENT), + new Token('', TokenType::CLOSING_TAG), + new Token('

', TokenType::CLOSING_TAG), + ], + actual: $tokens, + ); + } + public function test_lexer_with_falsy_values(): void { $html = <<<'HTML' diff --git a/packages/vite/composer.json b/packages/vite/composer.json index 37a039ec78..844454c4c5 100644 --- a/packages/vite/composer.json +++ b/packages/vite/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "dev", "require": { - "php": "^8.4" + "php": "^8.5" }, "autoload": { "psr-4": { diff --git a/packages/vite/src/Vite.php b/packages/vite/src/Vite.php index 652e33629b..fe21d7a39b 100644 --- a/packages/vite/src/Vite.php +++ b/packages/vite/src/Vite.php @@ -5,7 +5,7 @@ namespace Tempest\Vite; use Tempest\Container\Container; -use Tempest\Core\AppConfig; +use Tempest\Core\Environment; use Tempest\Support\Filesystem; use Tempest\Support\Json; use Tempest\Vite\Exceptions\DevelopmentServerWasNotRunning; @@ -28,7 +28,7 @@ final class Vite private static ?Manifest $manifest = null; public function __construct( - private readonly AppConfig $appConfig, + private readonly Environment $environment, private readonly ViteConfig $viteConfig, private readonly Container $container, private readonly TagCompiler $tagCompiler, @@ -109,7 +109,7 @@ private function getManifest(): Manifest private function shouldUseManifest(): bool { - if ($this->appConfig->environment->isTesting() && ! $this->viteConfig->useManifestDuringTesting) { + if ($this->environment->isTesting() && ! $this->viteConfig->useManifestDuringTesting) { return false; } diff --git a/packages/vite/src/functions.php b/packages/vite/src/functions.php index 7340fda949..b217c05043 100644 --- a/packages/vite/src/functions.php +++ b/packages/vite/src/functions.php @@ -2,17 +2,16 @@ declare(strict_types=1); -namespace Tempest { - use Tempest\Support\Html\HtmlString; - use Tempest\Vite\Vite; +namespace Tempest\Vite; - /** - * Inject tags for the specified or configured `$entrypoints`. - */ - function vite_tags(null|string|array $entrypoints = null): HtmlString - { - return new HtmlString( - string: implode('', get(Vite::class)->getTags(is_array($entrypoints) ? $entrypoints : [$entrypoints])), - ); - } +use Tempest\Vite\Vite; + +use function Tempest\Container\get; + +/** + * Gets tags for the specified or configured `$entrypoints`. + */ +function get_tags(null|string|array $entrypoints = null): array +{ + return get(Vite::class)->getTags(is_array($entrypoints) ? $entrypoints : [$entrypoints]); } diff --git a/packages/vite/src/x-vite-tags.view.php b/packages/vite/src/x-vite-tags.view.php index d2d9674e14..3b745ddc20 100644 --- a/packages/vite/src/x-vite-tags.view.php +++ b/packages/vite/src/x-vite-tags.view.php @@ -4,14 +4,15 @@ * @var string|null $entrypoint */ +use Tempest\Support\Html\HtmlString; +use Tempest\Vite; use Tempest\Vite\ViteConfig; -use function Tempest\get; -use function Tempest\vite_tags; +use function Tempest\Container\get; $viteConfig = get(ViteConfig::class); - -$html = vite_tags($entrypoints ?? $entrypoint ?? $viteConfig->entrypoints); +$tags = Vite\get_tags($entrypoints ?? $entrypoint ?? $viteConfig->entrypoints); +$html = new HtmlString(implode('', $tags)); ?> {!! $html !!} diff --git a/rector.php b/rector.php index 76d45de6c1..b511c8c6c9 100644 --- a/rector.php +++ b/rector.php @@ -10,7 +10,7 @@ use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector; use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector; use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector; -use Rector\Php81\Rector\Array_\FirstClassCallableRector; +use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; use Rector\Php82\Rector\Class_\ReadOnlyClassRector; @@ -43,7 +43,7 @@ ArgumentAdderRector::class, ClosureToArrowFunctionRector::class, EmptyOnNullableObjectToInstanceOfRector::class, - FirstClassCallableRector::class, + ArrayToFirstClassCallableRector::class, NullToStrictStringFuncCallArgRector::class, ReadOnlyClassRector::class, ReadOnlyPropertyRector::class, diff --git a/src/Tempest/Framework/Commands/MigrateFreshCommand.php b/src/Tempest/Framework/Commands/MigrateFreshCommand.php index 02bae840ca..8e80c7d0b1 100644 --- a/src/Tempest/Framework/Commands/MigrateFreshCommand.php +++ b/src/Tempest/Framework/Commands/MigrateFreshCommand.php @@ -14,7 +14,7 @@ use Tempest\Database\Migrations\FreshMigrationFailed; use Tempest\Database\Migrations\MigrationManager; use Tempest\Database\Migrations\TableDropped; -use Tempest\EventBus\EventHandler; +use Tempest\EventBus\EventBus; #[Singleton] final class MigrateFreshCommand @@ -24,6 +24,7 @@ final class MigrateFreshCommand public function __construct( private readonly Console $console, private readonly MigrationManager $migrationManager, + private readonly EventBus $eventBus, ) {} #[ConsoleCommand( @@ -43,6 +44,9 @@ public function __invoke( #[ConsoleArgument(description: 'Select one specific seeder to run')] ?string $seeder = null, ): ExitCode { + $this->eventBus->listen($this->onTableDropped(...)); + $this->eventBus->listen($this->onFreshMigrationFailed(...)); + if ($validate) { $validationSuccess = $this->console->call(MigrateValidateCommand::class); @@ -78,7 +82,6 @@ public function __invoke( return ExitCode::SUCCESS; } - #[EventHandler] public function onTableDropped(TableDropped $event): void { $this->count += 1; @@ -88,7 +91,6 @@ public function onTableDropped(TableDropped $event): void ); } - #[EventHandler] public function onFreshMigrationFailed(FreshMigrationFailed $event): void { $this->console->error($event->throwable->getMessage()); diff --git a/src/Tempest/Framework/Commands/MigrateValidateCommand.php b/src/Tempest/Framework/Commands/MigrateValidateCommand.php index ad121d257c..cc8077d7dd 100644 --- a/src/Tempest/Framework/Commands/MigrateValidateCommand.php +++ b/src/Tempest/Framework/Commands/MigrateValidateCommand.php @@ -13,7 +13,7 @@ use Tempest\Database\Migrations\MigrationHashMismatched; use Tempest\Database\Migrations\MigrationManager; use Tempest\Database\Migrations\MigrationValidationFailed; -use Tempest\EventBus\EventHandler; +use Tempest\EventBus\EventBus; #[Singleton] final class MigrateValidateCommand @@ -23,6 +23,7 @@ final class MigrateValidateCommand public function __construct( private readonly Console $console, private readonly MigrationManager $migrationManager, + private readonly EventBus $eventBus, ) {} #[ConsoleCommand( @@ -33,6 +34,7 @@ public function __invoke( #[ConsoleArgument(description: 'Use a specific database.')] ?string $database = null, ): ExitCode { + $this->eventBus->listen($this->onMigrationValidationFailed(...)); $this->console->header('Validating migration files'); $this->migrationManager->onDatabase($database)->validate(); @@ -48,7 +50,6 @@ public function __invoke( return ExitCode::SUCCESS; } - #[EventHandler] public function onMigrationValidationFailed(MigrationValidationFailed $event): void { $error = match ($event->exception::class) { diff --git a/src/Tempest/Framework/Installers/index.php b/src/Tempest/Framework/Installers/index.php index 93d0cefae7..a86f566e51 100644 --- a/src/Tempest/Framework/Installers/index.php +++ b/src/Tempest/Framework/Installers/index.php @@ -7,5 +7,3 @@ require_once __DIR__ . '/../vendor/autoload.php'; HttpApplication::boot(__DIR__ . '/../')->run(); - -exit(); diff --git a/src/Tempest/Framework/Testing/Http/HttpRouterTester.php b/src/Tempest/Framework/Testing/Http/HttpRouterTester.php index 3dc6065c0f..f5bf6cddfd 100644 --- a/src/Tempest/Framework/Testing/Http/HttpRouterTester.php +++ b/src/Tempest/Framework/Testing/Http/HttpRouterTester.php @@ -4,133 +4,216 @@ namespace Tempest\Framework\Testing\Http; +use BackedEnum; +use InvalidArgumentException; use Laminas\Diactoros\ServerRequestFactory; use Psr\Http\Message\ServerRequestInterface as PsrRequest; use Tempest\Container\Container; +use Tempest\Http\ContentType; use Tempest\Http\GenericRequest; use Tempest\Http\Mappers\RequestToPsrRequestMapper; use Tempest\Http\Method; use Tempest\Http\Request; +use Tempest\Reflection\MethodReflector; +use Tempest\Router\Exceptions\HttpExceptionHandler; +use Tempest\Router\Route; use Tempest\Router\RouteConfig; +use Tempest\Router\RouteDecorator; use Tempest\Router\Router; +use Tempest\Router\Routing\Construction\DiscoveredRoute; +use Tempest\Router\Routing\Construction\RouteConfigurator; +use Tempest\Router\SecFetchMode; +use Tempest\Router\SecFetchSite; +use Tempest\Router\Static\StaticPageConfig; +use Tempest\Router\StaticPage; use Tempest\Support\Uri; +use Throwable; -use function Tempest\map; +use function Tempest\Mapper\map; final class HttpRouterTester { - private bool $throwHttpExceptions = false; + private(set) ?ContentType $contentType = null; + + private(set) bool $includeSecFetchHeaders = true; public function __construct( private Container $container, ) {} - public function get(string $uri, array $query = [], array $headers = []): TestResponseHelper + /** + * Registers a route for testing purposes. + * + * @param array{0: class-string, 1: string}|class-string|MethodReflector $action + */ + public function registerRoute(array|string|MethodReflector $action): self { - return $this->sendRequest( - new GenericRequest( - method: Method::GET, - uri: Uri\merge_query($uri, ...$query), - body: [], - headers: $headers, + $reflector = match (true) { + $action instanceof MethodReflector => $action, + is_array($action) => MethodReflector::fromParts(...$action), + default => MethodReflector::fromParts($action, '__invoke'), + }; + + if ($reflector->getAttribute(Route::class) === null) { + throw new InvalidArgumentException('Missing route attribute'); + } + + $configurator = $this->container->get(RouteConfigurator::class); + + $configurator->addRoute( + DiscoveredRoute::fromRoute( + $reflector->getAttribute(Route::class), + [ + ...$reflector->getDeclaringClass()->getAttributes(RouteDecorator::class), + ...$reflector->getAttributes(RouteDecorator::class), + ], + $reflector, ), ); + + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->apply($configurator->toRouteConfig()); + + return $this; } - public function head(string $uri, array $query = [], array $headers = []): TestResponseHelper + /** + * Registers a static page for testing purposes. + * + * @param array{0: class-string, 1: string}|class-string|MethodReflector $action + */ + public function registerStaticPage(array|string|MethodReflector $action): self { - return $this->sendRequest( - new GenericRequest( - method: Method::HEAD, - uri: Uri\merge_query($uri, ...$query), - body: [], - headers: $headers, - ), + $reflector = match (true) { + $action instanceof MethodReflector => $action, + is_array($action) => MethodReflector::fromParts(...$action), + default => MethodReflector::fromParts($action, '__invoke'), + }; + + if ($reflector->getAttribute(StaticPage::class) === null) { + throw new InvalidArgumentException('Missing static page attribute'); + } + + $this->container->get(StaticPageConfig::class)->addHandler( + $reflector->getAttribute(StaticPage::class), + $reflector, ); + + return $this; } - public function post(string $uri, array $body = [], array $query = [], array $headers = []): TestResponseHelper + /** + * Specifies the "Accept" header for subsequent requests. + */ + public function as(ContentType $contentType): self { - return $this->sendRequest( - new GenericRequest( - method: Method::POST, - uri: Uri\merge_query($uri, ...$query), - body: $body, - headers: $headers, - ), - ); + $this->contentType = $contentType; + + return $this; } - public function put(string $uri, array $body = [], array $query = [], array $headers = []): TestResponseHelper + /** + * Specifies that subsequent requests should be sent without Sec-Fetch headers. + */ + public function withoutSecFetchHeaders(): self { - return $this->sendRequest( - new GenericRequest( - method: Method::PUT, - uri: Uri\merge_query($uri, ...$query), - body: $body, - headers: $headers, - ), - ); + $this->includeSecFetchHeaders = false; + + return $this; } - public function delete(string $uri, array $body = [], array $query = [], array $headers = []): TestResponseHelper + public function get(string $uri, array $query = [], array $headers = []): TestResponseHelper { - return $this->sendRequest( - new GenericRequest( - method: Method::DELETE, - uri: Uri\merge_query($uri, ...$query), - body: $body, - headers: $headers, - ), - ); + return $this->sendRequest(new GenericRequest( + method: Method::GET, + uri: Uri\merge_query($uri, ...$query), + body: [], + headers: $this->createHeaders($headers), + )); + } + + public function head(string $uri, array $query = [], array $headers = []): TestResponseHelper + { + return $this->sendRequest(new GenericRequest( + method: Method::HEAD, + uri: Uri\merge_query($uri, ...$query), + body: [], + headers: $this->createHeaders($headers), + )); + } + + public function post(string $uri, array|string $body = [], array $query = [], array $headers = []): TestResponseHelper + { + return $this->sendRequest(new GenericRequest( + method: Method::POST, + uri: Uri\merge_query($uri, ...$query), + body: is_string($body) ? [] : $body, + headers: $this->createHeaders($headers), + raw: is_string($body) ? $body : null, + )); + } + + public function put(string $uri, array|string $body = [], array $query = [], array $headers = []): TestResponseHelper + { + return $this->sendRequest(new GenericRequest( + method: Method::PUT, + uri: Uri\merge_query($uri, ...$query), + body: is_string($body) ? [] : $body, + headers: $this->createHeaders($headers), + raw: is_string($body) ? $body : null, + )); + } + + public function delete(string $uri, array|string $body = [], array $query = [], array $headers = []): TestResponseHelper + { + return $this->sendRequest(new GenericRequest( + method: Method::DELETE, + uri: Uri\merge_query($uri, ...$query), + body: is_string($body) ? [] : $body, + headers: $this->createHeaders($headers), + raw: is_string($body) ? $body : null, + )); } public function connect(string $uri, array $query = [], array $headers = []): TestResponseHelper { - return $this->sendRequest( - new GenericRequest( - method: Method::CONNECT, - uri: Uri\merge_query($uri, ...$query), - body: [], - headers: $headers, - ), - ); + return $this->sendRequest(new GenericRequest( + method: Method::CONNECT, + uri: Uri\merge_query($uri, ...$query), + body: [], + headers: $this->createHeaders($headers), + )); } public function options(string $uri, array $query = [], array $headers = []): TestResponseHelper { - return $this->sendRequest( - new GenericRequest( - method: Method::OPTIONS, - uri: Uri\merge_query($uri, ...$query), - body: [], - headers: $headers, - ), - ); + return $this->sendRequest(new GenericRequest( + method: Method::OPTIONS, + uri: Uri\merge_query($uri, ...$query), + body: [], + headers: $this->createHeaders($headers), + )); } public function trace(string $uri, array $query = [], array $headers = []): TestResponseHelper { - return $this->sendRequest( - new GenericRequest( - method: Method::TRACE, - uri: Uri\merge_query($uri, ...$query), - body: [], - headers: $headers, - ), - ); + return $this->sendRequest(new GenericRequest( + method: Method::TRACE, + uri: Uri\merge_query($uri, ...$query), + body: [], + headers: $this->createHeaders($headers), + )); } - public function patch(string $uri, array $body = [], array $query = [], array $headers = []): TestResponseHelper + public function patch(string $uri, array|string $body = [], array $query = [], array $headers = []): TestResponseHelper { - return $this->sendRequest( - new GenericRequest( - method: Method::PATCH, - uri: Uri\merge_query($uri, ...$query), - body: $body, - headers: $headers, - ), - ); + return $this->sendRequest(new GenericRequest( + method: Method::PATCH, + uri: Uri\merge_query($uri, ...$query), + body: is_string($body) ? [] : $body, + headers: $this->createHeaders($headers), + raw: is_string($body) ? $body : null, + )); } public function sendRequest(Request $request): TestResponseHelper @@ -138,29 +221,28 @@ public function sendRequest(Request $request): TestResponseHelper /** @var Router $router */ $router = $this->container->get(Router::class); - $this->container->get(RouteConfig::class)->throwHttpExceptions = $this->throwHttpExceptions; + try { + $response = $router->dispatch(map($request)->with(RequestToPsrRequestMapper::class)->do()); + } catch (Throwable $throwable) { + return new TestResponseHelper( + response: $this->container->get(HttpExceptionHandler::class)->renderResponse($request, $throwable), + request: $request, + container: $this->container, + throwable: $throwable, + ); + } return new TestResponseHelper( - response: $router->dispatch(map($request)->with(RequestToPsrRequestMapper::class)->do()), + response: $response, request: $request, container: $this->container, ); } - /** - * Instructs the router to throw {@see Tempest\Http\HttpException} errors when an error response is returned. This mimics production behavior. - */ - public function throwExceptions(bool $throw = true): self - { - $this->throwHttpExceptions = $throw; - - return $this; - } - public function makePsrRequest( string $uri, Method $method = Method::GET, - array $body = [], + array|string $body = [], array $headers = [], array $cookies = [], array $files = [], @@ -168,15 +250,48 @@ public function makePsrRequest( $_SERVER['REQUEST_URI'] = $uri; $_SERVER['REQUEST_METHOD'] = $method->value; - foreach ($headers as $key => $value) { - $key = strtoupper($key); + foreach ($this->createHeaders($headers) as $key => $value) { + if ($value instanceof BackedEnum) { + $value = $value->value; + } + + $key = strtoupper(str_replace('-', '_', $key)); $_SERVER["HTTP_{$key}"] = $value; } $_COOKIE = $cookies; - $_POST = $body; + + if (is_array($body)) { + $_POST = $body; + } else { + $_POST = []; + } return ServerRequestFactory::fromGlobals()->withUploadedFiles($files); } + + private function createHeaders(array $headers = []): array + { + $key = array_find_key( + array: $headers, + callback: fn (mixed $_, string $headerKey): bool => strcasecmp($headerKey, 'accept') === 0, + ); + + if ($this->contentType !== null) { + $headers[$key ?? 'accept'] = $this->contentType->value; + } + + if ($this->includeSecFetchHeaders === true) { + if (! array_key_exists('sec-fetch-site', array_change_key_case($headers, case: CASE_LOWER))) { + $headers['sec-fetch-site'] = SecFetchSite::SAME_ORIGIN; + } + + if (! array_key_exists('sec-fetch-mode', array_change_key_case($headers, case: CASE_LOWER))) { + $headers['sec-fetch-mode'] = SecFetchMode::CORS; + } + } + + return $headers; + } } diff --git a/src/Tempest/Framework/Testing/Http/TestResponseHelper.php b/src/Tempest/Framework/Testing/Http/TestResponseHelper.php index 0da7a41358..7915e7d04b 100644 --- a/src/Tempest/Framework/Testing/Http/TestResponseHelper.php +++ b/src/Tempest/Framework/Testing/Http/TestResponseHelper.php @@ -14,23 +14,30 @@ use Tempest\Http\Cookie\Cookie; use Tempest\Http\Request; use Tempest\Http\Response; -use Tempest\Http\Responses\Invalid; +use Tempest\Http\Session\FormSession; use Tempest\Http\Session\Session; use Tempest\Http\Status; use Tempest\Support\Arr; +use Tempest\Support\Json; use Tempest\Validation\Rule; -use Tempest\Validation\Validator; use Tempest\View\View; use Tempest\View\ViewRenderer; +use Throwable; use function Tempest\Support\arr; final class TestResponseHelper { + /** + * @param Response $response The original response from the controller. + * @param Request $request The original request sent to the controller. + * @param null|Throwable $throwable The exception thrown during the request, if any. + */ public function __construct( private(set) Response $response, private(set) Request $request, - private(set) ?Container $container = null, + private ?Container $container = null, + private(set) ?Throwable $throwable = null, ) {} public Status $status { @@ -46,23 +53,29 @@ public function __construct( get => $this->response->body; } + /** + * Asserts that the response has the given header, case insensitive. + */ public function assertHasHeader(string $name): self { Assert::assertArrayHasKey( - $name, - $this->response->headers, - sprintf('Failed to assert that response contains header [%s].', $name), + mb_strtolower($name), + array_change_key_case($this->response->headers, case: CASE_LOWER), + sprintf('Failed to assert that response contains the header [%s].', $name), ); return $this; } + /** + * Asserts that the response does not have the given header, case insensitive. + */ public function assertDoesNotHaveHeader(string $name): self { Assert::assertArrayNotHasKey( - $name, - $this->response->headers, - sprintf('Failed to assert that response does not contain header [%s].', $name), + mb_strtolower($name), + array_change_key_case($this->response->headers, case: CASE_LOWER), + sprintf('Failed to assert that response does not contain the header [%s].', $name), ); return $this; @@ -109,6 +122,9 @@ public function assertHeaderMatches(string $name, string $format): self Assert::fail(sprintf('Failed to assert that response header [%s] value contains [%s]. These header values were found: %s', $name, $format, $headerString)); } + /** + * Asserts that the response is a redirect. If a URL is provided, it also asserts that the "Location" header contains the given URL. + */ public function assertRedirect(?string $to = null): self { Assert::assertTrue( @@ -121,21 +137,63 @@ public function assertRedirect(?string $to = null): self : $this->assertHeaderContains('Location', $to); } + /** + * Asserts that the response status code is 200. + */ public function assertOk(): self { return $this->assertStatus(Status::OK); } + /** + * Asserts that the response status code is 403. + */ public function assertForbidden(): self { return $this->assertStatus(Status::FORBIDDEN); } + /** + * Asserts that the response status is code 404. + */ public function assertNotFound(): self { return $this->assertStatus(Status::NOT_FOUND); } + /** + * Asserts that the response has a status code in the [200-300[ range. + */ + public function assertSuccessful(): self + { + Assert::assertTrue($this->status->isSuccessful()); + + return $this; + } + + /** + * Asserts that the response has a status code in the [400-500[ range. + */ + public function assertClientError(): self + { + Assert::assertTrue($this->status->isClientError()); + + return $this; + } + + /** + * Asserts that the response has a status code in the 500 range. + */ + public function assertServerError(): self + { + Assert::assertTrue($this->status->isServerError()); + + return $this; + } + + /** + * Asserts that the response status matches the expected status. + */ public function assertStatus(Status $expected): self { Assert::assertSame( @@ -204,6 +262,55 @@ public function assertDoesNotHaveCookie(string $key, null|string|Closure $value return $this; } + public function assertHasForm(Closure $closure): self + { + $this->assertHasContainer(); + + $formSession = $this->container->get(FormSession::class); + + if (false === $closure($formSession)) { + Assert::fail('Failed validating form session.'); + } + + return $this; + } + + /** + * Asserts that the original form values in the session match the given values. + */ + public function assertHasFormOriginalValues(array $values): self + { + $this->assertHasContainer(); + + $formSession = $this->container->get(FormSession::class); + $originalValues = $formSession->values(); + + foreach ($values as $key => $expectedValue) { + Assert::assertArrayHasKey( + key: $key, + array: $originalValues, + message: sprintf( + 'No original form value was set for [%s], available original form values: %s', + $key, + implode(', ', array_keys($originalValues)), + ), + ); + + Assert::assertEquals( + expected: $expectedValue, + actual: $originalValues[$key], + message: sprintf( + 'Original form value for [%s] does not match expected value. Expected: %s, Actual: %s', + $key, + var_export($expectedValue, return: true), + var_export($originalValues[$key], return: true), + ), + ); + } + + return $this; + } + public function assertHasSession(string $key, ?Closure $callback = null): self { $this->assertHasContainer(); @@ -229,8 +336,11 @@ public function assertHasSession(string $key, ?Closure $callback = null): self public function assertHasValidationError(string $key, ?Closure $callback = null): self { - $session = $this->container->get(Session::class); - $validationErrors = $session->get(Session::VALIDATION_ERRORS) ?? []; + $validationErrors = $this->response->getHeader('x-validation')->first(); + + Assert::assertNotNull($validationErrors, 'The response does not have a x-validation header.'); + + $validationErrors = Json\decode($validationErrors); Assert::assertArrayHasKey( key: $key, @@ -251,8 +361,8 @@ public function assertHasValidationError(string $key, ?Closure $callback = null) public function assertHasNoValidationsErrors(): self { - $session = $this->container->get(Session::class); - $validationErrors = $session->get(Session::VALIDATION_ERRORS) ?? []; + $formSession = $this->container->get(FormSession::class); + $validationErrors = $formSession->getErrors(); Assert::assertEmpty( actual: $validationErrors, @@ -274,6 +384,10 @@ public function assertSee(string $search): self $body = $this->container->get(ViewRenderer::class)->render($body); } + if (is_array($body)) { + $body = json_encode($body); + } + Assert::assertStringContainsString($search, $body); return $this; @@ -430,7 +544,7 @@ public function assertViewModel(string $expected, ?Closure $callback = null): se * * @param array $expected */ - public function assertJson(array $expected): self + public function assertJson(array $expected = []): self { Assert::assertEquals( expected: arr($expected)->undot()->toArray(), @@ -491,9 +605,7 @@ public function assertJsonContains(array $expected): self } /** - * Asserts the response contains the given JSON validation errors. - * - * The keys can also be specified using dot notation. + * Asserts the response contains the given JSON validation errors. The keys can be specified using dot notation, and the values may contain `sprintf` placeholders. * * ### Example * ``` @@ -503,26 +615,26 @@ public function assertJsonContains(array $expected): self * ]); * ``` * - * @param array $expectedErrors + * @param array $expectedErrors */ public function assertHasJsonValidationErrors(array $expectedErrors): self { $this->assertHasContainer(); - Assert::assertInstanceOf(Invalid::class, $this->response); - Assert::assertContains($this->response->status, [Status::BAD_REQUEST, Status::FOUND]); - Assert::assertNotNull($this->response->getHeader('x-validation')); + Assert::assertContains($this->response->status, [Status::BAD_REQUEST, Status::FOUND, Status::UNPROCESSABLE_CONTENT]); - $session = $this->container->get(Session::class); - $validator = $this->container->get(Validator::class); - $validationRules = arr($session->get(Session::VALIDATION_ERRORS))->dot(); + $validationErrors = $this->response->getHeader('x-validation')->first(); + + Assert::assertNotNull($validationErrors, 'The response does not have a x-validation header.'); + + $validationErrors = Arr\dot(Json\decode($validationErrors)); - arr($expectedErrors) - ->dot() - ->each(fn ($expectedErrorValue, $expectedErrorKey) => Assert::assertEquals( - expected: $expectedErrorValue, - actual: $validator->getErrorMessage($validationRules->get($expectedErrorKey)), - )); + foreach (Arr\dot($expectedErrors) as $key => $expectedMessage) { + Assert::assertStringMatchesFormat( + format: $expectedMessage, + string: $validationErrors[$key], + ); + } return $this; } @@ -539,17 +651,22 @@ public function assertHasJsonValidationErrors(array $expectedErrors): self public function assertHasNoJsonValidationErrors(): self { Assert::assertNotContains($this->response->status, [Status::BAD_REQUEST, Status::FOUND]); - Assert::assertNotInstanceOf(Invalid::class, $this->response); Assert::assertNull($this->response->getHeader('x-validation')); return $this; } /** + * Dumps the response and die. + * * @mago-expect lint:no-debug-symbols */ public function dd(): never { + if ($this->throwable !== null) { + dump(sprintf('There was a [%s] exception during this request handling: %s', $this->throwable::class, $this->throwable->getMessage())); // @phpstan-ignore disallowed.function + } + dd($this->response); // @phpstan-ignore disallowed.function } diff --git a/src/Tempest/Framework/Testing/Http/TestingResponseSender.php b/src/Tempest/Framework/Testing/Http/TestingResponseSender.php new file mode 100644 index 0000000000..e255902dae --- /dev/null +++ b/src/Tempest/Framework/Testing/Http/TestingResponseSender.php @@ -0,0 +1,19 @@ +responses[] = $response; + + return $response; + } +} diff --git a/src/Tempest/Framework/Testing/IntegrationTest.php b/src/Tempest/Framework/Testing/IntegrationTest.php index 08ea0e93d4..7c600b78ba 100644 --- a/src/Tempest/Framework/Testing/IntegrationTest.php +++ b/src/Tempest/Framework/Testing/IntegrationTest.php @@ -16,17 +16,15 @@ use Tempest\Console\OutputBuffer; use Tempest\Console\Testing\ConsoleTester; use Tempest\Container\GenericContainer; -use Tempest\Core\AppConfig; -use Tempest\Core\ExceptionTester; +use Tempest\Core\Exceptions\ExceptionTester; use Tempest\Core\FrameworkKernel; use Tempest\Core\Kernel; -use Tempest\Database\Migrations\CreateMigrationsTable; -use Tempest\Database\Migrations\MigrationManager; use Tempest\Database\Testing\DatabaseTester; use Tempest\DateTime\DateTimeInterface; use Tempest\Discovery\DiscoveryLocation; use Tempest\EventBus\Testing\EventBusTester; use Tempest\Framework\Testing\Http\HttpRouterTester; +use Tempest\Framework\Testing\View\ViewTester; use Tempest\Http\GenericRequest; use Tempest\Http\Method; use Tempest\Http\Request; @@ -52,36 +50,72 @@ abstract class IntegrationTest extends TestCase /** @var \Tempest\Discovery\DiscoveryLocation[] */ protected array $discoveryLocations = []; - protected AppConfig $appConfig; - protected Kernel $kernel; protected GenericContainer $container; protected ConsoleTester $console; + /** + * Provides utilities for testing HTTP routes. + */ protected HttpRouterTester $http; + /** + * Provides utilities for testing installers. + */ protected InstallerTester $installer; + /** + * Provides utilities for testing the Vite integration. + */ protected ViteTester $vite; + /** + * Provides utilities for testing the event bus. + */ protected EventBusTester $eventBus; + /** + * Provides utilities for testing storage management. + */ protected StorageTester $storage; + /** + * Provides utilities for testing emails. + */ protected MailTester $mailer; + /** + * Provides utilities for testing the cache. + */ protected CacheTester $cache; + /** + * Provides utilities for testing exception reporting. + */ protected ExceptionTester $exceptions; + /** + * Provides utilities for testing process execution. + */ protected ProcessTester $process; + /** + * Provides utilities for testing OAuth flows. + */ protected OAuthTester $oauth; + /** + * Provides utilities for testing the database. + */ protected DatabaseTester $database; + /** + * Provides utilities for testing views. + */ + protected ViewTester $view; + protected function setUp(): void { parent::setUp(); @@ -127,8 +161,6 @@ protected function setupKernel(): self $container = $this->kernel->container; $this->container = $container; - $this->appConfig = $this->container->get(className: AppConfig::class); - return $this; } @@ -154,7 +186,7 @@ protected function setupTesters(): self $this->process->disableProcessExecution(); $this->exceptions = $this->container->get(ExceptionTester::class); - $this->exceptions->preventReporting(); + $this->exceptions->preventProcessing(); $this->vite = $this->container->get(ViteTester::class); $this->vite->preventTagResolution(); @@ -162,6 +194,7 @@ protected function setupTesters(): self $this->oauth = new OAuthTester($this->container); $this->database = new DatabaseTester($this->container); + $this->view = new ViewTester($this->container); return $this; } @@ -175,41 +208,6 @@ protected function setupBaseRequest(): self return $this; } - /** - * Cleans up the database and migrates the migrations using `migrateDatabase`. - * - * @deprecated Use `$this->database->setup()` instead. - */ - protected function setupDatabase(): self - { - $migrationManager = $this->container->get(MigrationManager::class); - $migrationManager->dropAll(); - - $this->migrateDatabase(); - - return $this; - } - - /** - * Creates the migration table. You may override this method to provide more migrations to run for every tests in this file. - * - * @deprecated Use `$this->database->migrate()` instead. - */ - protected function migrateDatabase(): void - { - $this->migrate(CreateMigrationsTable::class); - } - - /** - * Migrates the specified migration classes. - * - * @deprecated Use `$this->database->migrate()` instead. - */ - protected function migrate(string|object ...$migrationClasses): void - { - $this->database->migrate(...$migrationClasses); - } - protected function clock(DateTimeInterface|string $now = 'now'): MockClock { $clock = new MockClock($now); @@ -228,8 +226,6 @@ protected function tearDown(): void /** @phpstan-ignore-next-line */ unset($this->discoveryLocations); /** @phpstan-ignore-next-line */ - unset($this->appConfig); - /** @phpstan-ignore-next-line */ unset($this->kernel); /** @phpstan-ignore-next-line */ unset($this->container); diff --git a/src/Tempest/Framework/Testing/View/ViewTester.php b/src/Tempest/Framework/Testing/View/ViewTester.php new file mode 100644 index 0000000000..246ba1b1db --- /dev/null +++ b/src/Tempest/Framework/Testing/View/ViewTester.php @@ -0,0 +1,53 @@ +data(...$params); + + return $this->container->get(ViewRenderer::class)->render($view); + } + + /** + * Registers a view component for testing purposes. + */ + public function registerViewComponent(string $name, string $html, string $file = '', bool $isVendor = false): self + { + $viewComponent = new ViewComponent( + name: $name, + contents: $html, + file: $file, + isVendorComponent: $isVendor, + ); + + $this->container->get(ViewConfig::class)->addViewComponent($viewComponent); + + return $this; + } +} diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index 96d1c5cee0..ae61b4aa51 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -9,7 +9,7 @@ use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; use Tests\Tempest\Integration\CommandBus\Fixtures\MyFailingAsyncCommand; -use function Tempest\command; +use function Tempest\CommandBus\command; final readonly class DispatchAsyncCommand { diff --git a/tests/Fixtures/Controllers/FormController.php b/tests/Fixtures/Controllers/FormController.php index f7bff5f487..5c08c7afdb 100644 --- a/tests/Fixtures/Controllers/FormController.php +++ b/tests/Fixtures/Controllers/FormController.php @@ -7,7 +7,7 @@ use Tempest\Router\Get; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final readonly class FormController { diff --git a/tests/Fixtures/Controllers/HeaderWithUnderscoresController.php b/tests/Fixtures/Controllers/HeaderWithUnderscoresController.php new file mode 100644 index 0000000000..a0616b99b2 --- /dev/null +++ b/tests/Fixtures/Controllers/HeaderWithUnderscoresController.php @@ -0,0 +1,16 @@ +addHeader('tempest_session_id', $request->headers->get('tempest_session_id')); + } +} diff --git a/tests/Fixtures/Controllers/InferredConstraintsController.php b/tests/Fixtures/Controllers/InferredConstraintsController.php new file mode 100644 index 0000000000..1e6912f000 --- /dev/null +++ b/tests/Fixtures/Controllers/InferredConstraintsController.php @@ -0,0 +1,48 @@ +migrate(CreateMigrationsTable::class, CreateServiceAccountTableMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateServiceAccountTableMigration::class); $this->container->config(new AuthConfig(authenticatables: [ServiceAccount::class])); } diff --git a/tests/Integration/Auth/Authentication/DatabaseAuthenticatableResolverTest.php b/tests/Integration/Auth/Authentication/DatabaseAuthenticatableResolverTest.php index 6cbe4d880e..f11ecea7c5 100644 --- a/tests/Integration/Auth/Authentication/DatabaseAuthenticatableResolverTest.php +++ b/tests/Integration/Auth/Authentication/DatabaseAuthenticatableResolverTest.php @@ -21,7 +21,7 @@ final class DatabaseAuthenticatableResolverTest extends FrameworkIntegrationTest #[Test] public function can_resolve_custom_authenticatable_class(): void { - $this->migrate(CreateMigrationsTable::class, CreateApiTokensTableMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateApiTokensTableMigration::class); $this->container->config(new AuthConfig(authenticatables: [ApiToken::class])); @@ -37,7 +37,7 @@ public function can_resolve_custom_authenticatable_class(): void #[Test] public function can_resolve_id_from_custom_authenticatable_class(): void { - $this->migrate(CreateMigrationsTable::class, CreateApiTokensTableMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateApiTokensTableMigration::class); $this->container->config(new AuthConfig(authenticatables: [ApiToken::class])); diff --git a/tests/Integration/Auth/Authentication/SessionAuthenticatorTest.php b/tests/Integration/Auth/Authentication/SessionAuthenticatorTest.php index eaae6fc5f3..e20251574e 100644 --- a/tests/Integration/Auth/Authentication/SessionAuthenticatorTest.php +++ b/tests/Integration/Auth/Authentication/SessionAuthenticatorTest.php @@ -50,7 +50,7 @@ protected function configure(): void $this->container->get(SessionConfig::class), )); - $this->migrate(CreateMigrationsTable::class, CreateUsersTableMigration::class, CreateApiKeysTableMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUsersTableMigration::class, CreateApiKeysTableMigration::class); } #[PostCondition] diff --git a/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php new file mode 100644 index 0000000000..dae2801108 --- /dev/null +++ b/tests/Integration/Auth/Installer/AuthenticationInstallerTest.php @@ -0,0 +1,40 @@ +installer + ->configure( + __DIR__ . '/install', + new Psr4Namespace('App\\', __DIR__ . '/install/App'), + ) + ->setRoot(__DIR__ . '/install') + ->put('.env.example', '') + ->put('.env', ''); + } + + protected function tearDown(): void + { + $this->installer->clean(); + + parent::tearDown(); + } + + #[Test] + public function install_oauth_provider_with_migrations(): void + { + $this->console + ->call('install auth --force --migrate') + ->input(0) + ->assertSuccess(); + } +} diff --git a/tests/Integration/Auth/Installer/OAuthInstallerTest.php b/tests/Integration/Auth/Installer/OAuthInstallerTest.php index 22e754b600..40a7eb5f1f 100644 --- a/tests/Integration/Auth/Installer/OAuthInstallerTest.php +++ b/tests/Integration/Auth/Installer/OAuthInstallerTest.php @@ -121,6 +121,11 @@ public static function oauthProvider(): array 'expectedConfigPath' => 'App/Authentication/OAuth/slack.config.php', 'expectedControllerPath' => 'App/Authentication/OAuth/SlackController.php', ], + 'twitch' => [ + 'provider' => SupportedOAuthProvider::TWITCH, + 'expectedConfigPath' => 'App/Authentication/OAuth/twitch.config.php', + 'expectedControllerPath' => 'App/Authentication/OAuth/TwitchController.php', + ], ]; } } diff --git a/tests/Integration/Cache/LockTest.php b/tests/Integration/Cache/LockTest.php index c3822f2bde..63db4ce91c 100644 --- a/tests/Integration/Cache/LockTest.php +++ b/tests/Integration/Cache/LockTest.php @@ -61,10 +61,10 @@ public function test_lock_with_ttl(): void $clock = $this->clock(); $cache = new GenericCache(new ArrayAdapter(clock: $clock->toPsrClock())); - $lock = $cache->lock('processing', expiration: Duration::hours(1)); + $lock = $cache->lock('processing', duration: Duration::hours(1)); $this->assertTrue($lock->acquire()); - $this->assertTrue($lock->expiration->equals($clock->now()->plusHours(1))); + $this->assertTrue($lock->duration->equals(Duration::hours(1))); // Still locked after 30 min $clock->plus(Duration::minutes(30)); @@ -73,7 +73,7 @@ public function test_lock_with_ttl(): void // No longer locked after another 30 min (total 1h) $clock->plus(Duration::minutes(30)); $this->assertTrue($lock->acquire()); - $this->assertFalse($lock->release()); + $this->assertTrue($lock->release()); } public function test_lock_execution_without_timeout(): void @@ -106,7 +106,7 @@ public function test_lock_execution_when_already_locked_by_another_owner_with_ti $cache = new GenericCache(new ArrayAdapter(clock: $clock->toPsrClock())); // Lock externally for a set duration - $externalLock = $cache->lock('processing', expiration: Duration::hours(1)); + $externalLock = $cache->lock('processing', duration: Duration::hours(1)); $externalLock->acquire(); // Skip the set duration @@ -116,4 +116,31 @@ public function test_lock_execution_when_already_locked_by_another_owner_with_ti /** @phpstan-ignore-next-line */ $this->assertTrue($cache->lock('processing')->execute(fn () => true, wait: Duration::hours(1))); } + + public function test_lock_can_be_reacquired_after_expiration(): void + { + $clock = $this->clock(); + $cache = new GenericCache(new ArrayAdapter(clock: $clock->toPsrClock())); + + $lock = $cache->lock('processing', duration: Duration::hours(1)); + + // Acquire the lock + $this->assertTrue($lock->acquire()); + + // Skip the lock duration + $clock->plus(Duration::hours(1)); + + // Lock expired, so we can re-acquire it + $this->assertTrue($lock->acquire()); + + // Verify the lock is held + $this->assertTrue($lock->locked()); + + // Skip another hour + $clock->plus(Duration::hours(1)); + + // Lock expired again, so we can re-acquire it again + $this->assertTrue($lock->acquire()); + $this->assertTrue($lock->release()); + } } diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index d43c014d3a..2899542c34 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -12,7 +12,7 @@ use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\command; +use function Tempest\CommandBus\command; use function Tempest\Support\arr; /** diff --git a/tests/Integration/CommandBus/CommandBusTest.php b/tests/Integration/CommandBus/CommandBusTest.php index 2396cce630..b1bac0f315 100644 --- a/tests/Integration/CommandBus/CommandBusTest.php +++ b/tests/Integration/CommandBus/CommandBusTest.php @@ -12,7 +12,7 @@ use Tests\Tempest\Fixtures\Commands\MyCommandBusMiddleware; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\command; +use function Tempest\CommandBus\command; /** * @internal diff --git a/tests/Integration/Console/Commands/CompleteCommandTest.php b/tests/Integration/Console/Commands/CompleteCommandTest.php index 12462a380f..a61125e253 100644 --- a/tests/Integration/Console/Commands/CompleteCommandTest.php +++ b/tests/Integration/Console/Commands/CompleteCommandTest.php @@ -4,6 +4,7 @@ namespace Tests\Tempest\Integration\Console\Commands; +use PHPUnit\Framework\Attributes\Test; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; /** @@ -11,20 +12,22 @@ */ final class CompleteCommandTest extends FrameworkIntegrationTestCase { - public function test_complete_commands(): void + #[Test] + public function complete_commands(): void { $this->console ->complete() - ->assertSee('tail:server' . PHP_EOL) + ->assertSee('migrate:up' . PHP_EOL) ->assertSee('schedule:run' . PHP_EOL); } - public function test_complete_arguments(): void + #[Test] + public function complete_arguments(): void { $this->console - ->complete('tail:') - ->assertSee('tail:server' . PHP_EOL) - ->assertSee('tail:project' . PHP_EOL) - ->assertSee('tail:debug' . PHP_EOL); + ->complete('migrate:') + ->assertSee('migrate:down' . PHP_EOL) + ->assertSee('migrate:up' . PHP_EOL) + ->assertSee('migrate:rehash' . PHP_EOL); } } diff --git a/tests/Integration/Console/Commands/CompletionInstallCommandTest.php b/tests/Integration/Console/Commands/CompletionInstallCommandTest.php new file mode 100644 index 0000000000..ac5d80ccf0 --- /dev/null +++ b/tests/Integration/Console/Commands/CompletionInstallCommandTest.php @@ -0,0 +1,123 @@ +installedFile !== null && Filesystem\is_file($this->installedFile)) { + Filesystem\delete_file($this->installedFile); + $this->installedFile = null; + } + + parent::tearDown(); + } + + #[Test] + public function install_with_explicit_shell_flag(): void + { + $this->installedFile = Shell::ZSH->getInstalledCompletionPath(); + + $this->console + ->call('completion:install --shell=zsh --force') + ->assertSee('Installed completion script to:') + ->assertSee('_tempest') + ->assertSuccess(); + } + + #[Test] + public function install_with_invalid_shell(): void + { + $this->console + ->withoutPrompting() + ->call('completion:install --shell=fish') + ->assertSee('Invalid argument `fish` for `shell` argument') + ->assertError(); + } + + #[Test] + public function install_shows_post_install_instructions_for_zsh(): void + { + $this->installedFile = Shell::ZSH->getInstalledCompletionPath(); + + $this->console + ->call('completion:install --shell=zsh --force') + ->assertSee('fpath=') + ->assertSee('compinit') + ->assertSuccess(); + } + + #[Test] + public function install_shows_post_install_instructions_for_bash(): void + { + $this->installedFile = Shell::BASH->getInstalledCompletionPath(); + + $this->console + ->call('completion:install --shell=bash --force') + ->assertSee('source') + ->assertSee('tempest.bash') + ->assertSuccess(); + } + + #[Test] + public function install_cancelled_when_user_denies_confirmation(): void + { + $this->console + ->call('completion:install --shell=zsh') + ->assertSee('Installing zsh completions') + ->deny() + ->assertSee('Installation cancelled') + ->assertCancelled(); + } + + #[Test] + public function install_creates_directory_if_not_exists(): void + { + $targetDir = Shell::ZSH->getCompletionsDirectory(); + $dirExisted = Filesystem\is_directory($targetDir); + + $this->installedFile = Shell::ZSH->getInstalledCompletionPath(); + + $result = $this->console + ->call('completion:install --shell=zsh --force'); + + if (! $dirExisted) { + $result->assertSee('Created directory:'); + } + + $result->assertSuccess(); + } + + #[Test] + public function install_asks_for_overwrite_when_file_exists(): void + { + $targetPath = Shell::ZSH->getInstalledCompletionPath(); + $targetDir = Shell::ZSH->getCompletionsDirectory(); + + Filesystem\create_directory($targetDir); + Filesystem\write_file($targetPath, '# existing content'); + + $this->installedFile = $targetPath; + + $this->console + ->call('completion:install --shell=zsh') + ->confirm() + ->assertSee('Completion file already exists') + ->deny() + ->assertSee('Installation cancelled') + ->assertCancelled(); + } +} diff --git a/tests/Integration/Console/Commands/CompletionShowCommandTest.php b/tests/Integration/Console/Commands/CompletionShowCommandTest.php new file mode 100644 index 0000000000..d4c1b32510 --- /dev/null +++ b/tests/Integration/Console/Commands/CompletionShowCommandTest.php @@ -0,0 +1,42 @@ +console + ->call('completion:show --shell=zsh') + ->assertSee('_tempest') + ->assertSuccess(); + } + + #[Test] + public function show_bash_completion_script(): void + { + $this->console + ->call('completion:show --shell=bash') + ->assertSee('_tempest') + ->assertSuccess(); + } + + #[Test] + public function show_with_invalid_shell(): void + { + $this->console + ->withoutPrompting() + ->call('completion:show --shell=fish') + ->assertSee('Invalid argument `fish` for `shell` argument') + ->assertError(); + } +} diff --git a/tests/Integration/Console/Commands/CompletionUninstallCommandTest.php b/tests/Integration/Console/Commands/CompletionUninstallCommandTest.php new file mode 100644 index 0000000000..4f042f4583 --- /dev/null +++ b/tests/Integration/Console/Commands/CompletionUninstallCommandTest.php @@ -0,0 +1,98 @@ +getInstalledCompletionPath(); + $targetDir = Shell::ZSH->getCompletionsDirectory(); + + Filesystem\create_directory($targetDir); + Filesystem\write_file($targetPath, '# completion script'); + + $this->console + ->call('completion:uninstall --shell=zsh --force') + ->assertSee('Removed completion script:') + ->assertSee('_tempest') + ->assertSuccess(); + + $this->assertFalse(Filesystem\is_file($targetPath)); + } + + #[Test] + public function uninstall_with_invalid_shell(): void + { + $this->console + ->withoutPrompting() + ->call('completion:uninstall --shell=fish') + ->assertSee('Invalid argument `fish` for `shell` argument') + ->assertError(); + } + + #[Test] + public function uninstall_when_file_not_exists(): void + { + $targetPath = Shell::ZSH->getInstalledCompletionPath(); + + if (Filesystem\is_file($targetPath)) { + Filesystem\delete_file($targetPath); + } + + $this->console + ->withoutPrompting() + ->call('completion:uninstall --shell=zsh --force') + ->assertSee('Completion file not found') + ->assertSee('Nothing to uninstall') + ->assertSuccess(); + } + + #[Test] + public function uninstall_shows_config_file_reminder(): void + { + $targetPath = Shell::BASH->getInstalledCompletionPath(); + $targetDir = Shell::BASH->getCompletionsDirectory(); + + Filesystem\create_directory($targetDir); + Filesystem\write_file($targetPath, '# completion script'); + + $this->console + ->call('completion:uninstall --shell=bash --force') + ->assertSee('Remember to remove any related lines') + ->assertSee('.bashrc') + ->assertSuccess(); + } + + #[Test] + public function uninstall_cancelled_when_user_denies_confirmation(): void + { + $targetPath = Shell::ZSH->getInstalledCompletionPath(); + $targetDir = Shell::ZSH->getCompletionsDirectory(); + + Filesystem\create_directory($targetDir); + Filesystem\write_file($targetPath, '# completion script'); + + $this->console + ->call('completion:uninstall --shell=zsh') + ->assertSee('Uninstalling zsh completions') + ->deny() + ->assertSee('Uninstallation cancelled') + ->assertCancelled(); + + $this->assertTrue(Filesystem\is_file($targetPath)); + + Filesystem\delete_file($targetPath); + } +} diff --git a/tests/Integration/Console/Middleware/CautionMiddlewareTest.php b/tests/Integration/Console/Middleware/CautionMiddlewareTest.php index b91f87c85e..c98b2692b0 100644 --- a/tests/Integration/Console/Middleware/CautionMiddlewareTest.php +++ b/tests/Integration/Console/Middleware/CautionMiddlewareTest.php @@ -4,7 +4,8 @@ namespace Tests\Tempest\Integration\Console\Middleware; -use Tempest\Core\AppConfig; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestWith; use Tempest\Core\Environment; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -13,17 +14,22 @@ */ final class CautionMiddlewareTest extends FrameworkIntegrationTestCase { - public function test_in_local(): void + #[Test] + public function in_local(): void { + $this->container->singleton(Environment::class, Environment::LOCAL); + $this->console ->call('caution') ->assertContains('CAUTION confirmed'); } - public function test_in_production(): void + #[Test] + #[TestWith([Environment::PRODUCTION])] + #[TestWith([Environment::STAGING])] + public function in_caution_environments(Environment $environment): void { - $appConfig = $this->container->get(AppConfig::class); - $appConfig->environment = Environment::PRODUCTION; + $this->container->singleton(Environment::class, $environment); $this->console ->call('caution') diff --git a/tests/Integration/Container/TaggedDynamicInitializerTest.php b/tests/Integration/Container/TaggedDynamicInitializerTest.php index 815d47f1e1..19b0abc64b 100644 --- a/tests/Integration/Container/TaggedDynamicInitializerTest.php +++ b/tests/Integration/Container/TaggedDynamicInitializerTest.php @@ -7,12 +7,14 @@ use Tempest\Database\Config\SQLiteConfig; use Tempest\Database\Database; use Tempest\Database\DatabaseInitializer; +use Tempest\Mapper\SerializerFactory; final class TaggedDynamicInitializerTest extends TestCase { public function test_resolve(): void { $container = new GenericContainer(); + $container->singleton(SerializerFactory::class, new SerializerFactory($container)); $container->addInitializer(DatabaseInitializer::class); $container->config(new SQLiteConfig( diff --git a/tests/Integration/Core/AppConfigTest.php b/tests/Integration/Core/AppConfigTest.php index 2d5a24f450..a47e9ceafe 100644 --- a/tests/Integration/Core/AppConfigTest.php +++ b/tests/Integration/Core/AppConfigTest.php @@ -4,8 +4,8 @@ namespace Tests\Tempest\Integration\Core; +use PHPUnit\Framework\Attributes\Test; use Tempest\Core\AppConfig; -use Tempest\Core\Environment; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; /** @@ -13,11 +13,12 @@ */ final class AppConfigTest extends FrameworkIntegrationTestCase { - public function test_defaults(): void + #[Test] + public function defaults(): void { $appConfig = $this->container->get(AppConfig::class); - $this->assertSame(Environment::TESTING, $appConfig->environment); $this->assertSame('', $appConfig->baseUri); + $this->assertSame(null, $appConfig->name); } } diff --git a/tests/Integration/Core/Config/LoadConfigTest.php b/tests/Integration/Core/Config/LoadConfigTest.php index 10a20779ef..365fff006f 100644 --- a/tests/Integration/Core/Config/LoadConfigTest.php +++ b/tests/Integration/Core/Config/LoadConfigTest.php @@ -2,7 +2,6 @@ namespace Tests\Tempest\Integration\Core\Config; -use Tempest\Core\AppConfig; use Tempest\Core\ConfigCache; use Tempest\Core\Environment; use Tempest\Core\Kernel\LoadConfig; @@ -64,7 +63,8 @@ public function test_non_production_configs_are_discarded_in_production(): void 'db.config.php', ]); - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; + $this->container->singleton(Environment::class, Environment::PRODUCTION); + $config = $this->container->get(LoadConfig::class)->find(); $this->assertCount(2, $config); @@ -82,7 +82,8 @@ public function test_non_staging_configs_are_discarded_in_staging(): void 'db.config.php', ]); - $this->container->get(AppConfig::class)->environment = Environment::STAGING; + $this->container->singleton(Environment::class, Environment::STAGING); + $config = $this->container->get(LoadConfig::class)->find(); $this->assertCount(2, $config); @@ -100,7 +101,8 @@ public function test_non_dev_configs_are_discarded_in_dev(): void 'db.config.php', ]); - $this->container->get(AppConfig::class)->environment = Environment::LOCAL; + $this->container->singleton(Environment::class, Environment::LOCAL); + $config = $this->container->get(LoadConfig::class)->find(); $this->assertCount(3, $config); diff --git a/tests/Integration/Core/DiscoveryCacheTest.php b/tests/Integration/Core/DiscoveryCacheTest.php index 69fb4014ff..270a657ddf 100644 --- a/tests/Integration/Core/DiscoveryCacheTest.php +++ b/tests/Integration/Core/DiscoveryCacheTest.php @@ -2,17 +2,28 @@ namespace Tests\Tempest\Integration\Core; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\Test; use Tempest\Core\CouldNotStoreDiscoveryCache; use Tempest\Core\DiscoveryCache; +use Tempest\Core\DiscoveryCacheStrategy; use Tempest\Discovery\DiscoveryLocation; use Tests\Tempest\Integration\Core\Fixtures\TestDiscovery; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\reflect; +use function Tempest\Reflection\reflect; final class DiscoveryCacheTest extends FrameworkIntegrationTestCase { - public function test_exception_with_unserializable_discovery_items(): void + #[PostCondition] + protected function cleanup(): void + { + putenv('ENVIRONMENT=testing'); + putenv('DISCOVERY_CACHE=true'); + } + + #[Test] + public function exception_with_unserializable_discovery_items(): void { $this->assertException(CouldNotStoreDiscoveryCache::class, function (): void { $discoveryCache = $this->container->get(DiscoveryCache::class); @@ -26,4 +37,49 @@ public function test_exception_with_unserializable_discovery_items(): void ]); }); } + + #[Test] + public function partial_locally(): void + { + putenv('ENVIRONMENT=local'); + putenv('DISCOVERY_CACHE=null'); + + $this->assertSame(DiscoveryCacheStrategy::PARTIAL, DiscoveryCacheStrategy::resolveFromEnvironment()); + } + + #[Test] + public function overridable_locally(): void + { + putenv('ENVIRONMENT=local'); + putenv('DISCOVERY_CACHE=false'); + + $this->assertSame(DiscoveryCacheStrategy::NONE, DiscoveryCacheStrategy::resolveFromEnvironment()); + } + + #[Test] + public function enabled_in_production(): void + { + putenv('ENVIRONMENT=production'); + putenv('DISCOVERY_CACHE=null'); + + $this->assertSame(DiscoveryCacheStrategy::FULL, DiscoveryCacheStrategy::resolveFromEnvironment()); + } + + #[Test] + public function enabled_in_staging(): void + { + putenv('ENVIRONMENT=staging'); + putenv('DISCOVERY_CACHE=null'); + + $this->assertSame(DiscoveryCacheStrategy::FULL, DiscoveryCacheStrategy::resolveFromEnvironment()); + } + + #[Test] + public function overridable_in_production(): void + { + putenv('ENVIRONMENT=production'); + putenv('DISCOVERY_CACHE=partial'); + + $this->assertSame(DiscoveryCacheStrategy::PARTIAL, DiscoveryCacheStrategy::resolveFromEnvironment()); + } } diff --git a/tests/Integration/Core/ExceptionProcessorTest.php b/tests/Integration/Core/ExceptionProcessorTest.php index 55669e7134..fb4481ce84 100644 --- a/tests/Integration/Core/ExceptionProcessorTest.php +++ b/tests/Integration/Core/ExceptionProcessorTest.php @@ -2,16 +2,38 @@ namespace Tests\Tempest\Integration\Core; -use Tempest\Core\AppConfig; -use Tempest\Core\LogExceptionProcessor; +use Exception; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; +use Tempest\Core\Exceptions\ExceptionProcessor; +use Tempest\Core\Exceptions\ExceptionsConfig; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; +use Tests\Tempest\Integration\Http\Fixtures\NullExceptionReporter; final class ExceptionProcessorTest extends FrameworkIntegrationTestCase { - public function test_exception_processors_are_discovered(): void + #[PreCondition] + protected function configure(): void { - $processors = $this->container->get(AppConfig::class)->exceptionProcessors; + $this->exceptions->allowProcessing(); + $this->container->get(ExceptionsConfig::class)->setReporters([NullExceptionReporter::class]); + } + + #[PostCondition] + protected function cleanup(): void + { + NullExceptionReporter::$exceptions = []; + } + + #[Test] + public function exception_reporter_processes_exception_processors(): void + { + $processor = $this->container->get(ExceptionProcessor::class); + $processor->process(new Exception('foo')); - $this->assertContains(LogExceptionProcessor::class, $processors); + $this->assertCount(1, NullExceptionReporter::$exceptions); + $this->assertInstanceOf(Exception::class, NullExceptionReporter::$exceptions[0]); + $this->assertSame('foo', NullExceptionReporter::$exceptions[0]->getMessage()); } } diff --git a/tests/Integration/Core/ExceptionReporterTest.php b/tests/Integration/Core/ExceptionReporterTest.php index 5501209ee2..f53d24c5d5 100644 --- a/tests/Integration/Core/ExceptionReporterTest.php +++ b/tests/Integration/Core/ExceptionReporterTest.php @@ -2,59 +2,24 @@ namespace Tests\Tempest\Integration\Core; -use Exception; -use Tempest\Core\AppConfig; -use Tempest\Core\ExceptionReporter; +use PHPUnit\Framework\Attributes\Test; +use Tempest\Core\Exceptions\ExceptionsConfig; +use Tempest\Core\Exceptions\LoggingExceptionReporter; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use Tests\Tempest\Integration\Http\Fixtures\NullExceptionProcessor; - -use function Tempest\report; final class ExceptionReporterTest extends FrameworkIntegrationTestCase { - protected function setUp(): void + #[Test] + public function logging_reporter_is_discovered(): void { - parent::setUp(); - - $this->exceptions->preventReporting(prevent: false); - $this->container->get(AppConfig::class)->exceptionProcessors = [NullExceptionProcessor::class]; + $this->assertContains(LoggingExceptionReporter::class, $this->container->get(ExceptionsConfig::class)->reporters); } - protected function tearDown(): void + #[Test] + public function logging_reporter_can_be_disabled_through_config(): void { - parent::tearDown(); - - NullExceptionProcessor::$exceptions = []; - } - - public function test_exception_reporter_processes_exception_processors(): void - { - /** @var ExceptionReporter $reporter */ - $reporter = $this->container->get(ExceptionReporter::class); - $reporter->report(new Exception('foo')); - - $this->assertCount(1, NullExceptionProcessor::$exceptions); - $this->assertInstanceOf(Exception::class, NullExceptionProcessor::$exceptions[0]); - $this->assertSame('foo', NullExceptionProcessor::$exceptions[0]->getMessage()); - - $this->assertCount(1, $reporter->reported); - $this->assertInstanceOf(Exception::class, $reporter->reported[0]); - $this->assertSame('foo', $reporter->reported[0]->getMessage()); - } - - public function test_report_function(): void - { - report(new Exception('foo')); - - $this->assertCount(1, NullExceptionProcessor::$exceptions); - $this->assertInstanceOf(Exception::class, NullExceptionProcessor::$exceptions[0]); - $this->assertSame('foo', NullExceptionProcessor::$exceptions[0]->getMessage()); - - /** @var ExceptionReporter $reporter */ - $reporter = $this->container->get(ExceptionReporter::class); + $this->container->config(new ExceptionsConfig(logging: false)); - $this->assertCount(1, $reporter->reported); - $this->assertInstanceOf(Exception::class, $reporter->reported[0]); - $this->assertSame('foo', $reporter->reported[0]->getMessage()); + $this->assertEmpty($this->container->get(ExceptionsConfig::class)->reporters); } } diff --git a/tests/Integration/Core/ExceptionTesterTest.php b/tests/Integration/Core/ExceptionTesterTest.php index 0b65e981bd..c39aa9e18f 100644 --- a/tests/Integration/Core/ExceptionTesterTest.php +++ b/tests/Integration/Core/ExceptionTesterTest.php @@ -4,36 +4,55 @@ use Exception; use InvalidArgumentException; -use Tempest\Core\AppConfig; -use Tempest\Core\ExceptionReporter; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; +use Tempest\Core\Exceptions\ExceptionProcessor; +use Tempest\Core\Exceptions\ExceptionsConfig; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use Tests\Tempest\Integration\Http\Fixtures\NullExceptionProcessor; - -use function Tempest\report; +use Tests\Tempest\Integration\Http\Fixtures\NullExceptionReporter; final class ExceptionTesterTest extends FrameworkIntegrationTestCase { - public function test_assert_reported(): void - { - $this->exceptions->assertNothingReported(); + private ExceptionProcessor $processor { + get => $this->container->get(ExceptionProcessor::class); + } - $this->container->get(ExceptionReporter::class)->report(new Exception('foo')); - report(new Exception('bar')); + #[PreCondition] + protected function configure(): void + { + $this->container + ->get(ExceptionsConfig::class) + ->setReporters([NullExceptionReporter::class]); + } - $this->exceptions->assertReported(Exception::class, count: 2); - $this->exceptions->assertNotReported(InvalidArgumentException::class); + #[PostCondition] + protected function cleanup(): void + { + NullExceptionReporter::$exceptions = []; } - public function test_prevent_reporting(): void + #[Test] + public function assert_reported(): void { - $this->container->get(AppConfig::class)->exceptionProcessors = [NullExceptionProcessor::class]; + $this->exceptions->assertNothingProcessed(); + + $this->processor->process(new Exception('foo')); - $this->exceptions->preventReporting(); + $this->exceptions->assertProcessed(Exception::class, count: 1); + $this->exceptions->assertNotProcessed(InvalidArgumentException::class); + } + + #[Test] + public function prevent_reporting(): void + { + $this->exceptions->preventProcessing(); - $this->container->get(ExceptionReporter::class)->report(new Exception('foo')); + $this->processor->process(new Exception('foo')); - $this->exceptions->assertReported(Exception::class); + $this->exceptions->assertProcessed(Exception::class); + $this->exceptions->assertProcessed(Exception::class, count: 1); - $this->assertEmpty(NullExceptionProcessor::$exceptions); + $this->assertEmpty(NullExceptionReporter::$exceptions); } } diff --git a/tests/Integration/Core/Exceptions/LoggingExceptionReporterTest.php b/tests/Integration/Core/Exceptions/LoggingExceptionReporterTest.php new file mode 100644 index 0000000000..952d893e7f --- /dev/null +++ b/tests/Integration/Core/Exceptions/LoggingExceptionReporterTest.php @@ -0,0 +1,132 @@ +report(new Exception('Something went wrong')); + + $this->assertCount(2, $logger->logs); + $this->assertSame('error', $logger->logs[0]['level']); + $this->assertSame('Something went wrong', $logger->logs[0]['message']); + } + + #[Test] + public function logs_exception_with_fallback_message(): void + { + $logger = new TestLogger(); + $reporter = new LoggingExceptionReporter($logger); + $reporter->report(new Exception('')); + + $this->assertCount(2, $logger->logs); + $this->assertSame('error', $logger->logs[0]['level']); + $this->assertSame('(no message)', $logger->logs[0]['message']); + } + + #[Test] + public function logs_exception_trace_separately(): void + { + $logger = new TestLogger(); + $reporter = new LoggingExceptionReporter($logger); + $reporter->report(new Exception('Test')); + + $this->assertCount(2, $logger->logs); + $this->assertSame('error', $logger->logs[1]['level']); + $this->assertIsString($logger->logs[1]['message']); + $this->assertStringStartsWith('#0 ', $logger->logs[1]['message']); + } + + #[Test] + public function logs_exception_with_context_when_exception_provides_context(): void + { + $logger = new TestLogger(); + $reporter = new LoggingExceptionReporter($logger); + $reporter->report(new ExceptionWithContext('Test')); + + $this->assertCount(2, $logger->logs); + $this->assertSame(['foo' => 'bar', 'baz' => 'qux'], $logger->logs[0]['context']); + } + + #[Test] + public function logs_exception_with_empty_context_when_exception_does_not_provide_context(): void + { + $logger = new TestLogger(); + $reporter = new LoggingExceptionReporter($logger); + $reporter->report(new Exception('Test')); + + $this->assertCount(2, $logger->logs); + $this->assertEmpty($logger->logs[0]['context']); + } +} + +final class TestLogger implements Logger +{ + public array $logs = []; + + public function emergency(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'emergency', 'message' => $message, 'context' => $context]; + } + + public function alert(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'alert', 'message' => $message, 'context' => $context]; + } + + public function critical(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'critical', 'message' => $message, 'context' => $context]; + } + + public function error(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'error', 'message' => $message, 'context' => $context]; + } + + public function warning(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'warning', 'message' => $message, 'context' => $context]; + } + + public function notice(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'notice', 'message' => $message, 'context' => $context]; + } + + public function info(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'info', 'message' => $message, 'context' => $context]; + } + + public function debug(string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => 'debug', 'message' => $message, 'context' => $context]; + } + + public function log(mixed $level, string|\Stringable $message, array $context = []): void + { + $this->logs[] = ['level' => $level, 'message' => $message, 'context' => $context]; + } +} + +final class ExceptionWithContext extends Exception implements ProvidesContext +{ + public function context(): iterable + { + return ['foo' => 'bar', 'baz' => 'qux']; + } +} diff --git a/tests/Integration/Core/LoadDiscoveryClassesTest.php b/tests/Integration/Core/LoadDiscoveryClassesTest.php index 9768277e29..29a21db43b 100644 --- a/tests/Integration/Core/LoadDiscoveryClassesTest.php +++ b/tests/Integration/Core/LoadDiscoveryClassesTest.php @@ -9,6 +9,7 @@ use Tempest\Database\MigratesUp; use Tempest\Database\Migrations\RunnableMigrations; use Tempest\Discovery\DiscoveryLocation; +use Tempest\Support\Arr; use Tests\Tempest\Fixtures\Discovery\HiddenDatabaseMigration; use Tests\Tempest\Fixtures\Discovery\HiddenMigratableDatabaseMigration; use Tests\Tempest\Fixtures\GlobalHiddenDiscovery; @@ -17,8 +18,6 @@ use Tests\Tempest\Integration\Core\Fixtures\ManualTestDiscoveryDependency; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\get; - /** * @internal */ @@ -27,9 +26,9 @@ final class LoadDiscoveryClassesTest extends FrameworkIntegrationTestCase #[Test] public function do_not_discover(): void { - $migrations = get(RunnableMigrations::class); + $migrations = $this->container->get(RunnableMigrations::class); - $this->assertNotContains(HiddenDatabaseMigration::class, $migrations); + $this->assertFalse(Arr\contains($migrations, fn ($m) => $m instanceof HiddenDatabaseMigration)); } #[Test] @@ -47,11 +46,11 @@ public function do_not_discover_global_path(): void #[Test] public function do_not_discover_except(): void { - $migrations = get(RunnableMigrations::class); + $migrations = $this->container->get(RunnableMigrations::class); - $foundMigrations = array_filter( - iterator_to_array($migrations), - static fn (MigratesUp $migration) => $migration instanceof HiddenMigratableDatabaseMigration, + $foundMigrations = Arr\filter( + array: iterator_to_array($migrations), + filter: static fn (MigratesUp $migration) => $migration instanceof HiddenMigratableDatabaseMigration, ); $this->assertCount(1, $foundMigrations, 'Expected one hidden migration to be found'); diff --git a/tests/Integration/Core/ViewComponentsInstallerTest.php b/tests/Integration/Core/ViewComponentsInstallerTest.php index 45528699f4..7ebbb5bcb6 100644 --- a/tests/Integration/Core/ViewComponentsInstallerTest.php +++ b/tests/Integration/Core/ViewComponentsInstallerTest.php @@ -24,14 +24,14 @@ protected function setUp(): void ) ->setRoot($this->internalStorage . '/install'); - $this->registerViewComponent( + $this->view->registerViewComponent( name: 'x-vendor-a', html: 'vendor a', file: __DIR__ . '/Fixtures/x-vendor-a.view.php', isVendor: true, ); - $this->registerViewComponent( + $this->view->registerViewComponent( name: 'x-vendor-b', html: 'vendor b', file: __DIR__ . '/Fixtures/x-vendor-b.view.php', @@ -72,7 +72,7 @@ public function test_all_vendor_view_components_are_listed(): void public function test_installed_vendor_components_are_not_listed_anymore(): void { - $this->registerViewComponent( + $this->view->registerViewComponent( name: 'x-vendor-b', html: 'vendor b', file: __DIR__ . '/Fixtures/x-vendor-b.view.php', diff --git a/tests/Integration/Database/Builder/CountQueryBuilderTest.php b/tests/Integration/Database/Builder/CountQueryBuilderTest.php index d9179bfd36..f580585ef6 100644 --- a/tests/Integration/Database/Builder/CountQueryBuilderTest.php +++ b/tests/Integration/Database/Builder/CountQueryBuilderTest.php @@ -138,7 +138,7 @@ public function test_count_query_with_conditions(): void public function test_count(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( diff --git a/tests/Integration/Database/Builder/CustomPrimaryKeyTest.php b/tests/Integration/Database/Builder/CustomPrimaryKeyTest.php index 8433d3aedb..bcee1f9161 100644 --- a/tests/Integration/Database/Builder/CustomPrimaryKeyTest.php +++ b/tests/Integration/Database/Builder/CustomPrimaryKeyTest.php @@ -15,7 +15,7 @@ final class CustomPrimaryKeyTest extends FrameworkIntegrationTestCase { public function test_model_with_custom_primary_key_name(): void { - $this->migrate(CreateMigrationsTable::class, CreateCustomPrimaryKeyUserModelTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateCustomPrimaryKeyUserModelTable::class); $frieren = query(CustomPrimaryKeyUserModel::class)->create(name: 'Frieren', magic: 'Time Magic'); @@ -32,7 +32,7 @@ public function test_model_with_custom_primary_key_name(): void public function test_update_or_create_with_custom_primary_key(): void { - $this->migrate(CreateMigrationsTable::class, CreateCustomPrimaryKeyUserModelTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateCustomPrimaryKeyUserModelTable::class); $frieren = query(CustomPrimaryKeyUserModel::class)->create(name: 'Frieren', magic: 'Time Magic'); @@ -47,7 +47,7 @@ public function test_update_or_create_with_custom_primary_key(): void public function test_model_without_id_property_still_works(): void { - $this->migrate(CreateMigrationsTable::class, CreateModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithoutIdMigration::class); $model = query(ModelWithoutId::class)->new(name: 'Test'); $this->assertInstanceOf(ModelWithoutId::class, $model); diff --git a/tests/Integration/Database/Builder/DeleteQueryBuilderTest.php b/tests/Integration/Database/Builder/DeleteQueryBuilderTest.php index 0c6e32631f..74fb24eb45 100644 --- a/tests/Integration/Database/Builder/DeleteQueryBuilderTest.php +++ b/tests/Integration/Database/Builder/DeleteQueryBuilderTest.php @@ -93,7 +93,7 @@ public function test_delete_on_plain_table_with_conditions(): void public function test_delete_with_non_object_model(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( diff --git a/tests/Integration/Database/Builder/InsertQueryBuilderTest.php b/tests/Integration/Database/Builder/InsertQueryBuilderTest.php index 8de47409e5..0fe939733a 100644 --- a/tests/Integration/Database/Builder/InsertQueryBuilderTest.php +++ b/tests/Integration/Database/Builder/InsertQueryBuilderTest.php @@ -129,7 +129,7 @@ public function test_insert_on_model_table_with_existing_relation(): void public function test_then_method(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert(title: 'Timeline Taxi') @@ -154,7 +154,7 @@ public function test_then_method(): void public function test_insert_with_non_object_model(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( diff --git a/tests/Integration/Database/Builder/InsertRelationsTest.php b/tests/Integration/Database/Builder/InsertRelationsTest.php index 97d4012f61..0f6c6c0920 100644 --- a/tests/Integration/Database/Builder/InsertRelationsTest.php +++ b/tests/Integration/Database/Builder/InsertRelationsTest.php @@ -28,7 +28,7 @@ final class InsertRelationsTest extends FrameworkIntegrationTestCase { public function test_inserting_has_many_with_arrays(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert( @@ -50,7 +50,7 @@ public function test_inserting_has_many_with_arrays(): void public function test_inserting_has_one_with_array(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -75,7 +75,7 @@ public function test_inserting_has_one_with_array(): void public function test_inserting_has_many_with_objects(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert( @@ -97,7 +97,7 @@ public function test_inserting_has_many_with_objects(): void public function test_inserting_has_one_with_object(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -122,7 +122,7 @@ public function test_inserting_has_one_with_object(): void public function test_inserting_mixed_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -154,7 +154,7 @@ public function test_inserting_mixed_relations(): void public function test_inserting_empty_has_many_relation(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert(title: 'Empty Book', chapters: []) @@ -170,7 +170,7 @@ public function test_inserting_empty_has_many_relation(): void public function test_inserting_large_batch_has_many(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $chapters = []; for ($i = 1; $i <= 10; $i++) { @@ -191,7 +191,7 @@ public function test_inserting_large_batch_has_many(): void public function test_inserting_has_many_preserves_additional_data(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert( @@ -233,7 +233,7 @@ public function test_inserting_has_one_with_invalid_type_throws_exception(): voi public function test_relation_insertion_with_mixed_types(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $id = query(Book::class) ->insert( @@ -257,7 +257,7 @@ public function test_relation_insertion_with_mixed_types(): void public function test_insertion_with_custom_primary_key_names(): void { - $this->migrate(CreateMigrationsTable::class, CreateMageTable::class, CreateSpellTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateMageTable::class, CreateSpellTable::class); $id = query(Mage::class) ->insert( @@ -285,7 +285,7 @@ public function test_insertion_with_custom_primary_key_names(): void public function test_insertion_with_non_standard_relation_names(): void { - $this->migrate(CreateMigrationsTable::class, CreatePartyTable::class, CreateAdventurerTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePartyTable::class, CreateAdventurerTable::class); $id = query(Party::class) ->insert( @@ -319,7 +319,7 @@ public function test_insertion_with_non_standard_relation_names(): void public function test_insertion_with_custom_foreign_key_names(): void { - $this->migrate(CreateMigrationsTable::class, CreateMageTable::class, CreateSpellTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateMageTable::class, CreateSpellTable::class); $spellId = query(Spell::class) ->insert( diff --git a/tests/Integration/Database/Builder/IsDatabaseModelTest.php b/tests/Integration/Database/Builder/IsDatabaseModelTest.php index 652ae0044a..8086c3364d 100644 --- a/tests/Integration/Database/Builder/IsDatabaseModelTest.php +++ b/tests/Integration/Database/Builder/IsDatabaseModelTest.php @@ -51,7 +51,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Database\query; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal @@ -60,7 +60,7 @@ final class IsDatabaseModelTest extends FrameworkIntegrationTestCase { public function test_create_and_update_model(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -88,7 +88,7 @@ public function test_create_and_update_model(): void public function test_get_with_non_id_object(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -104,7 +104,7 @@ public function test_get_with_non_id_object(): void public function test_creating_many_and_saving_preserves_model_id(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -123,7 +123,7 @@ public function test_creating_many_and_saving_preserves_model_id(): void public function test_complex_query(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -152,7 +152,7 @@ public function test_complex_query(): void public function test_all_with_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -185,7 +185,7 @@ public function test_all_with_relations(): void public function test_missing_relation_exception(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -216,7 +216,7 @@ public function test_missing_value_exception(): void public function test_nested_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -238,7 +238,7 @@ public function test_nested_relations(): void public function test_load_belongs_to(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -261,7 +261,7 @@ public function test_load_belongs_to(): void public function test_has_many_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -290,7 +290,7 @@ public function test_has_many_relations(): void public function test_has_many_through_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateHasManyParentTable::class, CreateHasManyChildTable::class, @@ -313,7 +313,7 @@ public function test_has_many_through_relation(): void public function test_empty_has_many_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -329,7 +329,7 @@ public function test_empty_has_many_relation(): void public function test_has_one_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -349,7 +349,7 @@ public function test_has_one_relation(): void public function test_invalid_has_one_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateHasManyParentTable::class, CreateHasManyChildTable::class, @@ -372,7 +372,7 @@ public function test_invalid_has_one_relation(): void public function test_lazy_load(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -397,7 +397,7 @@ public function test_lazy_load(): void public function test_eager_load(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -417,7 +417,7 @@ public function test_eager_load(): void public function test_no_result(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -468,7 +468,7 @@ public function test_virtual_hooked_property(): void public function test_select_virtual_property(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -488,7 +488,7 @@ public function test_select_virtual_property(): void public function test_update_with_virtual_property(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateATable::class, CreateBTable::class, @@ -518,7 +518,7 @@ public function test_update_with_virtual_property(): void public function test_update_or_create(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -565,7 +565,7 @@ public function test_update_or_create_uses_initial_data_to_create(): void public function test_delete(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -586,7 +586,7 @@ public function test_delete(): void public function test_delete_via_model_class_with_where_conditions(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -607,7 +607,7 @@ public function test_delete_via_model_class_with_where_conditions(): void public function test_delete_via_model_instance_with_primary_key(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -623,7 +623,7 @@ public function test_delete_via_model_instance_with_primary_key(): void public function test_delete_with_uninitialized_primary_key(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -637,7 +637,7 @@ public function test_delete_with_uninitialized_primary_key(): void public function test_delete_nonexistent_record(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, FooDatabaseMigration::class, ); @@ -656,7 +656,7 @@ public function test_delete_nonexistent_record(): void public function test_nullable_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateBNullableTable::class, CreateANullableTable::class, @@ -673,7 +673,7 @@ public function test_nullable_relations(): void public function test_nullable_relation_save(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateBNullableTable::class, CreateANullableTable::class, @@ -858,6 +858,11 @@ final class BaseModel final readonly class CarbonCaster implements Caster { + public static function for(): false + { + return false; + } + public function cast(mixed $input): mixed { return new Carbon($input); @@ -875,6 +880,11 @@ public function __construct( final readonly class CarbonSerializer implements Serializer { + public static function for(): false + { + return false; + } + public function serialize(mixed $input): string { if (! $input instanceof Carbon) { diff --git a/tests/Integration/Database/Builder/QueryBuilderTest.php b/tests/Integration/Database/Builder/QueryBuilderTest.php index 3be600ed12..dc47f6818d 100644 --- a/tests/Integration/Database/Builder/QueryBuilderTest.php +++ b/tests/Integration/Database/Builder/QueryBuilderTest.php @@ -18,7 +18,7 @@ final class QueryBuilderTest extends FrameworkIntegrationTestCase { public function test_select(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); query(TestUserModel::class)->create(name: 'Frieren'); query(TestUserModel::class)->create(name: 'Fern'); @@ -53,7 +53,7 @@ public function test_select(): void public function test_insert(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $builderWithId = query(TestUserModel::class)->insert(name: 'Frieren'); $builderWithoutId = query(TestUserModelWithoutId::class)->insert(name: 'Stark'); @@ -77,7 +77,7 @@ public function test_insert(): void public function test_update(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $createdWithId = query(TestUserModel::class)->create(name: 'Frieren'); query(TestUserModelWithoutId::class)->create(name: 'Stark'); @@ -102,7 +102,7 @@ public function test_update(): void public function test_delete(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $createdWithId = query(TestUserModel::class)->create(name: 'Frieren'); query(TestUserModel::class)->create(name: 'Fern'); @@ -129,7 +129,7 @@ public function test_delete(): void public function test_count(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); query(TestUserModel::class)->create(name: 'Frieren'); query(TestUserModel::class)->create(name: 'Fern'); @@ -170,7 +170,7 @@ public function test_new(): void public function test_get_with_id_query(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $created = query(TestUserModel::class)->create(name: 'Himmel'); $retrieved = query(TestUserModel::class)->get($created->id); @@ -182,7 +182,7 @@ public function test_get_with_id_query(): void public function test_get_throws_for_model_without_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage( @@ -194,7 +194,7 @@ public function test_get_throws_for_model_without_id(): void public function test_all(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); query(TestUserModel::class)->create(name: 'Fern'); query(TestUserModel::class)->create(name: 'Stark'); @@ -216,7 +216,7 @@ public function test_all(): void public function test_find(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); query(TestUserModel::class)->create(name: 'Frieren'); query(TestUserModel::class)->create(name: 'Fern'); @@ -242,7 +242,7 @@ public function test_find(): void public function test_create(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $createdWithId = query(TestUserModel::class)->create(name: 'Ubel'); $createdWithoutId = query(TestUserModelWithoutId::class)->create(name: 'Serie'); @@ -257,7 +257,7 @@ public function test_create(): void public function test_find_or_new_finds_existing(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $existingWithId = query(TestUserModel::class)->create(name: 'Serie'); $existingWithoutId = query(TestUserModelWithoutId::class)->create(name: 'Macht'); @@ -282,7 +282,7 @@ public function test_find_or_new_finds_existing(): void public function test_find_or_new_creates_new(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class, TestModelWithoutIdMigration::class); $resultWithId = query(TestUserModel::class)->findOrNew( find: ['name' => 'NonExistent'], @@ -304,7 +304,7 @@ public function test_find_or_new_creates_new(): void public function test_update_or_create_updates_existing(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $existingWithId = query(TestUserModel::class)->create(name: 'Qual'); @@ -320,7 +320,7 @@ public function test_update_or_create_updates_existing(): void public function test_update_or_create_creates_new(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $resultWithId = query(TestUserModel::class)->updateOrCreate( find: ['name' => 'NonExistent'], @@ -334,7 +334,7 @@ public function test_update_or_create_creates_new(): void public function test_get_with_string_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $created = query(TestUserModel::class)->create(name: 'Heiter'); $retrieved = query(TestUserModel::class)->get((string) $created->id->value); @@ -346,7 +346,7 @@ public function test_get_with_string_id(): void public function test_get_with_int_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $created = query(TestUserModel::class)->create(name: 'Eisen'); $retrieved = query(TestUserModel::class)->get($created->id->value); @@ -358,7 +358,7 @@ public function test_get_with_int_id(): void public function test_get_returns_null_for_non_existent_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWrapperMigration::class); $result = query(TestUserModel::class)->get(new PrimaryKey(999)); @@ -367,7 +367,7 @@ public function test_get_returns_null_for_non_existent_id(): void public function test_find_by_id_throws_for_model_without_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage( @@ -379,7 +379,7 @@ public function test_find_by_id_throws_for_model_without_id(): void public function test_update_or_create_throws_for_model_without_id(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWithoutIdMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage( @@ -394,7 +394,7 @@ public function test_update_or_create_throws_for_model_without_id(): void public function test_custom_primary_key_name(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWithCustomPrimaryKeyMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWithCustomPrimaryKeyMigration::class); $created = query(TestUserModelWithCustomPrimaryKey::class)->create(name: 'Fern'); @@ -410,7 +410,7 @@ public function test_custom_primary_key_name(): void public function test_custom_primary_key_update_or_create(): void { - $this->migrate(CreateMigrationsTable::class, TestModelWithCustomPrimaryKeyMigration::class); + $this->database->migrate(CreateMigrationsTable::class, TestModelWithCustomPrimaryKeyMigration::class); $original = query(TestUserModelWithCustomPrimaryKey::class)->create(name: 'Stark'); diff --git a/tests/Integration/Database/Builder/SelectQueryBuilderTest.php b/tests/Integration/Database/Builder/SelectQueryBuilderTest.php index 98f80fbc63..c4d257591d 100644 --- a/tests/Integration/Database/Builder/SelectQueryBuilderTest.php +++ b/tests/Integration/Database/Builder/SelectQueryBuilderTest.php @@ -116,7 +116,7 @@ public function test_multiple_where_field(): void public function test_where_statement(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -135,7 +135,7 @@ public function test_where_statement(): void public function test_join(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -158,7 +158,7 @@ public function test_join(): void public function test_order_by(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -177,7 +177,7 @@ public function test_order_by(): void public function test_order_by_with_field_and_direction(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -198,7 +198,7 @@ public function test_order_by_with_field_and_direction(): void public function test_order_by_with_field_defaults_to_asc(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -216,7 +216,7 @@ public function test_order_by_with_field_defaults_to_asc(): void public function test_order_by_raw_shorthand(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -252,7 +252,7 @@ public function test_order_by_sql_generation(): void public function test_limit(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -273,7 +273,7 @@ public function test_limit(): void public function test_offset(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -297,7 +297,7 @@ public function test_offset(): void public function test_chunk(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -324,7 +324,7 @@ public function test_chunk(): void public function test_chunk_with_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -345,7 +345,7 @@ public function test_chunk_with_relation(): void public function test_raw(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -392,7 +392,7 @@ public function test_select_query_with_conditions(): void public function test_select_first_with_non_object_model(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( @@ -411,7 +411,7 @@ public function test_select_first_with_non_object_model(): void public function test_select_all_with_non_object_model(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( diff --git a/tests/Integration/Database/Builder/UpdateQueryBuilderDtoTest.php b/tests/Integration/Database/Builder/UpdateQueryBuilderDtoTest.php index 9c3018c219..962dc68d41 100644 --- a/tests/Integration/Database/Builder/UpdateQueryBuilderDtoTest.php +++ b/tests/Integration/Database/Builder/UpdateQueryBuilderDtoTest.php @@ -17,7 +17,7 @@ final class UpdateQueryBuilderDtoTest extends FrameworkIntegrationTestCase { public function test_update_with_serialize_as_dto(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_create_users_table_for_dto_update'; public function up(): QueryStatement @@ -29,11 +29,10 @@ public function up(): QueryStatement } }); - $user = query(UserWithDtoSettings::class) - ->create( - name: 'John', - settings: new DtoSettings(DtoTheme::LIGHT), - ); + $user = query(UserWithDtoSettings::class)->create( + name: 'John', + settings: new DtoSettings(DtoTheme::LIGHT), + ); query(UserWithDtoSettings::class) ->update( diff --git a/tests/Integration/Database/Builder/UpdateQueryBuilderTest.php b/tests/Integration/Database/Builder/UpdateQueryBuilderTest.php index a036473379..0fd8271dbe 100644 --- a/tests/Integration/Database/Builder/UpdateQueryBuilderTest.php +++ b/tests/Integration/Database/Builder/UpdateQueryBuilderTest.php @@ -227,7 +227,7 @@ public function test_update_on_plain_table_with_conditions(): void public function test_update_with_non_object_model(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); query('authors') ->insert( diff --git a/tests/Integration/Database/Builder/UpdateRelationsTest.php b/tests/Integration/Database/Builder/UpdateRelationsTest.php index 14ffdd9ec4..8763cba18a 100644 --- a/tests/Integration/Database/Builder/UpdateRelationsTest.php +++ b/tests/Integration/Database/Builder/UpdateRelationsTest.php @@ -28,7 +28,7 @@ final class UpdateRelationsTest extends FrameworkIntegrationTestCase { public function test_updating_has_many_with_arrays(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -63,7 +63,7 @@ public function test_updating_has_many_with_arrays(): void public function test_updating_has_one_with_array(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -96,7 +96,7 @@ public function test_updating_has_one_with_array(): void public function test_updating_has_many_with_objects(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -129,7 +129,7 @@ public function test_updating_has_many_with_objects(): void public function test_updating_has_one_with_object(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -160,7 +160,7 @@ public function test_updating_has_one_with_object(): void public function test_updating_mixed_relations(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -204,7 +204,7 @@ public function test_updating_mixed_relations(): void public function test_updating_empty_has_many_relation(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -234,7 +234,7 @@ public function test_updating_empty_has_many_relation(): void public function test_updating_large_batch_has_many(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -268,7 +268,7 @@ public function test_updating_large_batch_has_many(): void public function test_updating_has_many_preserves_additional_data(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -302,7 +302,7 @@ public function test_updating_has_many_preserves_additional_data(): void public function test_updating_relation_with_mixed_types(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, CreateBookTable::class, CreateChapterTable::class); $bookId = query(Book::class) ->insert( @@ -336,7 +336,7 @@ public function test_updating_relation_with_mixed_types(): void public function test_updating_with_custom_primary_key_names(): void { - $this->migrate(CreateMigrationsTable::class, CreateUpdateMageTable::class, CreateUpdateSpellTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUpdateMageTable::class, CreateUpdateSpellTable::class); $mageId = query(UpdateMage::class) ->insert( @@ -375,7 +375,7 @@ public function test_updating_with_custom_primary_key_names(): void public function test_updating_with_non_standard_relation_names(): void { - $this->migrate(CreateMigrationsTable::class, CreateUpdatePartyTable::class, CreateUpdateAdventurerTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUpdatePartyTable::class, CreateUpdateAdventurerTable::class); $partyId = query(UpdateParty::class) ->insert( @@ -421,7 +421,7 @@ public function test_updating_with_non_standard_relation_names(): void public function test_updating_with_custom_foreign_key_names(): void { - $this->migrate(CreateMigrationsTable::class, CreateUpdateMageTable::class, CreateUpdateSpellTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUpdateMageTable::class, CreateUpdateSpellTable::class); $spellId = query(UpdateSpell::class) ->insert( @@ -457,7 +457,7 @@ public function test_updating_with_custom_foreign_key_names(): void public function test_update_throws_exception_when_model_has_no_primary_key(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, diff --git a/tests/Integration/Database/CircularEagerLoadingTest.php b/tests/Integration/Database/CircularEagerLoadingTest.php index 5f1e0a0cdc..2dfaa3e407 100644 --- a/tests/Integration/Database/CircularEagerLoadingTest.php +++ b/tests/Integration/Database/CircularEagerLoadingTest.php @@ -41,7 +41,7 @@ public function test_circular_with_relations_does_not_cause_infinite_loop(): voi public function test_it_saves_and_loads_relations_without_causing_infinite_loop(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateUserWithEagerTable::class, CreateProfileWithEagerTable::class, diff --git a/tests/Integration/Database/ConvenientDateWhereMethodsTest.php b/tests/Integration/Database/ConvenientDateWhereMethodsTest.php index 49b4ae1036..39ee9049a8 100644 --- a/tests/Integration/Database/ConvenientDateWhereMethodsTest.php +++ b/tests/Integration/Database/ConvenientDateWhereMethodsTest.php @@ -28,7 +28,7 @@ protected function setUp(): void $this->clock = $this->clock('2025-08-02 12:00:00'); - $this->migrate(CreateMigrationsTable::class, CreateEventTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateEventTable::class); $this->seedTestData(); } @@ -382,6 +382,37 @@ public function test_edge_case_month_boundary(): void $this->assertCount(0, $events); } + + public function test_datetime_objects_are_properly_serialized_for_database_queries(): void + { + $targetDate = DateTime::parse('2025-08-01'); + + query(Event::class) + ->insert( + name: 'Datetime serialization test', + created_at: $targetDate, + event_date: $targetDate, + ) + ->execute(); + + query(Event::class) + ->insert( + name: 'Should not be returned', + created_at: $targetDate->plusDays(1), + event_date: $targetDate->plusDays(1), + ) + ->execute(); + + $events = query(Event::class) + ->select() + ->whereBefore('event_date', DateTime::parse('2025-08-02')) + ->where('name', 'Datetime serialization test') + ->all(); + + $this->assertCount(1, $events); + $this->assertSame('Datetime serialization test', $events[0]->name); + $this->assertSame($targetDate->format('Y-m-d H:i:s'), $events[0]->event_date->format('Y-m-d H:i:s')); + } } final class CreateEventTable implements MigratesUp diff --git a/tests/Integration/Database/ConvenientWhereMethodsTest.php b/tests/Integration/Database/ConvenientWhereMethodsTest.php index cd248a9d52..c64f156bb7 100644 --- a/tests/Integration/Database/ConvenientWhereMethodsTest.php +++ b/tests/Integration/Database/ConvenientWhereMethodsTest.php @@ -24,7 +24,7 @@ protected function setUp(): void { parent::setUp(); - $this->migrate(CreateMigrationsTable::class, CreateUserTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserTable::class); $this->seedTestData(); } diff --git a/tests/Integration/Database/CustomPrimaryKeyRelationshipLoadingTest.php b/tests/Integration/Database/CustomPrimaryKeyRelationshipLoadingTest.php index 827efdddeb..4e9cc58ccf 100644 --- a/tests/Integration/Database/CustomPrimaryKeyRelationshipLoadingTest.php +++ b/tests/Integration/Database/CustomPrimaryKeyRelationshipLoadingTest.php @@ -25,7 +25,7 @@ final class CustomPrimaryKeyRelationshipLoadingTest extends FrameworkIntegration { public function test_has_one_relationship_with_uuid_primary_keys(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateGrimoireWithUuidMigration::class, @@ -57,7 +57,7 @@ public function test_has_one_relationship_with_uuid_primary_keys(): void public function test_has_many_relationship_with_uuid_primary_keys(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateSpellWithUuidMigration::class, @@ -100,7 +100,7 @@ public function test_has_many_relationship_with_uuid_primary_keys(): void public function test_belongs_to_relationship_with_uuid_primary_keys(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateSpellWithUuidMigration::class, @@ -129,7 +129,7 @@ public function test_belongs_to_relationship_with_uuid_primary_keys(): void public function test_nested_relationship_loading_with_uuid_primary_keys(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateGrimoireWithUuidMigration::class, @@ -174,7 +174,7 @@ public function test_nested_relationship_loading_with_uuid_primary_keys(): void public function test_relationship_with_custom_foreign_key_naming(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateArtifactWithUuidMigration::class, @@ -209,7 +209,7 @@ public function test_relationship_with_custom_foreign_key_naming(): void public function test_relationship_loading_preserves_uuid_integrity(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageWithUuidMigration::class, CreateSpellWithUuidMigration::class, @@ -253,7 +253,7 @@ public function test_relationship_loading_preserves_uuid_integrity(): void public function test_automatic_uuid_primary_key_detection(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateMageSimpleMigration::class, CreateSpellSimpleMigration::class, @@ -289,7 +289,7 @@ final class MageWithUuid { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; #[HasOne(ownerJoin: 'mage_uuid')] public ?GrimoireWithUuid $grimoire = null; @@ -313,7 +313,7 @@ final class GrimoireWithUuid { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; #[HasOne(ownerJoin: 'uuid', relationJoin: 'mage_uuid')] public ?MageWithUuid $mage = null; @@ -330,7 +330,7 @@ final class SpellWithUuid { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; #[HasOne(ownerJoin: 'uuid', relationJoin: 'mage_uuid')] public ?MageWithUuid $mage = null; @@ -348,7 +348,7 @@ final class ArtifactWithUuid { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; #[HasOne(ownerJoin: 'uuid', relationJoin: 'owner_uuid')] public ?MageWithUuid $owner = null; @@ -423,7 +423,7 @@ final class MageSimple { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; /** @var \Tests\Tempest\Integration\Database\SpellSimple[] */ #[HasMany] @@ -440,7 +440,7 @@ final class SpellSimple { use IsDatabaseModel; - public ?PrimaryKey $uuid = null; + public PrimaryKey $uuid; #[BelongsTo] public ?MageSimple $mage = null; diff --git a/tests/Integration/Database/DtoSerialization/BasicDtoSerializationTest.php b/tests/Integration/Database/DtoSerialization/BasicDtoSerializationTest.php index 5b91cbef2d..abcc62f25d 100644 --- a/tests/Integration/Database/DtoSerialization/BasicDtoSerializationTest.php +++ b/tests/Integration/Database/DtoSerialization/BasicDtoSerializationTest.php @@ -8,11 +8,7 @@ use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; -use Tempest\Mapper\Casters\DtoCaster; -use Tempest\Mapper\CastWith; use Tempest\Mapper\SerializeAs; -use Tempest\Mapper\Serializers\DtoSerializer; -use Tempest\Mapper\SerializeWith; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Database\query; @@ -21,7 +17,7 @@ final class BasicDtoSerializationTest extends FrameworkIntegrationTestCase { public function test_simple_dto_serialization(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_simple_character'; public function up(): QueryStatement @@ -53,7 +49,7 @@ public function up(): QueryStatement public function test_simple_dto_serialization_with_named_arguments(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_simple_character_named_args'; public function up(): QueryStatement @@ -85,7 +81,7 @@ public function up(): QueryStatement public function test_dto_with_enums(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '002_character_class_infos'; public function up(): QueryStatement @@ -121,7 +117,7 @@ public function up(): QueryStatement public function test_dto_with_custom_serialization_name(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '003_settings'; public function up(): QueryStatement @@ -191,8 +187,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class CharacterStats { public function __construct( @@ -210,8 +205,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class ClassDetails { public function __construct( @@ -229,8 +223,6 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] #[SerializeAs('app-settings')] final class ApplicationSettings { diff --git a/tests/Integration/Database/DtoSerialization/NestedDtoSerializationTest.php b/tests/Integration/Database/DtoSerialization/NestedDtoSerializationTest.php index 753f311f8e..4ecd038c47 100644 --- a/tests/Integration/Database/DtoSerialization/NestedDtoSerializationTest.php +++ b/tests/Integration/Database/DtoSerialization/NestedDtoSerializationTest.php @@ -8,10 +8,7 @@ use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; -use Tempest\Mapper\Casters\DtoCaster; -use Tempest\Mapper\CastWith; -use Tempest\Mapper\Serializers\DtoSerializer; -use Tempest\Mapper\SerializeWith; +use Tempest\Mapper\SerializeAs; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Database\query; @@ -20,7 +17,7 @@ final class NestedDtoSerializationTest extends FrameworkIntegrationTestCase { public function test_deeply_nested_dtos(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_spell_structure'; public function up(): QueryStatement @@ -82,7 +79,7 @@ public function up(): QueryStatement public function test_nested_dtos_with_mixed_types(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '002_grimoire'; public function up(): QueryStatement @@ -201,8 +198,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class SpellStructure { public function __construct( @@ -211,8 +207,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class Incantation { public function __construct( @@ -221,8 +216,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class Pronunciation { public function __construct( @@ -231,8 +225,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class EmphasisPattern { public function __construct( @@ -242,8 +235,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class SpellComponents { public function __construct( @@ -253,8 +245,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class MaterialComponent { public function __construct( @@ -264,8 +255,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class ItemProperties { public function __construct( @@ -282,8 +272,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class GrimoireMetadata { public function __construct( @@ -293,8 +282,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class Author { public function __construct( @@ -304,8 +292,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class GrimoireContents { public function __construct( @@ -316,8 +303,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class IndexingSystem { public function __construct( @@ -327,8 +313,7 @@ public function __construct( ) {} } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[SerializeAs(self::class)] final class PreservationInfo { public function __construct( diff --git a/tests/Integration/Database/DtoSerialization/SerializeAsTest.php b/tests/Integration/Database/DtoSerialization/SerializeAsTest.php index 4d9af955fe..45ddee65ef 100644 --- a/tests/Integration/Database/DtoSerialization/SerializeAsTest.php +++ b/tests/Integration/Database/DtoSerialization/SerializeAsTest.php @@ -22,7 +22,7 @@ public function test_serialize_as_simple_object(): void $config = $this->container->get(MapperConfig::class); $config->serializeAs(SimpleSpell::class, 'simple-spell'); - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_spell_library'; public function up(): QueryStatement @@ -65,7 +65,7 @@ public function test_serialize_as_nested_objects(): void $config->serializeAs(MageProfile::class, 'mage-profile'); $config->serializeAs(SimpleSpell::class, 'simple-spell'); - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '002_mage_profiles'; public function up(): QueryStatement @@ -113,7 +113,7 @@ public function test_serialize_as_with_arrays(): void $config->serializeAs(SpellCollection::class, 'spell-collection'); $config->serializeAs(SimpleSpell::class, 'simple-spell'); - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '003_collections'; public function up(): QueryStatement @@ -166,7 +166,7 @@ public function test_serialize_as_without_explicit_casters(): void $config = $this->container->get(MapperConfig::class); $config->serializeAs(MagicItem::class, 'magic-item'); - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '004_inventory'; public function up(): QueryStatement diff --git a/tests/Integration/Database/DtoSerialization/TopLevelArraySerializationTest.php b/tests/Integration/Database/DtoSerialization/TopLevelArraySerializationTest.php index 46bea21313..6ea779e16d 100644 --- a/tests/Integration/Database/DtoSerialization/TopLevelArraySerializationTest.php +++ b/tests/Integration/Database/DtoSerialization/TopLevelArraySerializationTest.php @@ -4,13 +4,14 @@ namespace Tests\Tempest\Integration\Database\DtoSerialization; +use Tempest\Database\Casters\DataTransferObjectCaster; use Tempest\Database\MigratesUp; use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; -use Tempest\Mapper\Casters\DtoCaster; +use Tempest\Database\Serializers\DataTransferObjectSerializer; use Tempest\Mapper\CastWith; -use Tempest\Mapper\Serializers\DtoSerializer; +use Tempest\Mapper\SerializeAs; use Tempest\Mapper\SerializeWith; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -20,7 +21,7 @@ final class TopLevelArraySerializationTest extends FrameworkIntegrationTestCase { public function test_top_level_array_of_simple_dtos_serialization(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '001_array_containers'; public function up(): QueryStatement @@ -62,7 +63,7 @@ public function up(): QueryStatement public function test_top_level_array_of_nested_dtos_serialization(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '002_array_containers_nested'; public function up(): QueryStatement @@ -105,7 +106,7 @@ public function up(): QueryStatement public function test_empty_top_level_array(): void { - $this->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { public string $name = '003_array_containers_empty'; public function up(): QueryStatement @@ -138,11 +139,13 @@ public function up(): QueryStatement { public function __construct( public string $name, - #[SerializeWith(DtoSerializer::class), CastWith(DtoCaster::class)] + #[SerializeWith(DataTransferObjectSerializer::class)] + #[CastWith(DataTransferObjectCaster::class)] public array $data, ) {} } +#[SerializeAs(self::class)] final readonly class SimpleArrayItem { public function __construct( @@ -151,6 +154,7 @@ public function __construct( ) {} } +#[SerializeAs(self::class)] final readonly class ItemWithNestedArray { public function __construct( diff --git a/tests/Integration/Database/DtoSerialization/UnionDtoSerializationTest.php b/tests/Integration/Database/DtoSerialization/UnionDtoSerializationTest.php new file mode 100644 index 0000000000..f25a285e0f --- /dev/null +++ b/tests/Integration/Database/DtoSerialization/UnionDtoSerializationTest.php @@ -0,0 +1,93 @@ +container->get(MapperConfig::class); + $config->serializeAs(TestUnionA::class, 'test_union_a'); + $config->serializeAs(TestUnionB::class, 'test_union_b'); + $config->serializeAs(TestNestedObject::class, 'test_object'); + + $this->database->migrate(CreateMigrationsTable::class, new class implements MigratesUp { + public string $name = '001_union_serialization'; + + public function up(): QueryStatement + { + return new CreateTableStatement('test_wrapper_models') + ->primary() + ->json('property'); + } + }); + + query(TestWrapperModel::class)->create(property: new TestUnionA( + content: 'abc', + testObject: new TestNestedObject('def'), + )); + + query(TestWrapperModel::class)->create(property: new TestUnionB( + message: '123', + testObject: new TestNestedObject('456'), + )); + + $results = query(TestWrapperModel::class)->all(); + + $this->assertCount(2, $results); + $this->assertInstanceOf(TestUnionA::class, $results[0]->property); + $this->assertSame('abc', $results[0]->property->content); + $this->assertInstanceOf(TestNestedObject::class, $results[0]->property->testObject); + $this->assertSame('def', $results[0]->property->testObject->content); + + $this->assertInstanceOf(TestUnionB::class, $results[1]->property); + $this->assertSame('123', $results[1]->property->message); + $this->assertInstanceOf(TestNestedObject::class, $results[1]->property->testObject); + $this->assertSame('456', $results[1]->property->testObject->content); + } +} + +final class TestWrapperModel +{ + public function __construct( + public TestUnionA|TestUnionB $property, + ) {} +} + +#[SerializeAs('test_object')] +final class TestNestedObject +{ + public function __construct( + public string $content, + ) {} +} + +#[SerializeAs('test_union_a')] +final class TestUnionA +{ + public function __construct( + public string $content, + public TestNestedObject $testObject, + ) {} +} + +#[SerializeAs('test_union_b')] +final class TestUnionB +{ + public function __construct( + public string $message, + public TestNestedObject $testObject, + ) {} +} diff --git a/tests/Integration/Database/EncryptedAttributeTest.php b/tests/Integration/Database/EncryptedAttributeTest.php index 07df48e120..68e17694d1 100644 --- a/tests/Integration/Database/EncryptedAttributeTest.php +++ b/tests/Integration/Database/EncryptedAttributeTest.php @@ -25,7 +25,7 @@ final class EncryptedAttributeTest extends FrameworkIntegrationTestCase #[Test] public function encrypts_value_on_insert(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); $user = query(UserWithEncryptedData::class)->create( email: 'test@example.com', @@ -42,7 +42,7 @@ public function encrypts_value_on_insert(): void #[Test] public function encrypts_value_on_update(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); $user = query(UserWithEncryptedData::class)->create( email: 'test@example.com', @@ -62,7 +62,7 @@ public function encrypts_value_on_update(): void #[Test] public function does_not_re_encrypt_already_encrypted_values(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); $user = query(UserWithEncryptedData::class)->create( email: 'test@example.com', @@ -75,7 +75,7 @@ public function does_not_re_encrypt_already_encrypted_values(): void #[Test] public function handles_null_values(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithNullableEncryptedDataTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithNullableEncryptedDataTable::class); $user = query(UserWithNullableEncryptedData::class)->create( email: 'test@example.com', @@ -88,7 +88,7 @@ public function handles_null_values(): void #[Test] public function handles_empty_strings(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithEncryptedDataTable::class); $user = query(UserWithEncryptedData::class)->create( email: 'test@example.com', diff --git a/tests/Integration/Database/GenericDatabaseTest.php b/tests/Integration/Database/GenericDatabaseTest.php index 3d27b9f839..a8a2571018 100644 --- a/tests/Integration/Database/GenericDatabaseTest.php +++ b/tests/Integration/Database/GenericDatabaseTest.php @@ -24,7 +24,7 @@ final class GenericDatabaseTest extends FrameworkIntegrationTestCase { public function test_transaction_manager_execute(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); $db = $this->container->get(Database::class); @@ -41,7 +41,7 @@ public function test_transaction_manager_execute(): void public function test_transaction_manager_fails(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); $db = $this->container->get(Database::class); @@ -60,7 +60,7 @@ public function test_transaction_manager_fails(): void public function test_query_with_semicolons(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class); $db = $this->container->get(Database::class); $db->execute( diff --git a/tests/Integration/Database/GenericTransactionManagerTest.php b/tests/Integration/Database/GenericTransactionManagerTest.php index d2d6ad71ad..21022ab439 100644 --- a/tests/Integration/Database/GenericTransactionManagerTest.php +++ b/tests/Integration/Database/GenericTransactionManagerTest.php @@ -19,7 +19,7 @@ final class GenericTransactionManagerTest extends FrameworkIntegrationTestCase { public function test_transaction_manager(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); $manager = $this->container->get(TransactionManager::class); @@ -35,7 +35,7 @@ public function test_transaction_manager(): void public function test_transaction_manager_commit(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); $manager = $this->container->get(TransactionManager::class); @@ -51,7 +51,7 @@ public function test_transaction_manager_commit(): void public function test_transaction_manager_commit_rollback(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); $manager = $this->container->get(TransactionManager::class); diff --git a/tests/Integration/Database/GroupedWhereMethodsTest.php b/tests/Integration/Database/GroupedWhereMethodsTest.php index e89f854728..2814f08af7 100644 --- a/tests/Integration/Database/GroupedWhereMethodsTest.php +++ b/tests/Integration/Database/GroupedWhereMethodsTest.php @@ -24,7 +24,7 @@ protected function setUp(): void { parent::setUp(); - $this->migrate(CreateMigrationsTable::class, CreateProductTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateProductTable::class); $this->seedTestData(); } diff --git a/tests/Integration/Database/HashedAttributeTest.php b/tests/Integration/Database/HashedAttributeTest.php index d0c37a4821..c0e76986d0 100644 --- a/tests/Integration/Database/HashedAttributeTest.php +++ b/tests/Integration/Database/HashedAttributeTest.php @@ -23,7 +23,7 @@ final class HashedAttributeTest extends FrameworkIntegrationTestCase #[Test] public function hashes_value_on_insert(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); $user = query(UserWithHash::class)->create( email: 'test@example.com', @@ -43,7 +43,7 @@ public function hashes_value_on_insert(): void #[Test] public function hashes_value_on_update(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); $user = query(UserWithHash::class) ->create( @@ -66,7 +66,7 @@ public function hashes_value_on_update(): void #[Test] public function does_not_rehash_already_hashed_values(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithHashTable::class); $user = query(UserWithHash::class) ->create( @@ -82,7 +82,7 @@ public function does_not_rehash_already_hashed_values(): void #[Test] public function handles_null_values(): void { - $this->migrate(CreateMigrationsTable::class, CreateUserWithNullablePasswordTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreateUserWithNullablePasswordTable::class); $user = query(UserWithNullablePassword::class) ->create( diff --git a/tests/Integration/Database/Mappers/SelectModelMapperTest.php b/tests/Integration/Database/Mappers/SelectModelMapperTest.php index cf4b854dcd..192e740bf6 100644 --- a/tests/Integration/Database/Mappers/SelectModelMapperTest.php +++ b/tests/Integration/Database/Mappers/SelectModelMapperTest.php @@ -7,7 +7,7 @@ use Tests\Tempest\Fixtures\Modules\Books\Models\Book; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\map; +use function Tempest\Mapper\map; final class SelectModelMapperTest extends FrameworkIntegrationTestCase { diff --git a/tests/Integration/Database/ModelInspector/ModelInspectorTest.php b/tests/Integration/Database/ModelInspector/ModelInspectorTest.php index 54a9c78067..b6c38dd833 100644 --- a/tests/Integration/Database/ModelInspector/ModelInspectorTest.php +++ b/tests/Integration/Database/ModelInspector/ModelInspectorTest.php @@ -2,11 +2,11 @@ namespace Tests\Tempest\Integration\Database\ModelInspector; +use Tempest\Database\Casters\DataTransferObjectCaster; use Tempest\Database\IsDatabaseModel; +use Tempest\Database\Serializers\DataTransferObjectSerializer; use Tempest\Database\Virtual; -use Tempest\Mapper\Casters\DtoCaster; use Tempest\Mapper\CastWith; -use Tempest\Mapper\Serializers\DtoSerializer; use Tempest\Mapper\SerializeWith; use Tests\Tempest\Integration\IntegrationTestCase; @@ -59,8 +59,8 @@ final class ModelInspectorTestModelWithVirtualDto public ModelInspectorTestDtoForModelWithVirtual $dto; } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[CastWith(DataTransferObjectCaster::class)] +#[SerializeWith(DataTransferObjectSerializer::class)] final class ModelInspectorTestDtoForModelWithSerializer { public function __construct( @@ -86,6 +86,6 @@ final class ModelInspectorTestModelWithSerializedDtoProperty { use IsDatabaseModel; - #[SerializeWith(DtoSerializer::class)] + #[SerializeWith(DataTransferObjectSerializer::class)] public ModelInspectorTestDtoForModelWithSerializerOnProperty $dto; } diff --git a/tests/Integration/Database/ModelInspector/ModelWithDtoTest.php b/tests/Integration/Database/ModelInspector/ModelWithDtoTest.php index d153b4f45e..7e0dc9a2c9 100644 --- a/tests/Integration/Database/ModelInspector/ModelWithDtoTest.php +++ b/tests/Integration/Database/ModelInspector/ModelWithDtoTest.php @@ -2,14 +2,14 @@ namespace Tests\Tempest\Integration\Database\ModelInspector; +use Tempest\Database\Casters\DataTransferObjectCaster; use Tempest\Database\IsDatabaseModel; use Tempest\Database\MigratesUp; use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Database\QueryStatement; use Tempest\Database\QueryStatements\CreateTableStatement; -use Tempest\Mapper\Casters\DtoCaster; +use Tempest\Database\Serializers\DataTransferObjectSerializer; use Tempest\Mapper\CastWith; -use Tempest\Mapper\Serializers\DtoSerializer; use Tempest\Mapper\SerializeWith; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -36,7 +36,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $migration); + $this->database->migrate(CreateMigrationsTable::class, $migration); ModelWithDtoTestModelWithSerializedDto::new(dto: new ModelWithDtoTestDtoForModelWithSerializer('test'))->save(); @@ -46,8 +46,8 @@ public function up(): QueryStatement } } -#[CastWith(DtoCaster::class)] -#[SerializeWith(DtoSerializer::class)] +#[CastWith(DataTransferObjectCaster::class)] +#[SerializeWith(DataTransferObjectSerializer::class)] final class ModelWithDtoTestDtoForModelWithSerializer { public function __construct( diff --git a/tests/Integration/Database/ModelsWithoutIdTest.php b/tests/Integration/Database/ModelsWithoutIdTest.php index 0e9b3426e1..91eccbc7ea 100644 --- a/tests/Integration/Database/ModelsWithoutIdTest.php +++ b/tests/Integration/Database/ModelsWithoutIdTest.php @@ -24,7 +24,7 @@ final class ModelsWithoutIdTest extends FrameworkIntegrationTestCase { public function test_update_model_without_id_with_specific_conditions(): void { - $this->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); query(LogEntry::class)->create( level: 'INFO', @@ -47,7 +47,7 @@ public function test_update_model_without_id_with_specific_conditions(): void public function test_delete_operations_on_models_without_id(): void { - $this->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); query(LogEntry::class)->create( level: 'TEMP', @@ -75,7 +75,7 @@ public function test_delete_operations_on_models_without_id(): void public function test_model_without_id_with_unique_constraints(): void { - $this->migrate(CreateMigrationsTable::class, CreateCacheEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateCacheEntryMigration::class); query(CacheEntry::class)->create( cache_key: 'spell_fire', @@ -97,7 +97,7 @@ public function test_model_without_id_with_unique_constraints(): void public function test_relationship_methods_throw_for_models_without_id(): void { - $this->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage('does not have a primary column defined, which is required for the `findById` method'); @@ -107,7 +107,7 @@ public function test_relationship_methods_throw_for_models_without_id(): void public function test_get_method_throws_for_models_without_id(): void { - $this->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage('does not have a primary column defined, which is required for the `get` method'); @@ -117,7 +117,7 @@ public function test_get_method_throws_for_models_without_id(): void public function test_update_or_create_throws_for_models_without_id(): void { - $this->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateLogEntryMigration::class); $this->expectException(ModelDidNotHavePrimaryColumn::class); $this->expectExceptionMessage('does not have a primary column defined, which is required for the `updateOrCreate` method'); @@ -130,7 +130,7 @@ public function test_update_or_create_throws_for_models_without_id(): void public function test_model_with_mixed_id_and_non_id_properties(): void { - $this->migrate(CreateMigrationsTable::class, CreateMixedModelMigration::class); + $this->database->migrate(CreateMigrationsTable::class, CreateMixedModelMigration::class); $mixed = query(MixedModel::class)->create( regular_field: 'test', diff --git a/tests/Integration/Database/QueryStatements/AlterTableStatementTest.php b/tests/Integration/Database/QueryStatements/AlterTableStatementTest.php index 4babb621a3..4bf52ccad9 100644 --- a/tests/Integration/Database/QueryStatements/AlterTableStatementTest.php +++ b/tests/Integration/Database/QueryStatements/AlterTableStatementTest.php @@ -27,7 +27,7 @@ public function test_it_can_alter_a_table_definition(): void { $migration = $this->getAlterTableMigration(); - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreateUserDatabaseMigration::class, ); @@ -53,7 +53,7 @@ public function test_it_can_alter_a_table_definition(): void $this->assertStringContainsString($message, $queryWasInvalid->getMessage()); } - $this->migrate($migration::class); + $this->database->migrate($migration::class); $this->assertCount(3, MigrationModel::all()); $this->assertSame( diff --git a/tests/Integration/Database/QueryStatements/BelongsToStatementTest.php b/tests/Integration/Database/QueryStatements/BelongsToStatementTest.php index 632454aa6f..07bd924612 100644 --- a/tests/Integration/Database/QueryStatements/BelongsToStatementTest.php +++ b/tests/Integration/Database/QueryStatements/BelongsToStatementTest.php @@ -54,7 +54,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $customersMigration, $belongsToMigration, $foreignKeyMigration); + $this->database->migrate(CreateMigrationsTable::class, $customersMigration, $belongsToMigration, $foreignKeyMigration); $this->expectNotToPerformAssertions(); } @@ -85,7 +85,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $categoriesMigration, $productsMigration); + $this->database->migrate(CreateMigrationsTable::class, $categoriesMigration, $productsMigration); $this->expectNotToPerformAssertions(); } diff --git a/tests/Integration/Database/QueryStatements/CreateTableStatementTest.php b/tests/Integration/Database/QueryStatements/CreateTableStatementTest.php index 8031ae0515..cefccf14af 100644 --- a/tests/Integration/Database/QueryStatements/CreateTableStatementTest.php +++ b/tests/Integration/Database/QueryStatements/CreateTableStatementTest.php @@ -46,7 +46,7 @@ public function up(): QueryStatement } }; - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -74,7 +74,7 @@ public function up(): QueryStatement DatabaseDialect::POSTGRESQL => $this->expectException(DialectWasNotSupported::class), }; - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -92,7 +92,7 @@ public function up(): QueryStatement } }; - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -102,7 +102,7 @@ public function up(): QueryStatement public function test_enum_statement(): void { - $this->migrate(CreateMigrationsTable::class); + $this->database->migrate(CreateMigrationsTable::class); if ($this->container->get(Database::class)->dialect === DatabaseDialect::POSTGRESQL) { $enumTypeMigration = new class() implements MigratesUp { @@ -117,7 +117,7 @@ public function up(): QueryStatement } }; - $this->migrate($enumTypeMigration); + $this->database->migrate($enumTypeMigration); } $tableMigration = new class() implements MigratesUp { @@ -134,7 +134,7 @@ enumClass: CreateTableStatementTestEnumForCreateTable::class, } }; - $this->migrate($tableMigration); + $this->database->migrate($tableMigration); $this->expectNotToPerformAssertions(); } @@ -154,7 +154,7 @@ public function up(): QueryStatement $this->expectException(DefaultValueWasInvalid::class); $this->expectExceptionMessage("Default value '{default: \"invalid json\"}' provided for json is not valid"); - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -172,7 +172,7 @@ public function up(): QueryStatement } }; - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -195,7 +195,7 @@ public function up(): QueryStatement $this->expectException(ValueWasInvalid::class); $this->expectExceptionMessage("Value '[]' provided for set is not valid"); - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, $migration, ); @@ -215,7 +215,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $migration); + $this->database->migrate(CreateMigrationsTable::class, $migration); $this->expectNotToPerformAssertions(); } @@ -247,7 +247,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $migration); + $this->database->migrate(CreateMigrationsTable::class, $migration); $this->expectNotToPerformAssertions(); } @@ -264,7 +264,7 @@ public function up(): QueryStatement } }; - $this->migrate(CreateMigrationsTable::class, $migration); + $this->database->migrate(CreateMigrationsTable::class, $migration); $this->expectNotToPerformAssertions(); } diff --git a/tests/Integration/Database/QueryTest.php b/tests/Integration/Database/QueryTest.php index 835c53e078..c3a48a6433 100644 --- a/tests/Integration/Database/QueryTest.php +++ b/tests/Integration/Database/QueryTest.php @@ -20,7 +20,7 @@ final class QueryTest extends FrameworkIntegrationTestCase { public function test_with_bindings(): void { - $this->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); + $this->database->migrate(CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class); new Author(name: 'A')->save(); new Author(name: 'B')->save(); @@ -39,12 +39,13 @@ public function test_raw_sql_enum_value(): void $this->assertTrue( new Query('?', [UnitEnumFixture::FOO]) ->toRawSql() - ->equals(UnitEnumFixture::FOO->name), + ->equals('"' . UnitEnumFixture::FOO->name . '"'), ); + $this->assertTrue( new Query('?', [BackedEnumFixture::FOO]) ->toRawSql() - ->equals(BackedEnumFixture::FOO->value), + ->equals('"' . BackedEnumFixture::FOO->value . '"'), ); } } diff --git a/tests/Integration/Database/RefreshModelTest.php b/tests/Integration/Database/RefreshModelTest.php index 3552c15643..7cae055fc2 100644 --- a/tests/Integration/Database/RefreshModelTest.php +++ b/tests/Integration/Database/RefreshModelTest.php @@ -17,7 +17,7 @@ final class RefreshModelTest extends FrameworkIntegrationTestCase { public function test_refresh_works_for_models_with_unloaded_relation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -65,7 +65,7 @@ public function test_refresh_works_for_models_with_unloaded_relation(): void public function test_load_method_only_refreshes_relations_and_nothing_else(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, diff --git a/tests/Integration/Database/ToRawSqlTest.php b/tests/Integration/Database/ToRawSqlTest.php index f54cdb9c6b..b014d3b60b 100644 --- a/tests/Integration/Database/ToRawSqlTest.php +++ b/tests/Integration/Database/ToRawSqlTest.php @@ -7,12 +7,14 @@ use Tempest\Database\Builder\WhereOperator; use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Database\Query; +use Tempest\DateTime\DateTime; use Tests\Tempest\Fixtures\Migrations\CreateAuthorTable; use Tests\Tempest\Fixtures\Migrations\CreateBookTable; use Tests\Tempest\Fixtures\Migrations\CreatePublishersTable; use Tests\Tempest\Fixtures\Modules\Books\Models\Author; use Tests\Tempest\Fixtures\Modules\Books\Models\AuthorType; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; +use Tests\Tempest\Integration\Validator\UnitEnumFixture; use function Tempest\Database\query; @@ -263,7 +265,7 @@ public function test_raw_sql_with_string_escaping(): void public function test_raw_sql_with_model_queries(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -353,9 +355,9 @@ public function test_raw_sql_handles_array_values_properly(): void $this->assertStringContainsString(needle: "('draft','archived')", haystack: $rawSql); } - public function test_raw_sql_with_enum_values(): void + public function test_raw_sql_with_backed_enum_values(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -363,11 +365,22 @@ public function test_raw_sql_with_enum_values(): void $rawSql = query('authors') ->select() - ->where('type', AuthorType::A->value) + ->where('type', AuthorType::A) ->toRawSql() ->toString(); - $this->assertStringContainsString(needle: "'a'", haystack: $rawSql); + $this->assertStringContainsString(needle: '"a"', haystack: $rawSql); + } + + public function test_raw_sql_with_unit_enum_values(): void + { + $rawSql = query('authors') + ->select() + ->where('type', UnitEnumFixture::FOO) + ->toRawSql() + ->toString(); + + $this->assertStringContainsString(needle: '"FOO"', haystack: $rawSql); } public function test_raw_sql_consistency_across_database_dialects(): void @@ -456,4 +469,19 @@ public function test_raw_sql_with_mixed_data_types(): void $this->assertStringContainsString(needle: 'deleted_at', haystack: $rawSql); $this->assertStringContainsString(needle: 'IS NULL', haystack: $rawSql); } + + public function test_raw_sql_with_date_where_methods(): void + { + $rawSql = query('books') + ->select() + ->whereBefore('published_at', DateTime::parse('2024-12-31')) + ->whereAfter('created_at', '2024-01-01') + ->toRawSql() + ->toString(); + + $this->assertStringContainsString(needle: 'published_at', haystack: $rawSql); + $this->assertStringContainsString(needle: 'created_at', haystack: $rawSql); + $this->assertStringContainsString(needle: '2024-12-31', haystack: $rawSql); + $this->assertStringContainsString(needle: '2024-01-01', haystack: $rawSql); + } } diff --git a/tests/Integration/Database/UuidPrimaryKeyTest.php b/tests/Integration/Database/UuidPrimaryKeyTest.php new file mode 100644 index 0000000000..43cdf86a9c --- /dev/null +++ b/tests/Integration/Database/UuidPrimaryKeyTest.php @@ -0,0 +1,166 @@ +database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $mage = query(DatabaseModelWithUuid::class)->create( + name: 'Frieren', + race: 'Human', + ); + + $this->assertInstanceOf(DatabaseModelWithUuid::class, $mage); + $this->assertInstanceOf(PrimaryKey::class, $mage->uuid); + $this->assertTrue(Random\is_uuid($mage->uuid->value)); + $this->assertSame('Frieren', $mage->name); + $this->assertSame('Human', $mage->race); + } + + #[Test] + public function uuid_primary_key_save_method(): void + { + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $mage = new DatabaseModelWithUuid(name: 'Fern', race: 'Human'); + $savedMage = $mage->save(); + + $this->assertSame($mage, $savedMage); + $this->assertInstanceOf(PrimaryKey::class, $mage->uuid); + $this->assertTrue(Random\is_uuid($mage->uuid->value)); + $this->assertSame('Fern', $mage->name); + } + + #[Test] + public function uuid_primary_key_retrieval(): void + { + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $mage = query(DatabaseModelWithUuid::class)->create( + name: 'Frieren', + race: 'Elf', + ); + + $retrieved = query(DatabaseModelWithUuid::class)->get($mage->uuid); + $this->assertNotNull($retrieved); + $this->assertSame('Frieren', $retrieved->name); + $this->assertTrue($mage->uuid->equals($retrieved->uuid)); + } + + #[Test] + public function uuid_primary_key_update_or_create(): void + { + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $original = query(DatabaseModelWithUuid::class)->create( + name: 'Himmel', + race: 'Elf', + ); + + $updated = query(DatabaseModelWithUuid::class)->updateOrCreate( + find: ['name' => 'Himmel'], + update: ['race' => 'Human'], + ); + + $this->assertTrue($original->uuid->equals($updated->uuid)); + $this->assertSame('Human', $updated->race); + } + + #[Test] + public function uuid_primary_key_manual_assignment(): void + { + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $uuid = Random\uuid(); + + $mage = new DatabaseModelWithUuid(name: 'Stark', race: 'Human'); + $mage->uuid = new PrimaryKey($uuid); + $mage->save(); + + $this->assertSame($uuid, $mage->uuid->value); + } + + #[Test] + public function uuid_primary_key_without_is_database_model_trait(): void + { + $this->database->migrate(CreateMigrationsTable::class, CreateModelWithUuidTableMigration::class); + + $mage = query(ModelWithUuid::class)->create( + name: 'Frieren', + race: 'Elf', + ); + + $this->assertInstanceOf(ModelWithUuid::class, $mage); + $this->assertInstanceOf(PrimaryKey::class, $mage->uuid); + $this->assertTrue(Random\is_uuid($mage->uuid->value)); + $this->assertSame('Frieren', $mage->name); + $this->assertSame('Elf', $mage->race); + + $retrieved = query(ModelWithUuid::class)->get($mage->uuid); + + $this->assertNotNull($retrieved); + $this->assertTrue($mage->uuid->equals($retrieved->uuid)); + } +} + +#[Table('model')] +final class DatabaseModelWithUuid +{ + use IsDatabaseModel; + + #[Uuid] + public PrimaryKey $uuid; + + public function __construct( + public string $name, + public string $race, + ) {} +} + +#[Table('model')] +final class ModelWithUuid +{ + #[Uuid] + public PrimaryKey $uuid; + + public function __construct( + public string $name, + public string $race, + ) {} +} + +final class CreateModelWithUuidTableMigration implements MigratesUp +{ + public string $name = '001_create_model_with_uuid'; + + public function up(): QueryStatement + { + return new CreateTableStatement('model') + ->uuid(name: 'uuid') + ->text('name') + ->text('race'); + } +} diff --git a/tests/Integration/EventBus/EventBusTest.php b/tests/Integration/EventBus/EventBusTest.php index 4a2640d1fb..9491287762 100644 --- a/tests/Integration/EventBus/EventBusTest.php +++ b/tests/Integration/EventBus/EventBusTest.php @@ -16,7 +16,7 @@ use Tests\Tempest\Fixtures\Handlers\EventInterfaceHandler; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\event; +use function Tempest\EventBus\event; /** * @internal diff --git a/tests/Integration/EventBus/EventBusTesterTest.php b/tests/Integration/EventBus/EventBusTesterTest.php index f8ee38709a..1c4c419003 100644 --- a/tests/Integration/EventBus/EventBusTesterTest.php +++ b/tests/Integration/EventBus/EventBusTesterTest.php @@ -5,6 +5,7 @@ namespace Tests\Tempest\Integration\EventBus; use LogicException; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\ExpectationFailedException; use Tempest\EventBus\EventBus; use Tempest\EventBus\Testing\FakeEventBus; @@ -16,109 +17,122 @@ */ final class EventBusTesterTest extends FrameworkIntegrationTestCase { - public function test_fake(): void + private EventBus $bus { + get => $this->container->get(EventBus::class); + } + + #[Test] + public function fake(): void { $this->eventBus->preventEventHandling(); - $this->assertInstanceOf(FakeEventBus::class, $this->container->get(EventBus::class)); + $this->assertInstanceOf(FakeEventBus::class, $this->bus); } - public function test_assertion_on_real_event_bus(): void + #[Test] + public function assertion_on_real_event_bus(): void { $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessage('Asserting against the event bus require the `preventEventHandling()` method to be called first.'); + $this->expectExceptionMessageMatches('*Asserting against the event bus require*'); $this->eventBus->assertDispatched('event-bus-fake-event'); } - public function test_assert_dispatched(): void + #[Test] + public function assert_dispatched(): void { $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event'); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertDispatched(FakeEvent::class); } - public function test_assert_dispatched_with_callback(): void + #[Test] + public function assert_dispatched_with_callback(): void { $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event', function (string $event) { return $event === 'event-bus-fake-event'; }); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertDispatched(FakeEvent::class, function (FakeEvent $event) { return $event->value === 'foo'; }); } - public function test_assert_dispatched_with_count(): void + #[Test] + public function assert_dispatched_with_count(): void { $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event', count: 1); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event', count: 2); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertDispatched(FakeEvent::class, count: 1); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertDispatched(FakeEvent::class, count: 2); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('baz')); + $this->bus->dispatch(new FakeEvent('baz')); $this->eventBus->assertDispatched(FakeEvent::class, count: 3); } - public function test_assert_dispatched_with_count_failure(): void + #[Test] + public function assert_dispatched_with_count_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The number of dispatches does not match'); $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event', count: 2); } - public function test_assert_dispatched_with_callback_failure(): void + #[Test] + public function assert_dispatched_with_callback_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The callback failed'); $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertDispatched('event-bus-fake-event', function (string $event) { return $event !== 'event-bus-fake-event'; }); } - public function test_assert_dispatched_object_with_callback_failure(): void + #[Test] + public function assert_dispatched_object_with_callback_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The callback failed'); $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertDispatched(FakeEvent::class, function (FakeEvent $event) { return $event->value === 'foobar'; }); } - public function test_assert_dispatched_failure(): void + #[Test] + public function assert_dispatched_failure(): void { $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The event was not dispatched.'); @@ -126,72 +140,72 @@ public function test_assert_dispatched_failure(): void $this->eventBus->assertDispatched('this-was-not-dispatched'); } - public function test_assert_not_dispatched(): void + #[Test] + public function assert_not_dispatched(): void { $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertNotDispatched('this-was-not-dispatched'); } - public function test_assert_not_dispatched_failure(): void + #[Test] + public function assert_not_dispatched_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The event was dispatched'); $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch('event-bus-fake-event'); + $this->bus->dispatch('event-bus-fake-event'); $this->eventBus->assertNotDispatched('event-bus-fake-event'); } - public function test_assert_not_dispatched_object_failure(): void + #[Test] + public function assert_not_dispatched_object_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The event was dispatched'); $this->eventBus->preventEventHandling(); - $this->container->get(EventBus::class)->dispatch(new FakeEvent('foo')); + $this->bus->dispatch(new FakeEvent('foo')); $this->eventBus->assertNotDispatched(FakeEvent::class); } - public function test_assert_listening_to(): void + #[Test] + public function assert_listening_to(): void { $this->eventBus->preventEventHandling(); - $this->container - ->get(EventBus::class) - ->listen(function (FakeEvent $_): never { - throw new LogicException('This should not be called'); - }); + $this->bus->listen(function (FakeEvent $_): never { + throw new LogicException('This should not be called'); + }); $this->eventBus->assertListeningTo(FakeEvent::class); $this->eventBus->assertListeningTo(FakeEvent::class); } - public function test_assert_listening_to_count(): void + #[Test] + public function assert_listening_to_count(): void { $this->eventBus->preventEventHandling(); - $this->container - ->get(EventBus::class) - ->listen(function (FakeEvent $_): never { - throw new LogicException('This should not be called'); - }); + $this->bus->listen(function (FakeEvent $_): never { + throw new LogicException('This should not be called'); + }); $this->eventBus->assertListeningTo(FakeEvent::class, count: 1); - $this->container - ->get(EventBus::class) - ->listen(function (FakeEvent $_): never { - throw new LogicException('This should not be called'); - }); + $this->bus->listen(function (FakeEvent $_): never { + throw new LogicException('This should not be called'); + }); $this->eventBus->assertListeningTo(FakeEvent::class, count: 2); } - public function test_assert_listening_to_failure(): void + #[Test] + public function assert_listening_to_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The event is not being listened to'); @@ -201,19 +215,35 @@ public function test_assert_listening_to_failure(): void $this->eventBus->assertListeningTo(FakeEvent::class); } - public function test_assert_listening_to_count_failure(): void + #[Test] + public function assert_listening_to_count_failure(): void { $this->expectException(ExpectationFailedException::class); $this->expectExceptionMessage('The number of handlers does not match'); $this->eventBus->preventEventHandling(); - $this->container - ->get(EventBus::class) - ->listen(function (FakeEvent $_): never { - throw new LogicException('This should not be called'); - }); + $this->bus->listen(function (FakeEvent $_): never { + throw new LogicException('This should not be called'); + }); $this->eventBus->assertListeningTo(FakeEvent::class, count: 2); } + + public function allows_handling(): void + { + $this->eventBus->recordEventDispatches(); + + $handled = false; + $this->bus->listen(function (FakeEvent $_) use (&$handled): void { + $handled = true; + }); + + $this->bus->dispatch(new FakeEvent('foo')); + + $this->eventBus->assertDispatched(FakeEvent::class); + $this->eventBus->assertListeningTo(FakeEvent::class); + + $this->assertTrue($handled); + } } diff --git a/tests/Integration/EventBus/EventIntegrationTestCase.php b/tests/Integration/EventBus/EventIntegrationTestCase.php index 78dcc6eaa2..376072f397 100644 --- a/tests/Integration/EventBus/EventIntegrationTestCase.php +++ b/tests/Integration/EventBus/EventIntegrationTestCase.php @@ -8,7 +8,7 @@ use Tests\Tempest\Fixtures\Events\ItHappened; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\event; +use function Tempest\EventBus\event; /** * @internal diff --git a/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php b/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php index c2816e7176..cbca648b15 100644 --- a/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php +++ b/tests/Integration/Framework/Commands/DatabaseSeedCommandTest.php @@ -2,7 +2,6 @@ namespace Tests\Tempest\Integration\Framework\Commands; -use Tempest\Core\AppConfig; use Tempest\Core\Environment; use Tempest\Database\Config\SeederConfig; use Tempest\Database\Migrations\CreateMigrationsTable; @@ -20,7 +19,7 @@ final class DatabaseSeedCommandTest extends FrameworkIntegrationTestCase { public function test_seed_with_selected_seeder(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -40,7 +39,7 @@ public function test_seed_with_selected_seeder(): void public function test_seed_with_manually_selected_seeder(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -69,7 +68,7 @@ public function test_migrate_fresh_seed_with_manually_selected_seeder(): void public function test_seed_all(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -96,7 +95,7 @@ public function test_seed_when_only_one_seeder_is_available(): void TestDatabaseSeeder::class, ]; - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -129,8 +128,7 @@ public function test_seed_via_migrate_fresh(): void public function test_db_seed_caution(): void { - $appConfig = $this->container->get(AppConfig::class); - $appConfig->environment = Environment::PRODUCTION; + $this->container->singleton(Environment::class, Environment::PRODUCTION); $this->console ->call('migrate:fresh --seed --all') diff --git a/tests/Integration/FrameworkIntegrationTestCase.php b/tests/Integration/FrameworkIntegrationTestCase.php index ee8dfd45fd..87ce6e6560 100644 --- a/tests/Integration/FrameworkIntegrationTestCase.php +++ b/tests/Integration/FrameworkIntegrationTestCase.php @@ -4,25 +4,13 @@ namespace Tests\Tempest\Integration; -use InvalidArgumentException; use Stringable; +use Tempest\Database\Builder\ModelInspector; use Tempest\Database\DatabaseInitializer; -use Tempest\Database\Migrations\MigrationManager; use Tempest\Discovery\DiscoveryLocation; use Tempest\Framework\Testing\IntegrationTest; -use Tempest\Reflection\MethodReflector; -use Tempest\Router\Route; -use Tempest\Router\RouteConfig; -use Tempest\Router\RouteDecorator; -use Tempest\Router\Routing\Construction\DiscoveredRoute; -use Tempest\Router\Routing\Construction\RouteConfigurator; -use Tempest\Router\Static\StaticPageConfig; -use Tempest\Router\StaticPage; -use Tempest\View\GenericView; -use Tempest\View\View; -use Tempest\View\ViewComponent; -use Tempest\View\ViewConfig; -use Tempest\View\ViewRenderer; +use Tempest\Support\Filesystem; +use Tempest\Support\Path; use function Tempest\Support\str; @@ -40,50 +28,21 @@ protected function setUp(): void { parent::setUp(); - // Database $this->container ->removeInitializer(DatabaseInitializer::class) ->addInitializer(TestingDatabaseInitializer::class); - $databaseConfigPath = __DIR__ . '/../Fixtures/Config/database.config.php'; + $defaultDatabaseConfigPath = Path\normalize(__DIR__, '..', 'Fixtures/Config/database.sqlite.php'); + $databaseConfigPath = Path\normalize(__DIR__, '..', 'Fixtures/Config/database.config.php'); - if (! file_exists($databaseConfigPath)) { - copy(__DIR__ . '/../Fixtures/Config/database.sqlite.php', $databaseConfigPath); + if (! Filesystem\exists($databaseConfigPath)) { + Filesystem\copy_file($defaultDatabaseConfigPath, $databaseConfigPath); } $this->container->config(require $databaseConfigPath); + $this->database->reset(migrate: false); - $this->rollbackDatabase(); - } - - protected function render(string|View $view, mixed ...$params): string - { - if (is_string($view)) { - $view = new GenericView($view); - } - - $view->data(...$params); - - return $this->container->get(ViewRenderer::class)->render($view); - } - - protected function registerViewComponent(string $name, string $html, string $file = '', bool $isVendor = false): void - { - $viewComponent = new ViewComponent( - name: $name, - contents: $html, - file: $file, - isVendorComponent: $isVendor, - ); - - $this->container->get(ViewConfig::class)->addViewComponent($viewComponent); - } - - protected function rollbackDatabase(): void - { - $migrationManager = $this->container->get(MigrationManager::class); - - $migrationManager->dropAll(); + ModelInspector::reset(); } protected function assertStringCount(string $subject, string $search, int $count): void @@ -91,53 +50,6 @@ protected function assertStringCount(string $subject, string $search, int $count $this->assertSame($count, substr_count($subject, $search)); } - protected function registerRoute(array|string|MethodReflector $action): void - { - $reflector = match (true) { - $action instanceof MethodReflector => $action, - is_array($action) => MethodReflector::fromParts(...$action), - default => MethodReflector::fromParts($action, '__invoke'), - }; - - if ($reflector->getAttribute(Route::class) === null) { - throw new InvalidArgumentException('Missing route attribute'); - } - - $configurator = $this->container->get(RouteConfigurator::class); - - $configurator->addRoute( - DiscoveredRoute::fromRoute( - $reflector->getAttribute(Route::class), - [ - ...$reflector->getDeclaringClass()->getAttributes(RouteDecorator::class), - ...$reflector->getAttributes(RouteDecorator::class), - ], - $reflector, - ), - ); - - $routeConfig = $this->container->get(RouteConfig::class); - $routeConfig->apply($configurator->toRouteConfig()); - } - - protected function registerStaticPage(array|string|MethodReflector $action): void - { - $reflector = match (true) { - $action instanceof MethodReflector => $action, - is_array($action) => MethodReflector::fromParts(...$action), - default => MethodReflector::fromParts($action, '__invoke'), - }; - - if ($reflector->getAttribute(StaticPage::class) === null) { - throw new InvalidArgumentException('Missing static page attribute'); - } - - $this->container->get(StaticPageConfig::class)->addHandler( - $reflector->getAttribute(StaticPage::class), - $reflector, - ); - } - protected function assertSnippetsMatch(string $expected, string $actual): void { $expected = str_replace([PHP_EOL, ' '], '', $expected); diff --git a/tests/Integration/Http/CleanupSessionsCommandTest.php b/tests/Integration/Http/CleanupSessionsCommandTest.php index c8844c3c3a..ded6c52a9c 100644 --- a/tests/Integration/Http/CleanupSessionsCommandTest.php +++ b/tests/Integration/Http/CleanupSessionsCommandTest.php @@ -4,10 +4,13 @@ namespace Tests\Tempest\Integration\Http; +use PHPUnit\Framework\Attributes\Test; use Tempest\DateTime\Duration; +use Tempest\Http\Session\CleanupSessionsCommand; use Tempest\Http\Session\Config\FileSessionConfig; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; +use Tempest\Support\Filesystem; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\internal_storage_path; @@ -17,10 +20,10 @@ */ final class CleanupSessionsCommandTest extends FrameworkIntegrationTestCase { - public function test_destroy_sessions(): void + #[Test] + public function destroy_sessions(): void { - @unlink(internal_storage_path('/tests/sessions/session_a')); - @unlink(internal_storage_path('/tests/sessions/session_b')); + Filesystem\delete(internal_storage_path('/tests/sessions/')); $clock = $this->clock('2024-01-01 00:00:00'); @@ -31,16 +34,22 @@ public function test_destroy_sessions(): void $sessionManager = $this->container->get(SessionManager::class); - $sessionManager->set(new SessionId('session_a'), 'test', 'value'); + $sessionA = $sessionManager->getOrCreate(new SessionId('session_a')); + $sessionA->set('test', 'value'); - $clock->plus(9); + $sessionManager->save($sessionA); - $sessionManager->set(new SessionId('session_b'), 'test', 'value'); + $clock->plus(Duration::seconds(9)); - $clock->plus(2); + $sessionB = $sessionManager->getOrCreate(new SessionId('session_b')); + $sessionB->set('test', 'value'); + + $sessionManager->save($sessionB); + + $clock->plus(Duration::seconds(2)); $this->console - ->call('session:clean') + ->call(CleanupSessionsCommand::class) ->assertContains('session_a') ->assertDoesNotContain('session_b'); diff --git a/tests/Integration/Http/CookieManagerTest.php b/tests/Integration/Http/CookieManagerTest.php index 86971a7217..108b97095c 100644 --- a/tests/Integration/Http/CookieManagerTest.php +++ b/tests/Integration/Http/CookieManagerTest.php @@ -62,7 +62,7 @@ public function test_removing_a_cookie(): void $this->http ->get('/') ->assertOk() - ->assertHeaderContains('set-cookie', 'new=; Expires=Wed, 31-Dec-1969 23:59:59 GMT; Max-Age=0; Path=/'); + ->assertHeaderContains('set-cookie', 'new=; Expires=Wed, 31-Dec-1969 23:59:59 GMT; Max-Age=0; Path=/; Secure; SameSite=Lax'); } public function test_manually_adding_a_cookie(): void diff --git a/tests/Integration/Http/CsrfTest.php b/tests/Integration/Http/CsrfTest.php deleted file mode 100644 index b3cffa1dbd..0000000000 --- a/tests/Integration/Http/CsrfTest.php +++ /dev/null @@ -1,156 +0,0 @@ -container->get(AppConfig::class)->environment = Environment::PRODUCTION; - - $token = $this->container->get(Session::class)->get(Session::CSRF_TOKEN_KEY); - - $this->http - ->get('/test') - ->assertHasCookie(VerifyCsrfMiddleware::CSRF_COOKIE_KEY, fn (string $value) => $value === $token); // @mago-expect lint:no-insecure-comparison - } - - #[TestWith([Method::POST])] - #[TestWith([Method::PUT])] - #[TestWith([Method::PATCH])] - #[TestWith([Method::DELETE])] - public function test_throws_when_missing_in_write_verbs(Method $method): void - { - $this->expectException(CsrfTokenDidNotMatch::class); - - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - $this->http->sendRequest(new GenericRequest($method, uri: '/test')); - } - - #[TestWith([Method::GET])] - #[TestWith([Method::OPTIONS])] - #[TestWith([Method::HEAD])] - public function test_allows_missing_in_read_verbs(Method $method): void - { - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - - $this->http - ->sendRequest(new GenericRequest($method, uri: '/test')) - ->assertOk(); - } - - public function test_throws_when_mismatch_from_body(): void - { - $this->expectException(CsrfTokenDidNotMatch::class); - - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - $this->container->get(Session::class)->set(Session::CSRF_TOKEN_KEY, 'abc'); - - $this->http->post('/test', [Session::CSRF_TOKEN_KEY => 'def']); - } - - public function test_throws_when_mismatch_from_header(): void - { - $this->expectException(CsrfTokenDidNotMatch::class); - - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - $this->container->get(Session::class)->set(Session::CSRF_TOKEN_KEY, 'abc'); - - $this->http->post('/test', [Session::CSRF_TOKEN_KEY => 'def']); - } - - public function test_matches_from_body(): void - { - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - - $session = $this->container->get(Session::class); - - $this->http - ->post('/test', [Session::CSRF_TOKEN_KEY => $session->token]) - ->assertOk(); - } - - public function test_matches_from_header_when_encrypted(): void - { - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - $session = $this->container->get(Session::class); - - // Encrypt the token as it would be in a real request - $sessionCookieValue = $this->container - ->get(Encrypter::class) - ->encrypt($session->token) - ->serialize(); - - $this->http - ->post('/test', headers: [VerifyCsrfMiddleware::CSRF_HEADER_KEY => $sessionCookieValue]) - ->assertOk(); - } - - public function test_throws_csrf_exception_when_header_is_non_serialized_hash(): void - { - $this->expectException(CsrfTokenDidNotMatch::class); - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; - $session = $this->container->get(Session::class); - - // simulate a non-serialized hash - $sessionCookieValue = 'i-am-not-correct'; - - $this->http - ->post('/test', headers: [VerifyCsrfMiddleware::CSRF_HEADER_KEY => $sessionCookieValue]); - } - - public function test_csrf_component(): void - { - $session = $this->container->get(Session::class); - $session->set(Session::CSRF_TOKEN_KEY, 'test'); - - $rendered = $this->render(<< - HTML); - - $this->assertSame( - '', - $rendered, - ); - } - - public function test_csrf_token_function(): void - { - $session = $this->container->get(Session::class); - $session->set(Session::CSRF_TOKEN_KEY, 'test'); - - $this->assertSame('test', csrf_token()); - } - - public function test_csrf_with_cached_view(): void - { - $this->get(ViewCache::class)->enabled = true; - - $oldVersion = $this->render(<< - HTML); - - $session = $this->container->get(Session::class); - $session->destroy(); - - $newVersion = $this->render(<< - HTML); - - $this->assertNotSame($oldVersion, $newVersion); - } -} diff --git a/tests/Integration/Http/DatabaseSessionTest.php b/tests/Integration/Http/DatabaseSessionTest.php index 10afe6a6c7..17cb0457da 100644 --- a/tests/Integration/Http/DatabaseSessionTest.php +++ b/tests/Integration/Http/DatabaseSessionTest.php @@ -7,19 +7,19 @@ use PHPUnit\Framework\Attributes\PreCondition; use PHPUnit\Framework\Attributes\Test; use Tempest\Clock\Clock; -use Tempest\Database\Database; use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\DateTime\Duration; -use Tempest\EventBus\EventBus; use Tempest\Http\Session\Config\DatabaseSessionConfig; use Tempest\Http\Session\Installer\CreateSessionsTable; use Tempest\Http\Session\Managers\DatabaseSession; use Tempest\Http\Session\Managers\DatabaseSessionManager; use Tempest\Http\Session\Session; use Tempest\Http\Session\SessionConfig; -use Tempest\Http\Session\SessionDestroyed; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; +use Tempest\Support\Random; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Database\query; @@ -29,7 +29,11 @@ */ final class DatabaseSessionTest extends FrameworkIntegrationTestCase { - public Session $session { + private SessionManager $manager { + get => $this->container->get(SessionManager::class); + } + + private Session $session { get => $this->container->get(Session::class); } @@ -41,7 +45,6 @@ protected function configure(): void $this->container->singleton(SessionManager::class, fn () => new DatabaseSessionManager( $this->container->get(Clock::class), $this->container->get(SessionConfig::class), - $this->container->get(Database::class), )); $this->database->reset(migrate: false); @@ -49,25 +52,56 @@ protected function configure(): void } #[Test] - public function create_session_from_container(): void + public function get_or_create_creates_new_session(): void { - $this->assertInstanceOf(Session::class, $this->session); - $this->assertSessionExistsInDatabase($this->session->id); + $this->eventBus->preventEventHandling(); + + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + + $this->assertInstanceOf(Session::class, $session); + $this->assertEquals($sessionId, $session->id); + + $this->manager->save($session); + $this->assertSessionExistsInDatabase($sessionId); + + $this->eventBus->assertDispatched( + event: SessionCreated::class, + callback: function (SessionCreated $event) use ($sessionId): void { + $this->assertEquals($sessionId, $event->session->id); + }, + count: 1, + ); + } + + #[Test] + public function get_or_create_loads_existing_session(): void + { + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $session->set('key', 'value'); + + $this->manager->save($session); + + $loaded = $this->manager->getOrCreate($sessionId); + + $this->assertEquals($sessionId, $loaded->id); + $this->assertTrue($session->createdAt->isSameMinute($loaded->createdAt)); + $this->assertEquals('value', $loaded->get('key')); } #[Test] - public function put_get(): void + public function save_persists_session_to_database(): void { $this->session->set('frieren', 'elf_mage'); + $this->manager->save($this->session); - $this->assertEquals('elf_mage', $this->session->get('frieren')); + $this->assertSessionExistsInDatabase($this->session->id); $this->assertSessionDataInDatabase($this->session->id, ['frieren' => 'elf_mage']); - - $this->assertEquals('deceased_hero', $this->session->get('himmel', 'deceased_hero')); } #[Test] - public function put_nested_data(): void + public function save_persists_nested_data(): void { $data = [ 'members' => ['Frieren', 'Fern', 'Stark'], @@ -80,93 +114,62 @@ public function put_nested_data(): void ]; $this->session->set('party', $data); + $this->manager->save($this->session); - $this->assertEquals($data, $this->session->get('party')); $this->assertSessionDataInDatabase($this->session->id, ['party' => $data]); } #[Test] - public function remove(): void + public function save_updates_last_active_timestamp(): void { - $this->session->set('spell', 'Zoltraak'); - $this->session->set('caster', 'Frieren'); + $clock = $this->clock('2025-01-01 00:00:00'); - $this->assertEquals('Zoltraak', $this->session->get('spell')); + $this->manager->save($this->session); + $originalTimestamp = $this->getSessionLastActiveTimestamp($this->session->id); - $this->session->remove('spell'); - - $this->assertNull($this->session->get('spell')); - $this->assertEquals('Frieren', $this->session->get('caster')); - $this->assertSessionDataInDatabase($this->session->id, ['caster' => 'Frieren']); - } - - #[Test] - public function all(): void - { - $data = [ - 'mage' => 'Frieren', - 'apprentice' => 'Fern', - 'warrior' => 'Stark', - ]; + $clock->plus(Duration::minutes(5)); - foreach ($data as $key => $value) { - $this->session->set($key, $value); - } + $this->session->set('action', 'spell_cast'); + $this->manager->save($this->session); + $updatedTimestamp = $this->getSessionLastActiveTimestamp($this->session->id); - $this->assertEquals($data, $this->session->all()); + $this->assertTrue($updatedTimestamp->after($originalTimestamp)); } #[Test] - public function destroy(): void + public function delete_removes_session_from_database(): void { - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('test_session_destroy'); + $this->eventBus->preventEventHandling(); - $session = $manager->create($sessionId); + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); $session->set('magic_type', 'offensive'); - $this->assertSessionExistsInDatabase($sessionId); + $this->manager->save($session); - $events = []; - $eventBus = $this->container->get(EventBus::class); - $eventBus->listen(function (SessionDestroyed $event) use (&$events): void { - $events[] = $event; - }); + $this->assertSessionExistsInDatabase($sessionId); - $session->destroy(); + $this->manager->delete($session); $this->assertSessionNotExistsInDatabase($sessionId); - $this->assertCount(1, $events); - $this->assertEquals((string) $sessionId, (string) $events[0]->id); - } - #[Test] - public function set_previous_url(): void - { - $session = $this->container->get(Session::class); - $session->setPreviousUrl('https://frieren.wiki/magic-academy'); - - $this->assertEquals('https://frieren.wiki/magic-academy', $session->getPreviousUrl()); - } - - #[Test] - public function is_valid(): void - { - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('new_session_validity'); - - $session = $manager->create($sessionId); - - $this->assertTrue($session->isValid()); - $this->assertTrue($manager->isValid($sessionId)); + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event) use ($sessionId): void { + $this->assertEquals($sessionId, $event->id); + }, + count: 1, + ); } #[Test] - public function is_valid_for_unknown_session(): void + public function is_valid_checks_expiration(): void { - $manager = $this->container->get(SessionManager::class); + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $this->manager->save($session); - $this->assertFalse($manager->isValid(new SessionId('unknown_session'))); + $this->assertTrue($this->manager->isValid($session)); } #[Test] @@ -176,128 +179,66 @@ public function is_valid_for_expired_session(): void $this->container->config(new DatabaseSessionConfig(expiration: Duration::second())); - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('expired_session'); - - $session = $manager->create($sessionId); + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $this->manager->save($session); - $this->assertTrue($session->isValid()); + $this->assertTrue($this->manager->isValid($session)); $clock->plus(2); - $this->assertFalse($session->isValid()); - $this->assertFalse($manager->isValid($sessionId)); + $this->assertFalse($this->manager->isValid($session)); } #[Test] - public function session_expires_based_on_last_activity(): void + public function delete_expired_sessions_removes_old_records(): void { - $clock = $this->clock('2023-01-01 00:00:00'); + $this->eventBus->preventEventHandling(); - $this->container->config(new DatabaseSessionConfig(expiration: Duration::minutes(30))); - - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('last_activity_test'); - - // Create session - $session = $manager->create($sessionId); - $this->assertTrue($session->isValid()); - - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); - - // Perform activity - $session->set('activity', 'user_action'); - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); - $this->assertTrue($manager->isValid($sessionId)); - - // Move forward another 10 minutes, now 35 minutes from last activity - $clock->plus(Duration::minutes(10)); - $this->assertFalse($session->isValid()); - $this->assertFalse($manager->isValid($sessionId)); - } - - #[Test] - public function session_reflash(): void - { - $session = $this->container->get(Session::class); - - $session->flash('success', 'Spell learned: Zoltraak'); - - $this->assertEquals('Spell learned: Zoltraak', $session->get('success')); - - $session->reflash(); - $session->cleanup(); - - $this->assertEquals('Spell learned: Zoltraak', $session->get('success')); - } - - #[Test] - public function cleanup_removes_expired_sessions(): void - { - $clock = $this->clock('2023-01-01 00:00:00'); + $clock = $this->clock('2025-01-01 00:00:00'); $this->container->config(new DatabaseSessionConfig(expiration: Duration::minutes(30))); - $manager = $this->container->get(SessionManager::class); - - $activeSessionId = new SessionId('active_session'); - $activeSession = $manager->create($activeSessionId); - $activeSession->set('status', 'active'); - - $clock->minus(Duration::hour()); - $expiredSessionId = new SessionId('expired_session'); - $expiredSession = $manager->create($expiredSessionId); - $expiredSession->set('status', 'expired'); - - $clock->plus(Duration::hour()); + $activeId = $this->createSessionId(); + $active = $this->manager->getOrCreate($activeId); + $active->set('status', 'active'); - $this->assertSessionExistsInDatabase($activeSessionId); - $this->assertSessionExistsInDatabase($expiredSessionId); + $this->manager->save($active); - $manager->cleanup(); + $expiredId = $this->createSessionId(); + $expired = $this->manager->getOrCreate($expiredId); + $expired->set('status', 'expired'); - $this->assertSessionExistsInDatabase($activeSessionId); - $this->assertSessionNotExistsInDatabase($expiredSessionId); - } - - #[Test] - public function session_updates_last_active_timestamp(): void - { - $clock = $this->clock('2023-01-01 12:00:00'); - - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('timestamp_test'); + $this->manager->save($expired); - $session = $manager->create($sessionId); - $originalTimestamp = $this->getSessionLastActiveTimestamp($sessionId); + // expire the second session + $clock->plus(Duration::minutes(35)); - $clock->plus(Duration::minutes(5)); + // keep active session fresh + $this->manager->save($active); - $session->set('action', 'spell_cast'); - $updatedTimestamp = $this->getSessionLastActiveTimestamp($sessionId); + $this->assertSessionExistsInDatabase($activeId); + $this->assertSessionExistsInDatabase($expiredId); - $this->assertTrue($updatedTimestamp->after($originalTimestamp)); - } - - #[Test] - public function session_persists_csrf_token(): void - { - $session = $this->container->get(Session::class); - $token = $session->token; + $this->manager->deleteExpiredSessions(); - $data = $this->getSessionDataFromDatabase($session->id); + $this->assertSessionExistsInDatabase($activeId); + $this->assertSessionNotExistsInDatabase($expiredId); - $this->assertEquals($token, $data[Session::CSRF_TOKEN_KEY]); - $this->assertEquals($token, $session->token); + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event) use ($expiredId): void { + $this->assertEquals($expiredId, $event->id); + }, + count: 1, + ); } private function assertSessionExistsInDatabase(SessionId $sessionId): void { $session = query(DatabaseSession::class) ->select() - ->where('session_id', (string) $sessionId) + ->where('id', (string) $sessionId) ->first(); $this->assertNotNull($session, "Session {$sessionId} should exist in database"); @@ -307,7 +248,7 @@ private function assertSessionNotExistsInDatabase(SessionId $sessionId): void { $session = query(DatabaseSession::class) ->select() - ->where('session_id', (string) $sessionId) + ->where('id', (string) $sessionId) ->first(); $this->assertNull($session, "Session {$sessionId} should not exist in database"); @@ -317,7 +258,7 @@ private function assertSessionDataInDatabase(SessionId $sessionId, array $data): { $session = query(DatabaseSession::class) ->select() - ->where('session_id', (string) $sessionId) + ->where('id', (string) $sessionId) ->first(); $this->assertNotNull($session, "Session {$sessionId} should exist in database"); @@ -329,25 +270,20 @@ private function assertSessionDataInDatabase(SessionId $sessionId, array $data): } } - private function getSessionDataFromDatabase(SessionId $sessionId): array - { - $session = query(DatabaseSession::class) - ->select() - ->where('session_id', (string) $sessionId) - ->first(); - - return unserialize($session->data ?? ''); - } - private function getSessionLastActiveTimestamp(SessionId $sessionId): \Tempest\DateTime\DateTime { $session = query(DatabaseSession::class) ->select() - ->where('session_id', (string) $sessionId) + ->where('id', (string) $sessionId) ->first(); $this->assertNotNull($session, "Session {$sessionId} should exist in database"); return $session->last_active_at; } + + private function createSessionId(): SessionId + { + return new SessionId(Random\uuid()); + } } diff --git a/tests/Integration/Http/Exceptions/ExceptionRendererTest.php b/tests/Integration/Http/Exceptions/ExceptionRendererTest.php new file mode 100644 index 0000000000..653695a589 --- /dev/null +++ b/tests/Integration/Http/Exceptions/ExceptionRendererTest.php @@ -0,0 +1,292 @@ +container->singleton(Kernel::class, fn () => new class($this->container->get(FrameworkKernel::class)) implements Kernel { + public const string VERSION = '1.0.0-alpha.6'; + + public string $root; + + public string $internalStorage; + + public array $discoveryLocations; + + public array $discoveryClasses; + + public Container $container; + + public function __construct(FrameworkKernel $kernel) + { + $this->root = $kernel->root; + $this->internalStorage = $kernel->internalStorage; + $this->discoveryLocations = $kernel->discoveryLocations; + $this->discoveryClasses = $kernel->discoveryClasses; + $this->container = $kernel->container; + } + + public static function boot(string $root, array $discoveryLocations = [], ?Container $container = null, ?string $internalStorage = null): self + { + return Kernel::boot($root, $discoveryLocations, $container, $internalStorage); // @phpstan-ignore-line + } + + public function shutdown(int|string $status = ''): never + { + throw new Exception('Shutdown.'); + } + }); + + $this->container->singleton(ResponseSender::class, fn () => new class($this) implements ResponseSender { + public function __construct( + private ExceptionRendererTest $case, + ) {} + + public function send(Response $response): Response + { + $this->case->response = $response; + + return $response; + } + }); + } + + #[Test] + public function custom_json_exception_renderer(): void + { + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->addExceptionRenderer(CustomJsonValidationRenderer::class, Priority::HIGH); + + $this->container->singleton( + Request::class, + fn () => new GenericRequest(Method::GET, '/test', headers: ['Accept' => 'application/json']), + ); + + $this->callExceptionHandler(function (): void { + $handler = $this->container->get(HttpExceptionHandler::class); + $handler->handle(new CustomValidationException('Custom validation failed')); + }); + + $body = Json\decode($this->response->body); + + $this->assertSame('json', $body['custom']); + $this->assertSame('Custom validation failed', $body['message']); + $this->assertContains('application/json', $this->response->getHeader('content-type')->values); + $this->assertSame(Status::UNPROCESSABLE_CONTENT, $this->response->status); + } + + #[Test] + public function custom_html_exception_renderer(): void + { + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->addExceptionRenderer(CustomHtmlValidationRenderer::class, Priority::HIGH); + + $this->container->singleton( + Request::class, + fn () => new GenericRequest(Method::GET, '/test', headers: ['Accept' => 'text/html']), + ); + + $this->callExceptionHandler(function (): void { + $handler = $this->container->get(HttpExceptionHandler::class); + $handler->handle(new CustomValidationException('Custom validation failed')); + }); + + $this->assertSame(Status::UNPROCESSABLE_CONTENT, $this->response->status); + $this->assertContains('text/html', $this->response->getHeader('content-type')->values); + $this->assertStringContainsString('Custom validation failed', $this->response->body); + } + + #[Test] + public function falls_back_to_default_renderer_when_no_custom_match(): void + { + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->addExceptionRenderer(CustomJsonValidationRenderer::class, Priority::HIGH); + + $this->container->singleton( + Request::class, + fn () => new GenericRequest(Method::GET, '/test', headers: ['Accept' => 'application/json']), + ); + + $this->callExceptionHandler(function (): void { + $handler = $this->container->get(HttpExceptionHandler::class); + $handler->handle(new Exception('Regular exception')); + }); + + $this->assertNotNull($this->response, 'Response should not be null'); + $this->assertSame(Status::INTERNAL_SERVER_ERROR, $this->response->status); + $this->assertSame('Internal Server Error', $this->response->body['message']); + } + + #[Test] + public function priority_ordering(): void + { + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->addExceptionRenderer(CustomJsonValidationRenderer::class, Priority::LOW); + $routeConfig->addExceptionRenderer(HighPriorityRenderer::class, Priority::HIGHEST); + + $this->container->singleton( + Request::class, + fn () => new GenericRequest(Method::GET, '/test', headers: ['Accept' => 'application/json']), + ); + + $this->callExceptionHandler(function (): void { + $handler = $this->container->get(HttpExceptionHandler::class); + $handler->handle(new CustomValidationException('Test')); + }); + + $body = Json\decode($this->response->body); + + $this->assertSame('high', $body['priority']); + $this->assertNotNull($this->response, 'Response should not be null'); + } + + #[Test] + public function custom_404_renderer(): void + { + $routeConfig = $this->container->get(RouteConfig::class); + $routeConfig->addExceptionRenderer(Custom404Renderer::class, Priority::HIGH); + + $this->container->singleton( + Request::class, + fn () => new GenericRequest(Method::GET, '/test', headers: ['Accept' => 'text/html']), + ); + + $this->callExceptionHandler(function (): void { + $handler = $this->container->get(HttpExceptionHandler::class); + $handler->handle(new HttpRequestFailed(status: Status::NOT_FOUND)); + }); + + $this->assertSame(Status::NOT_FOUND, $this->response->status); + $this->assertStringContainsString('Custom 404 page', (string) $this->response->body); + } + + private function callExceptionHandler(Closure $callback): void + { + try { + $callback(); + } catch (Throwable $throwable) { + $this->assertSame('Shutdown.', $throwable->getMessage()); + } + } +} + +final class CustomValidationException extends Exception +{ +} + +#[Priority(Priority::HIGH)] +final readonly class CustomJsonValidationRenderer implements ExceptionRenderer +{ + public function canRender(Throwable $throwable, Request $request): bool + { + return $throwable instanceof CustomValidationException && $request->accepts(ContentType::JSON); + } + + public function render(Throwable $throwable): Response + { + return new GenericResponse( + status: Status::UNPROCESSABLE_CONTENT, + body: Json\encode(['custom' => 'json', 'message' => $throwable->getMessage()]), + headers: [ + 'Content-Type' => ContentType::JSON->value, + ], + ); + } +} + +#[Priority(Priority::HIGH)] +final readonly class CustomHtmlValidationRenderer implements ExceptionRenderer +{ + public function canRender(Throwable $throwable, Request $request): bool + { + return $throwable instanceof CustomValidationException && $request->accepts(ContentType::HTML); + } + + public function render(Throwable $throwable): Response + { + return new GenericResponse( + status: Status::UNPROCESSABLE_CONTENT, + body: '' . $throwable->getMessage() . '', + headers: [ + 'Content-Type' => ContentType::HTML->value, + ], + ); + } +} + +final readonly class HighPriorityRenderer implements ExceptionRenderer +{ + public function canRender(Throwable $throwable, Request $request): bool + { + return $throwable instanceof CustomValidationException && $request->accepts(ContentType::JSON); + } + + public function render(Throwable $throwable): Response + { + return new GenericResponse( + status: Status::UNPROCESSABLE_CONTENT, + body: Json\encode(['priority' => 'high']), + headers: [ + 'Content-Type' => ContentType::JSON->value, + ], + ); + } +} + +final readonly class Custom404Renderer implements ExceptionRenderer +{ + public function canRender(Throwable $throwable, Request $request): bool + { + if (! $request->accepts(ContentType::HTML)) { + return false; + } + + if (! $throwable instanceof HttpRequestFailed) { + return false; + } + + return $throwable->status === Status::NOT_FOUND; + } + + public function render(Throwable $throwable): Response + { + return new GenericResponse( + status: Status::NOT_FOUND, + body: '

Custom 404 page

The page you are looking for does not exist.

', + headers: [ + 'Content-Type' => ContentType::HTML->value, + ], + ); + } +} diff --git a/tests/Integration/Http/Exceptions/HtmlExceptionRendererTest.php b/tests/Integration/Http/Exceptions/HtmlExceptionRendererTest.php new file mode 100644 index 0000000000..509393b7dc --- /dev/null +++ b/tests/Integration/Http/Exceptions/HtmlExceptionRendererTest.php @@ -0,0 +1,195 @@ + $this->container->get(HtmlExceptionRenderer::class); + } + + #[Test] + #[TestWith(['text/html', true])] + #[TestWith(['application/xhtml+xml', true])] + #[TestWith(['text/html, application/json', true])] + #[TestWith(['application/json', false])] + public function can_render_depends_on_accept_header(string $accept, bool $expectsToRender): void + { + $this->container->singleton( + GenericRequest::class, + $request = new GenericRequest( + method: Method::GET, + uri: '/', + headers: ['Accept' => $accept], + ), + ); + + $this->assertSame($expectsToRender, $this->renderer->canRender(new Exception(), $request)); + } + + #[Test] + public function converts_to_response(): void + { + $response = $this->renderer->render(new ExceptionThatConvertsToRedirectResponse()); + + $this->assertSame(Status::FOUND, $response->status); + } + + #[Test] + public function validation_failed(): void + { + $response = $this->renderer->render(new ValidationFailed( + failingRules: [ + 'name' => [new FailingRule(new IsNotNull())], + ], + subject: new GenericRequest( + method: Method::POST, + uri: '/test', + body: ['name' => null], + ), + )); + + $this->assertInstanceOf(GenericResponse::class, $response); + $this->assertSame(Status::UNPROCESSABLE_CONTENT, $response->status); + $this->assertNotNull($response->getHeader('x-validation')); + } + + #[Test] + public function access_denied_with_custom_message(): void + { + $response = $this->renderer->render(new AccessWasDenied( + accessDecision: AccessDecision::denied('Access denied'), + )); + + $this->assertSame(Status::FORBIDDEN, $response->status); + $this->assertSame('Access denied', $response->body->data['message']); + } + + #[Test] + public function http_request_failed(): void + { + $response = $this->renderer->render(new HttpRequestFailed(Status::BAD_REQUEST)); + + $this->assertSame(Status::BAD_REQUEST, $response->status); + } + + #[Test] + public function generic_exception(): void + { + $response = $this->renderer->render(new Exception('Something went wrong')); + + $this->assertSame(Status::INTERNAL_SERVER_ERROR, $response->status); + } + + #[Test] + public function renders_development_exception_in_local_environment(): void + { + $this->container->singleton(Environment::class, Environment::LOCAL); + $this->container->singleton(GenericRequest::class, new GenericRequest( + method: Method::GET, + uri: '/', + )); + + $this->assertInstanceOf( + expected: DevelopmentException::class, + actual: $this->renderer->render(new Exception('Something went wrong')), + ); + } + + #[Test] + public function does_not_render_development_exception_for_not_found_in_local(): void + { + $this->container->singleton(Environment::class, Environment::LOCAL); + $this->container->singleton(GenericRequest::class, new GenericRequest( + method: Method::GET, + uri: '/', + )); + + $response = $this->renderer->render(new HttpRequestFailed(Status::NOT_FOUND)); + + $this->assertNotInstanceOf(DevelopmentException::class, $response); + $this->assertSame(Status::NOT_FOUND, $response->status); + } + + #[Test] + public function converts_request_failed_string_bodies_to_proper_responses(): void + { + $response = $this->renderer->render(new HttpRequestFailed( + status: Status::NOT_FOUND, + cause: new NotFound('custom 404'), + )); + + $this->assertInstanceOf(GenericResponse::class, $response); + $this->assertSame(Status::NOT_FOUND, $response->status); + $this->assertInstanceOf(GenericView::class, $response->body); + $this->assertSame('custom 404', $response->body->data['message']); + } + + #[Test] + public function renders_original_response_body(): void + { + $response = $this->renderer->render(new HttpRequestFailed( + status: Status::NOT_FOUND, + cause: new NotFound(new GenericView('./error.view.php')), + )); + + $this->assertInstanceOf(NotFound::class, $response); + $this->assertSame(Status::NOT_FOUND, $response->status); + $this->assertInstanceOf(GenericView::class, $response->body); + $this->assertSame('./error.view.php', $response->body->path); + } + + #[Test] + public function uses_translations(): void + { + $this->container->get(Catalog::class)->add( + locale: Locale::ENGLISH, + key: 'http_status_error.418', + message: 'I am a teapot.', + ); + + $response = $this->renderer->render(new HttpRequestFailed(Status::IM_A_TEAPOT)); + + $this->assertSame(Status::IM_A_TEAPOT, $response->status); + $this->assertInstanceOf(View::class, $response->body); + $this->assertSame('I am a teapot.', $response->body->data['message']); + } + + #[Test] + public function http_request_failed_with_custom_message(): void + { + $response = $this->renderer->render(new HttpRequestFailed( + status: Status::BAD_REQUEST, + message: 'Custom error message', + )); + + $this->assertSame(Status::BAD_REQUEST, $response->status); + $this->assertInstanceOf(GenericResponse::class, $response); + $this->assertInstanceOf(GenericView::class, $response->body); + $this->assertSame('Custom error message', $response->body->data['message']); + } +} diff --git a/tests/Integration/Http/HttpExceptionHandlerTest.php b/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php similarity index 81% rename from tests/Integration/Http/HttpExceptionHandlerTest.php rename to tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php index c73bc85dea..370c7d9e1f 100644 --- a/tests/Integration/Http/HttpExceptionHandlerTest.php +++ b/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php @@ -1,27 +1,24 @@ assertSame(Status::INTERNAL_SERVER_ERROR, $this->response->status); - $this->assertStringContainsString('An unexpected server error occurred', $this->render($this->response->body)); + $this->assertStringContainsString('An unexpected server error occurred', $this->response->body->data['message']); } #[TestWith([Status::BAD_REQUEST])] @@ -124,7 +121,7 @@ public function test_exception_handler_returns_same_code_as_http_exception(Statu { $this->callExceptionHandler(function () use ($status): void { $handler = $this->container->get(HttpExceptionHandler::class); - $handler->handle(new HttpRequestFailed(new GenericRequest(Method::GET, '/test'), $status)); + $handler->handle(new HttpRequestFailed($status)); }); $this->assertSame($status, $this->response->status); @@ -132,9 +129,9 @@ public function test_exception_handler_returns_same_code_as_http_exception(Statu public function test_exception_handler_runs_exception_processors(): void { - $this->exceptions->preventReporting(false); + $this->exceptions->preventProcessing(false); - $this->container->get(AppConfig::class)->exceptionProcessors[] = NullExceptionProcessor::class; + $this->container->get(ExceptionsConfig::class)->setReporters([NullExceptionReporter::class]); $thrown = new ExceptionWithContext(); @@ -143,20 +140,10 @@ public function test_exception_handler_runs_exception_processors(): void $handler->handle($thrown); }); - $this->assertContains($thrown, NullExceptionProcessor::$exceptions); - $this->assertArrayHasKey('foo', NullExceptionProcessor::$exceptions[0]->context()); + $this->assertContains($thrown, NullExceptionReporter::$exceptions); + $this->assertArrayHasKey('foo', NullExceptionReporter::$exceptions[0]->context()); - NullExceptionProcessor::$exceptions = []; - } - - public function test_exception_handler_returns_unprocessable_for_csrf_mismatch(): void - { - $this->callExceptionHandler(function (): void { - $handler = $this->container->get(HttpExceptionHandler::class); - $handler->handle(new CsrfTokenDidNotMatch()); - }); - - $this->assertSame(Status::UNPROCESSABLE_CONTENT, $this->response->status); + NullExceptionReporter::$exceptions = []; } private function callExceptionHandler(Closure $callback): void diff --git a/tests/Integration/Http/Exceptions/JsonExceptionRendererTest.php b/tests/Integration/Http/Exceptions/JsonExceptionRendererTest.php new file mode 100644 index 0000000000..292bdcd660 --- /dev/null +++ b/tests/Integration/Http/Exceptions/JsonExceptionRendererTest.php @@ -0,0 +1,182 @@ + $this->container->get(JsonExceptionRenderer::class); + } + + #[Test] + #[TestWith(['application/json', true])] + #[TestWith(['application/json, text/html', true])] + #[TestWith(['text/html', false])] + #[TestWith(['application/xhtml+xml', false])] + public function can_render_depends_on_accept_header(string $accept, bool $expectsToRender): void + { + $this->container->singleton( + GenericRequest::class, + $request = new GenericRequest( + method: Method::GET, + uri: '/', + headers: ['Accept' => $accept], + ), + ); + + $this->assertSame($expectsToRender, $this->renderer->canRender(new Exception(), $request)); + } + + #[Test] + public function converts_to_response(): void + { + $response = $this->renderer->render(new ExceptionThatConvertsToRedirectResponse()); + + $this->assertSame(Status::FOUND, $response->status); + } + + #[Test] + public function validation_failed(): void + { + $response = $this->renderer->render(new ValidationFailed( + failingRules: [ + 'name' => [new FailingRule(new IsNotNull())], + 'email' => [new FailingRule(new IsNotNull())], + ], + subject: new GenericRequest( + method: Method::POST, + uri: '/test', + body: ['name' => null, 'email' => null], + ), + )); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::UNPROCESSABLE_CONTENT, $response->status); + $this->assertArrayHasKey('message', $response->body); + $this->assertArrayHasKey('errors', $response->body); + $this->assertArrayHasKey('name', $response->body['errors']); + $this->assertArrayHasKey('email', $response->body['errors']); + } + + #[Test] + public function access_denied_with_custom_message(): void + { + $response = $this->renderer->render(new AccessWasDenied( + accessDecision: AccessDecision::denied('Custom access denied message'), + )); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::FORBIDDEN, $response->status); + $this->assertArrayHasKey('message', $response->body); + $this->assertSame('Custom access denied message', $response->body['message']); + } + + #[Test] + public function http_request_failed(): void + { + $response = $this->renderer->render(new HttpRequestFailed(Status::BAD_REQUEST)); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::BAD_REQUEST, $response->status); + } + + #[Test] + public function http_request_failed_not_found(): void + { + $response = $this->renderer->render(new HttpRequestFailed(Status::NOT_FOUND)); + + $this->assertInstanceOf(NotFound::class, $response); + $this->assertSame(Status::NOT_FOUND, $response->status); + } + + #[Test] + public function generic_exception(): void + { + $response = $this->renderer->render(new Exception('Something went wrong')); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::INTERNAL_SERVER_ERROR, $response->status); + $this->assertArrayHasKey('message', $response->body); + } + + #[Test] + public function includes_debug_info_in_local_environment(): void + { + $this->container->singleton(Environment::class, Environment::LOCAL); + + $exception = new Exception('Test error message'); + $response = $this->renderer->render($exception); + + $this->assertInstanceOf(Json::class, $response); + $this->assertArrayHasKey('debug', $response->body); + $this->assertSame('Test error message', $response->body['debug']['message']); + $this->assertSame(Exception::class, $response->body['debug']['exception']); + $this->assertArrayHasKey('file', $response->body['debug']); + $this->assertArrayHasKey('line', $response->body['debug']); + $this->assertArrayHasKey('trace', $response->body['debug']); + } + + #[Test] + public function does_not_include_debug_info_in_production_environment(): void + { + $this->container->singleton(Environment::class, Environment::PRODUCTION); + + $response = $this->renderer->render(new Exception('Test error')); + + $this->assertInstanceOf(Json::class, $response); + $this->assertArrayNotHasKey('debug', $response->body); + } + + #[Test] + public function http_request_failed_with_string_cause_body(): void + { + $response = $this->renderer->render(new HttpRequestFailed( + status: Status::BAD_REQUEST, + cause: new NotFound('Custom error message'), + )); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::BAD_REQUEST, $response->status); + $this->assertSame('Custom error message', $response->body['message']); + } + + #[Test] + public function uses_status_description(): void + { + $response = $this->renderer->render(new HttpRequestFailed(Status::IM_A_TEAPOT)); + + $this->assertSame("I'm a teapot", $response->body['message']); + } + + #[Test] + public function http_request_failed_with_custom_message(): void + { + $response = $this->renderer->render(new HttpRequestFailed( + status: Status::BAD_REQUEST, + message: 'Custom error message', + )); + + $this->assertInstanceOf(Json::class, $response); + $this->assertSame(Status::BAD_REQUEST, $response->status); + $this->assertSame('Custom error message', $response->body['message']); + } +} diff --git a/tests/Integration/Http/FileSessionTest.php b/tests/Integration/Http/FileSessionTest.php index 099a9eac61..8fef58cdf0 100644 --- a/tests/Integration/Http/FileSessionTest.php +++ b/tests/Integration/Http/FileSessionTest.php @@ -4,6 +4,9 @@ namespace Tests\Tempest\Integration\Http; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; use Tempest\Clock\Clock; use Tempest\Core\FrameworkKernel; use Tempest\DateTime\Duration; @@ -11,13 +14,14 @@ use Tempest\Http\Session\Managers\FileSessionManager; use Tempest\Http\Session\Session; use Tempest\Http\Session\SessionConfig; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; use Tempest\Support\Filesystem; +use Tempest\Support\Path; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\Support\path; - /** * @internal */ @@ -25,118 +29,160 @@ final class FileSessionTest extends FrameworkIntegrationTestCase { private string $path = __DIR__ . '/Fixtures/tmp'; - protected function setUp(): void - { - parent::setUp(); + private SessionManager $manager { + get => $this->container->get(SessionManager::class); + } + private Session $session { + get => $this->container->get(Session::class); + } + + #[PreCondition] + protected function configure(): void + { Filesystem\ensure_directory_empty($this->path); $this->path = realpath($this->path); - $this->container->get(FrameworkKernel::class)->internalStorage = realpath($this->path); - $this->container->config(new FileSessionConfig(path: 'sessions', expiration: Duration::hours(2))); - $this->container->singleton( - SessionManager::class, - fn () => new FileSessionManager( - $this->container->get(Clock::class), - $this->container->get(SessionConfig::class), - ), - ); + + $this->container->config(new FileSessionConfig( + path: 'sessions', + expiration: Duration::hours(2), + )); + + $this->container->singleton(SessionManager::class, fn () => new FileSessionManager( + $this->container->get(Clock::class), + $this->container->get(SessionConfig::class), + )); } - protected function tearDown(): void + #[PostCondition] + protected function cleanup(): void { Filesystem\delete_directory($this->path); } - public function test_create_session_from_container(): void + #[Test] + public function get_or_create_creates_new_session(): void { - $session = $this->container->get(Session::class); + $this->eventBus->preventEventHandling(); + + $sessionId = new SessionId('new_session'); + $session = $this->manager->getOrCreate($sessionId); $this->assertInstanceOf(Session::class, $session); + $this->assertEquals($sessionId, $session->id); + + $this->eventBus->assertDispatched( + event: SessionCreated::class, + callback: function (SessionCreated $event) use ($sessionId): void { + $this->assertEquals($sessionId, $event->session->id); + }, + count: 1, + ); } - public function test_put_get(): void + #[Test] + public function get_or_create_loads_existing_session(): void { - $session = $this->container->get(Session::class); - - $session->set('test', 'value'); + $sessionId = new SessionId('existing_session'); + $session = $this->manager->getOrCreate($sessionId); + $session->set('key', 'value'); - $value = $session->get('test'); - $this->assertEquals('value', $value); - } - - public function test_remove(): void - { - $session = $this->container->get(Session::class); + $this->manager->save($session); - $session->set('test', 'value'); - $session->remove('test'); + $loaded = $this->manager->getOrCreate($sessionId); - $value = $session->get('test'); - $this->assertNull($value); + $this->assertEquals($sessionId, $loaded->id); + $this->assertTrue($session->createdAt->isSameMinute($loaded->createdAt)); + $this->assertEquals('value', $loaded->get('key')); } - public function test_destroy(): void + #[Test] + public function save_persists_session_to_file(): void { - $session = $this->container->get(Session::class); - $path = path($this->path, 'sessions', (string) $session->id)->toString(); + $this->session->set('test_key', 'test_value'); + $this->manager->save($this->session); + + $path = Path\normalize($this->path, 'sessions', (string) $this->session->id); $this->assertFileExists($path); - $session->destroy(); + $content = unserialize(file_get_contents($path)); - $this->assertFileDoesNotExist($path); + $this->assertInstanceOf(Session::class, $content); + $this->assertEquals('test_value', $content->get('test_key')); } - public function test_set_previous_url(): void + #[Test] + public function save_updates_last_active_timestamp(): void { - $session = $this->container->get(Session::class); - $session->setPreviousUrl('http://localhost/previous'); + $clock = $this->clock('2025-01-01 00:00:00'); + $original = $this->session->lastActiveAt; - $this->assertEquals('http://localhost/previous', $session->getPreviousUrl()); + // session created with current timestamp + $this->assertTrue($this->session->lastActiveAt->equals($clock->now())); + + // save it 5 minutes later + $clock->plus(Duration::minutes(5)); + $this->manager->save($this->session); + + // last active at has updated + $this->assertTrue($this->session->lastActiveAt->after($original)); } - public function test_is_valid(): void + #[Test] + public function delete_removes_session_file(): void { - $clock = $this->clock('2023-01-01 00:00:00'); + $this->eventBus->preventEventHandling(); - $this->container->config(new FileSessionConfig( - path: 'test_sessions', - expiration: Duration::second(), - )); + $this->manager->save($this->session); - $sessionManager = $this->container->get(SessionManager::class); + $path = Path\normalize($this->path, 'sessions', (string) $this->session->id); - $this->assertFalse($sessionManager->isValid(new SessionId('unknown'))); - - $session = $sessionManager->create(new SessionId('new')); + $this->assertFileExists($path); - $this->assertTrue($session->isValid()); + $this->manager->delete($this->session); - $clock->plus(1); + $this->assertFileDoesNotExist($path); - $this->assertFalse($session->isValid()); + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event): void { + $this->assertEquals($this->session->id, $event->id); + }, + count: 1, + ); } - public function test_session_reflash(): void + #[Test] + public function is_valid_checks_expiration(): void { - $session = $this->container->get(Session::class); + $clock = $this->clock('2025-01-01 00:00:00'); - $session->flash('test', 'value'); - $session->flash('test2', ['key' => 'value']); + $this->container->config(new FileSessionConfig( + path: 'test_sessions', + expiration: Duration::seconds(10), + )); + + $session = $this->manager->getOrCreate(new SessionId('expiration_test')); - $this->assertEquals('value', $session->get('test')); + $this->manager->save($session); - $session->reflash(); - $session->cleanup(); + $this->assertTrue($this->manager->isValid($session)); - $this->assertEquals('value', $session->get('test')); - $this->assertEquals(['key' => 'value'], $session->get('test2')); + $clock->plus(Duration::seconds(5)); + $this->assertTrue($this->manager->isValid($session)); + + $clock->plus(Duration::seconds(6)); + $this->assertFalse($this->manager->isValid($session)); } - public function test_session_expires_based_on_last_activity(): void + #[Test] + public function delete_expired_sessions_removes_old_files(): void { + $this->eventBus->preventEventHandling(); + $clock = $this->clock('2023-01-01 00:00:00'); $this->container->config(new FileSessionConfig( @@ -144,25 +190,35 @@ public function test_session_expires_based_on_last_activity(): void expiration: Duration::minutes(30), )); - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('last_activity_test'); + $active = $this->manager->getOrCreate(new SessionId('active')); + $this->manager->save($active); - // Create session - $session = $manager->create($sessionId); - $this->assertTrue($session->isValid()); + $expired = $this->manager->getOrCreate(new SessionId('expired')); + $this->manager->save($expired); - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); + // we expire the $expired one + $clock->plus(Duration::minutes(35)); - // Perform activity - $session->set('activity', 'user_action'); - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); - $this->assertTrue($manager->isValid($sessionId)); + // keep active session fresh + $this->manager->save($active); - // Move forward another 10 minutes, now 35 minutes from last activity - $clock->plus(Duration::minutes(10)); - $this->assertFalse($session->isValid()); - $this->assertFalse($manager->isValid($sessionId)); + $activePath = Path\normalize($this->path, 'test_sessions', (string) $active->id); + $expiredPath = Path\normalize($this->path, 'test_sessions', (string) $expired->id); + + $this->assertFileExists($activePath); + $this->assertFileExists($expiredPath); + + $this->manager->deleteExpiredSessions(); + + $this->assertFileExists($activePath); + $this->assertFileDoesNotExist($expiredPath); + + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event) use ($expired): void { + $this->assertEquals($expired->id, $event->id); + }, + count: 1, + ); } } diff --git a/tests/Integration/Http/Fixtures/ExceptionThatConvertsToRedirectResponse.php b/tests/Integration/Http/Fixtures/ExceptionThatConvertsToRedirectResponse.php index 4f6e26450f..985adf7dba 100644 --- a/tests/Integration/Http/Fixtures/ExceptionThatConvertsToRedirectResponse.php +++ b/tests/Integration/Http/Fixtures/ExceptionThatConvertsToRedirectResponse.php @@ -12,7 +12,7 @@ */ final class ExceptionThatConvertsToRedirectResponse extends Exception implements ConvertsToResponse { - public function toResponse(): Response + public function convertToResponse(): Response { return new Redirect('https://tempestphp.com'); } diff --git a/tests/Integration/Http/Fixtures/ExceptionWithContext.php b/tests/Integration/Http/Fixtures/ExceptionWithContext.php index 3af7dfaaf2..b1dcfd6c9f 100644 --- a/tests/Integration/Http/Fixtures/ExceptionWithContext.php +++ b/tests/Integration/Http/Fixtures/ExceptionWithContext.php @@ -3,9 +3,9 @@ namespace Tests\Tempest\Integration\Http\Fixtures; use Exception; -use Tempest\Core\HasContext; +use Tempest\Core\ProvidesContext; -final class ExceptionWithContext extends Exception implements HasContext +final class ExceptionWithContext extends Exception implements ProvidesContext { public function context(): array { diff --git a/tests/Integration/Http/Fixtures/NullExceptionProcessor.php b/tests/Integration/Http/Fixtures/NullExceptionReporter.php similarity index 52% rename from tests/Integration/Http/Fixtures/NullExceptionProcessor.php rename to tests/Integration/Http/Fixtures/NullExceptionReporter.php index 945eed25e0..353bdd2c9f 100644 --- a/tests/Integration/Http/Fixtures/NullExceptionProcessor.php +++ b/tests/Integration/Http/Fixtures/NullExceptionReporter.php @@ -2,16 +2,18 @@ namespace Tests\Tempest\Integration\Http\Fixtures; -use Tempest\Core\ExceptionProcessor; +use Tempest\Container\Singleton; +use Tempest\Core\Exceptions\ExceptionReporter; use Tempest\Discovery\SkipDiscovery; use Throwable; #[SkipDiscovery] -final class NullExceptionProcessor implements ExceptionProcessor +#[Singleton] +final class NullExceptionReporter implements ExceptionReporter { public static array $exceptions = []; - public function process(Throwable $throwable): void + public function report(Throwable $throwable): void { static::$exceptions[] = $throwable; } diff --git a/tests/Integration/Http/FormSessionTest.php b/tests/Integration/Http/FormSessionTest.php new file mode 100644 index 0000000000..788fc00691 --- /dev/null +++ b/tests/Integration/Http/FormSessionTest.php @@ -0,0 +1,192 @@ + $this->container->get(FormSession::class); + } + + private Session $session { + get => $this->container->get(Session::class); + } + + #[Test] + public function flash_errors_stores_errors(): void + { + $errors = [ + 'name' => [new FailingRule(new HasLength(min: 3))], + 'email' => [new FailingRule(new HasLength(min: 5))], + ]; + + $this->formSession->setErrors($errors); + + $this->assertEquals($errors, $this->formSession->getErrors()); + } + + #[Test] + public function errors_for_returns_field_specific_errors(): void + { + $nameError = new FailingRule(new HasLength(min: 3)); + $emailError = new FailingRule(new HasLength(min: 5)); + + $this->formSession->setErrors([ + 'name' => [$nameError], + 'email' => [$emailError], + ]); + + $this->assertEquals([$nameError], $this->formSession->getErrorsFor('name')); + $this->assertEquals([$emailError], $this->formSession->getErrorsFor('email')); + } + + #[Test] + public function errors_for_returns_empty_array_when_field_has_no_errors(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + + $this->assertEquals([], $this->formSession->getErrorsFor('email')); + } + + #[Test] + public function has_errors_returns_true_when_errors_exist(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + + $this->assertTrue($this->formSession->hasErrors()); + } + + #[Test] + public function has_errors_returns_false_when_no_errors(): void + { + $this->assertFalse($this->formSession->hasErrors()); + } + + #[Test] + public function has_error_returns_true_when_field_has_errors(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + + $this->assertTrue($this->formSession->hasError('name')); + } + + #[Test] + public function has_error_returns_false_when_field_has_no_errors(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + + $this->assertFalse($this->formSession->hasError('email')); + } + + #[Test] + public function values_returns_empty_array_when_no_values(): void + { + $this->assertEquals([], $this->formSession->values()); + } + + #[Test] + public function flash_values_stores_values(): void + { + $values = [ + 'name' => 'John Doe', + 'email' => 'john@example.com', + ]; + + $this->formSession->setOriginalValues($values); + + $this->assertEquals($values, $this->formSession->values()); + } + + #[Test] + public function value_returns_field_specific_value(): void + { + $this->formSession->setOriginalValues([ + 'name' => 'John Doe', + 'email' => 'john@example.com', + ]); + + $this->assertEquals('John Doe', $this->formSession->getOriginalValueFor('name')); + $this->assertEquals('john@example.com', $this->formSession->getOriginalValueFor('email')); + } + + #[Test] + public function value_returns_default_when_field_not_found(): void + { + $this->formSession->setOriginalValues([ + 'name' => 'John Doe', + ]); + + $this->assertEquals('', $this->formSession->getOriginalValueFor('email')); + $this->assertEquals('default', $this->formSession->getOriginalValueFor('email', 'default')); + } + + #[Test] + public function clear_removes_errors_and_values(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + $this->formSession->setOriginalValues([ + 'name' => 'John', + ]); + + $this->formSession->clear(); + + $this->assertEquals([], $this->formSession->getErrors()); + $this->assertEquals([], $this->formSession->values()); + } + + #[Test] + public function errors_are_flashed_and_cleared_after_next_request(): void + { + $this->formSession->setErrors([ + 'name' => [new FailingRule(new HasLength(min: 3))], + ]); + + // First access - errors exist + $this->assertTrue($this->formSession->hasErrors()); + + // Simulate cleanup after request + $this->session->cleanup(); + + // Second access - errors cleared + $this->assertFalse($this->formSession->hasErrors()); + } + + #[Test] + public function values_are_flashed_and_cleared_after_next_request(): void + { + $this->formSession->setOriginalValues([ + 'name' => 'John Doe', + ]); + + // First access - values exist + $this->assertEquals('John Doe', $this->formSession->getOriginalValueFor('name')); + + // Simulate cleanup after request + $this->session->cleanup(); + + // Second access - values cleared + $this->assertEquals('', $this->formSession->getOriginalValueFor('name')); + } +} diff --git a/tests/Integration/Http/GenericResponseSenderTest.php b/tests/Integration/Http/GenericResponseSenderTest.php index 9b381abfb2..7102f293ef 100644 --- a/tests/Integration/Http/GenericResponseSenderTest.php +++ b/tests/Integration/Http/GenericResponseSenderTest.php @@ -22,7 +22,7 @@ use Tempest\View\ViewRenderer; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\view; +use function Tempest\View\view; /** * @internal diff --git a/tests/Integration/Http/SessionFromHeaderTest.php b/tests/Integration/Http/HeaderSessionIdResolverTest.php similarity index 77% rename from tests/Integration/Http/SessionFromHeaderTest.php rename to tests/Integration/Http/HeaderSessionIdResolverTest.php index adb9d3b5b7..41ffec6d44 100644 --- a/tests/Integration/Http/SessionFromHeaderTest.php +++ b/tests/Integration/Http/HeaderSessionIdResolverTest.php @@ -9,19 +9,26 @@ use Tempest\Http\Method; use Tempest\Http\Request; use Tempest\Http\Session\Config\FileSessionConfig; +use Tempest\Http\Session\Resolvers\HeaderSessionIdResolver; use Tempest\Http\Session\Session; +use Tempest\Http\Session\SessionIdResolver; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; /** * @internal */ -final class SessionFromHeaderTest extends FrameworkIntegrationTestCase +final class HeaderSessionIdResolverTest extends FrameworkIntegrationTestCase { public function test_resolving_session_from_header(): void { + $this->container->singleton( + SessionIdResolver::class, + $this->container->get(HeaderSessionIdResolver::class), + ); + $this->container->config(new FileSessionConfig( - path: 'test_sessions', expiration: Duration::hours(2), + path: 'test_sessions', )); $this->setSessionId('session_a'); diff --git a/tests/Integration/Http/PreviousUrlTest.php b/tests/Integration/Http/PreviousUrlTest.php new file mode 100644 index 0000000000..e15651f48e --- /dev/null +++ b/tests/Integration/Http/PreviousUrlTest.php @@ -0,0 +1,138 @@ + $this->container->get(PreviousUrl::class); + } + + private Session $session { + get => $this->container->get(Session::class); + } + + #[Test] + public function tracks_get_requests(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/dashboard', + )); + + $this->assertEquals('/dashboard', $this->previousUrl->get()); + } + + #[Test] + public function does_not_track_post_requests(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::POST, + uri: '/submit-form', + )); + + $this->assertEquals('/', $this->previousUrl->get()); + } + + #[Test] + public function does_not_track_ajax_requests(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/api/data', + headers: ['X-Requested-With' => 'XMLHttpRequest'], + )); + + $this->assertEquals('/', $this->previousUrl->get()); + } + + #[Test] + public function does_not_track_prefetch_requests(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/prefetch-page', + headers: ['Purpose' => 'prefetch'], + )); + + $this->assertEquals('/', $this->previousUrl->get()); + } + + #[Test] + public function get_returns_default_when_no_previous_url(): void + { + $this->assertEquals('/', $this->previousUrl->get()); + $this->assertEquals('/home', $this->previousUrl->get('/home')); + } + + #[Test] + public function updates_previous_url_on_subsequent_tracks(): void + { + $this->previousUrl->track(new GenericRequest(method: Method::GET, uri: '/page1')); + $this->assertEquals('/page1', $this->previousUrl->get()); + + $this->previousUrl->track(new GenericRequest(method: Method::GET, uri: '/page2')); + $this->assertEquals('/page2', $this->previousUrl->get()); + + $this->previousUrl->track(new GenericRequest(method: Method::GET, uri: '/page3')); + $this->assertEquals('/page3', $this->previousUrl->get()); + } + + #[Test] + public function set_intended_stores_url(): void + { + $this->previousUrl->setIntended('/protected-page'); + + $this->assertEquals('/protected-page', $this->session->get('#intended_url')); + } + + #[Test] + public function get_intended_returns_and_removes_url(): void + { + $this->previousUrl->setIntended('/admin/dashboard'); + + $this->assertEquals('/admin/dashboard', $this->previousUrl->getIntended()); + $this->assertEquals('/', $this->previousUrl->getIntended()); + } + + #[Test] + public function get_intended_returns_default_when_not_set(): void + { + $this->assertEquals('/', $this->previousUrl->getIntended()); + $this->assertEquals('/fallback', $this->previousUrl->getIntended('/fallback')); + } + + #[Test] + public function tracks_urls_with_query_strings(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/search?q=tempest&filter=docs', + )); + + $this->assertEquals('/search?q=tempest&filter=docs', $this->previousUrl->get()); + } + + #[Test] + public function tracks_urls_with_fragments(): void + { + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/docs#installation', + )); + + $this->assertEquals('/docs#installation', $this->previousUrl->get()); + } +} diff --git a/tests/Integration/Http/RedisSessionTest.php b/tests/Integration/Http/RedisSessionTest.php index a382927360..ac19c832e0 100644 --- a/tests/Integration/Http/RedisSessionTest.php +++ b/tests/Integration/Http/RedisSessionTest.php @@ -4,19 +4,21 @@ namespace Tests\Tempest\Integration\Http; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; use PHPUnit\Framework\Attributes\Test; use Tempest\Clock\Clock; use Tempest\DateTime\DateTimeInterface; use Tempest\DateTime\Duration; -use Tempest\EventBus\EventBus; use Tempest\Http\Session\Config\RedisSessionConfig; use Tempest\Http\Session\Managers\RedisSessionManager; use Tempest\Http\Session\Session; -use Tempest\Http\Session\SessionConfig; -use Tempest\Http\Session\SessionDestroyed; +use Tempest\Http\Session\SessionCreated; +use Tempest\Http\Session\SessionDeleted; use Tempest\Http\Session\SessionId; use Tempest\Http\Session\SessionManager; use Tempest\KeyValue\Redis\Redis; +use Tempest\Support\Random; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Throwable; @@ -25,246 +27,256 @@ */ final class RedisSessionTest extends FrameworkIntegrationTestCase { - protected function setUp(): void - { - parent::setUp(); - - $this->container->config(new RedisSessionConfig(expiration: Duration::hours(2), prefix: 'test_session:')); - $this->container->singleton( - SessionManager::class, - fn () => new RedisSessionManager( - $this->container->get(Clock::class), - $this->container->get(Redis::class), - $this->container->get(SessionConfig::class), - ), - ); + private SessionManager $manager { + get => $this->container->get(SessionManager::class); } - protected function tearDown(): void - { - $this->container->get(Redis::class)->flush(); + private Session $session { + get => $this->container->get(Session::class); } - #[Test] - public function create_session_from_container(): void + #[PreCondition] + protected function configure(): void { - $session = $this->container->get(Session::class); + $this->container->config(new RedisSessionConfig( + expiration: Duration::hours(2), + prefix: 'test_session:', + )); - $this->assertInstanceOf(Session::class, $session); + $this->container->singleton(SessionManager::class, fn () => new RedisSessionManager( + clock: $this->container->get(Clock::class), + redis: $this->container->get(Redis::class), + config: $this->container->get(RedisSessionConfig::class), + )); + + try { + $this->container->get(Redis::class)->connect(); + } catch (Throwable) { + $this->markTestSkipped('Could not connect to Redis.'); + } } - #[Test] - public function put_get(): void + #[PostCondition] + protected function cleanup(): void { - $session = $this->container->get(Session::class); - - $session->set('test', 'value'); - - $value = $session->get('test'); - $this->assertEquals('value', $value); + try { + $this->container->get(Redis::class)->flush(); + } catch (Throwable) { // @mago-expect lint:no-empty-catch-clause + } } #[Test] - public function remove(): void + public function get_or_create_creates_new_session(): void { - $session = $this->container->get(Session::class); + $this->eventBus->preventEventHandling(); - $session->set('test', 'value'); - $session->remove('test'); + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $this->manager->save($session); - $value = $session->get('test'); - $this->assertNull($value); + $this->assertInstanceOf(Session::class, $session); + $this->assertEquals($sessionId, $session->id); + + $this->eventBus->assertDispatched( + event: SessionCreated::class, + callback: function (SessionCreated $event) use ($sessionId): void { + $this->assertEquals($sessionId, $event->session->id); + }, + count: 1, + ); } #[Test] - public function destroy(): void + public function get_or_create_loads_existing_session(): void { - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('test_session_destroy'); - - $session = $manager->create($sessionId); - $session->set('magic_type', 'offensive'); + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $session->set('key', 'value'); - $this->assertTrue($manager->isValid($sessionId)); + $this->manager->save($session); - $events = []; - $eventBus = $this->container->get(EventBus::class); - $eventBus->listen(function (SessionDestroyed $event) use (&$events): void { - $events[] = $event; - }); + $loaded = $this->manager->getOrCreate($sessionId); - $session->destroy(); - - $this->assertFalse($manager->isValid($sessionId)); - $this->assertCount(1, $events); - $this->assertEquals((string) $sessionId, (string) $events[0]->id); + $this->assertEquals($sessionId, $loaded->id); + $this->assertTrue($session->createdAt->isSameMinute($loaded->createdAt)); + $this->assertEquals('value', $loaded->get('key')); } #[Test] - public function set_previous_url(): void + public function save_persists_session_to_redis(): void { - $session = $this->container->get(Session::class); - $session->setPreviousUrl('http://localhost/previous'); + $this->session->set('frieren', 'elf_mage'); + $this->manager->save($this->session); - $this->assertEquals('http://localhost/previous', $session->getPreviousUrl()); + $this->assertSessionExistsInRedis($this->session->id); + $this->assertSessionDataInRedis($this->session->id, ['frieren' => 'elf_mage']); } #[Test] - public function is_valid(): void + public function save_persists_nested_data(): void { - $clock = $this->clock('2023-01-01 00:00:00'); - - $this->container->config(new RedisSessionConfig( - expiration: Duration::second(), - prefix: 'test_session:', - )); - - $sessionManager = $this->container->get(SessionManager::class); + $data = [ + 'members' => ['Frieren', 'Fern', 'Stark'], + 'location' => 'Northern Plateau', + 'quest' => [ + 'name' => 'Journey to Ende', + 'progress' => 0.75, + ], + ]; + + $this->session->set('party', $data); + $this->manager->save($this->session); + + $this->assertSessionDataInRedis($this->session->id, ['party' => $data]); + } - $this->assertFalse($sessionManager->isValid(new SessionId('unknown'))); + #[Test] + public function save_updates_last_active_timestamp(): void + { + $clock = $this->clock('2025-01-01 00:00:00'); - $session = $sessionManager->create(new SessionId('new')); + $this->manager->save($this->session); + $originalTimestamp = $this->getSessionLastActiveTimestamp($this->session->id); - $this->assertTrue($session->isValid()); + $clock->plus(Duration::minutes(5)); - $clock->plus(1); + $this->session->set('action', 'spell_cast'); + $this->manager->save($this->session); + $updatedTimestamp = $this->getSessionLastActiveTimestamp($this->session->id); - $this->assertFalse($session->isValid()); + $this->assertTrue($updatedTimestamp->after($originalTimestamp)); } #[Test] - public function session_reflash(): void + public function delete_removes_session_from_redis(): void { - $session = $this->container->get(Session::class); + $this->eventBus->preventEventHandling(); + + $sessionId = $this->createSessionId(); + $session = $this->manager->getOrCreate($sessionId); + $session->set('magic_type', 'offensive'); + + $this->manager->save($session); - $session->flash('test', 'value'); - $session->flash('test2', ['key' => 'value']); + $this->assertSessionExistsInRedis($sessionId); - $this->assertEquals('value', $session->get('test')); + $this->manager->delete($session); - $session->reflash(); - $session->cleanup(); + $this->assertSessionNotExistsInRedis($sessionId); - $this->assertEquals('value', $session->get('test')); - $this->assertEquals(['key' => 'value'], $session->get('test2')); + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event) use ($sessionId): void { + $this->assertEquals($sessionId, $event->id); + }, + count: 1, + ); } #[Test] - public function session_expires_based_on_last_activity(): void + public function is_valid_checks_expiration(): void { $clock = $this->clock('2023-01-01 00:00:00'); $this->container->config(new RedisSessionConfig( - expiration: Duration::minutes(30), + expiration: Duration::seconds(10), prefix: 'test_session:', )); - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('last_activity_test'); + $session = $this->manager->getOrCreate(new SessionId('expiration_test')); + $this->manager->save($session); - // Create session - $session = $manager->create($sessionId); - $this->assertTrue($session->isValid()); + $this->assertTrue($this->manager->isValid($session)); - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); + $clock->plus(Duration::seconds(5)); + $this->assertTrue($this->manager->isValid($session)); - // Perform activity - $session->set('activity', 'user_action'); - $clock->plus(Duration::minutes(25)); - $this->assertTrue($session->isValid()); - $this->assertTrue($manager->isValid($sessionId)); - - // Move forward another 10 minutes, now 35 minutes from last activity - $clock->plus(Duration::minutes(10)); - $this->assertFalse($session->isValid()); - $this->assertFalse($manager->isValid($sessionId)); + $clock->plus(Duration::seconds(6)); + $this->assertFalse($this->manager->isValid($session)); } #[Test] - public function cleanup_removes_expired_sessions(): void + public function delete_expired_sessions_removes_old_records(): void { - $clock = $this->clock('2023-01-01 00:00:00'); + $this->eventBus->preventEventHandling(); - $this->container->config(new RedisSessionConfig(expiration: Duration::minutes(30), prefix: 'test_session:')); - - $manager = $this->container->get(SessionManager::class); - - $activeSessionId = new SessionId('active_session'); - $activeSession = $manager->create($activeSessionId); - $activeSession->set('status', 'active'); + $clock = $this->clock('2023-01-01 00:00:00'); - $clock->minus(Duration::hour()); - $expiredSessionId = new SessionId('expired_session'); - $expiredSession = $manager->create($expiredSessionId); - $expiredSession->set('status', 'expired'); + $this->container->config(new RedisSessionConfig( + expiration: Duration::minutes(30), + prefix: 'test_session:', + )); - $clock->plus(Duration::hour()); + $activeSessionId = $this->createSessionId(); + $active = $this->manager->getOrCreate($activeSessionId); + $active->set('status', 'active'); - $this->assertSessionExistsInDatabase($activeSessionId); - $this->assertSessionExistsInDatabase($expiredSessionId); + $this->manager->save($active); - $manager->cleanup(); + $expiredSessionId = $this->createSessionId(); + $expired = $this->manager->getOrCreate($expiredSessionId); + $expired->set('status', 'expired'); - $this->assertSessionExistsInDatabase($activeSessionId); - $this->assertSessionNotExistsInDatabase($expiredSessionId); - } + $this->manager->save($expired); - #[Test] - public function session_updates_last_active_timestamp(): void - { - $clock = $this->clock('2023-01-01 12:00:00'); + // expire the $expired one + $clock->plus(Duration::minutes(35)); - $manager = $this->container->get(SessionManager::class); - $sessionId = new SessionId('timestamp_test'); + // keep the first one active + $this->manager->save($active); - $session = $manager->create($sessionId); - $originalTimestamp = $this->getSessionLastActiveTimestamp($sessionId); + $this->assertSessionExistsInRedis($activeSessionId); + $this->assertSessionExistsInRedis($expiredSessionId); - $clock->plus(Duration::minutes(5)); + $this->manager->deleteExpiredSessions(); - $session->set('action', 'spell_cast'); - $updatedTimestamp = $this->getSessionLastActiveTimestamp($sessionId); + $this->assertSessionExistsInRedis($activeSessionId); + $this->assertSessionNotExistsInRedis($expiredSessionId); - $this->assertTrue($updatedTimestamp->after($originalTimestamp)); + $this->eventBus->assertDispatched( + event: SessionDeleted::class, + callback: function (SessionDeleted $event) use ($expiredSessionId): void { + $this->assertEquals($expiredSessionId, $event->id); + }, + count: 1, + ); } - #[Test] - public function session_persists_csrf_token(): void + private function assertSessionExistsInRedis(SessionId $sessionId): void { - $session = $this->container->get(Session::class); - $token = $session->token; + $session = $this->getSessionFromRedis($sessionId); - $data = $this->getSessionDataFromDatabase($session->id); - - $this->assertEquals($token, $data[Session::CSRF_TOKEN_KEY]); - $this->assertEquals($token, $session->token); + $this->assertNotNull($session, "Session {$sessionId} should exist in Redis"); } - private function assertSessionExistsInDatabase(SessionId $sessionId): void + private function assertSessionNotExistsInRedis(SessionId $sessionId): void { - $session = $this->getSessionFromDatabase($sessionId); + $session = $this->getSessionFromRedis($sessionId); - $this->assertNotNull($session, "Session {$sessionId} should exist in database"); + $this->assertNull($session, "Session {$sessionId} should not exist in Redis"); } - private function assertSessionNotExistsInDatabase(SessionId $sessionId): void + private function assertSessionDataInRedis(SessionId $sessionId, array $data): void { - $session = $this->getSessionFromDatabase($sessionId); + $session = $this->getSessionFromRedis($sessionId); + + $this->assertNotNull($session, "Session {$sessionId} should exist in Redis"); - $this->assertNull($session, "Session {$sessionId} should not exist in database"); + foreach ($data as $key => $value) { + $this->assertEquals($value, $session->data[$key], "Session data key '{$key}' should match expected value"); + } } private function getSessionLastActiveTimestamp(SessionId $sessionId): DateTimeInterface { - $session = $this->getSessionFromDatabase($sessionId); + $session = $this->getSessionFromRedis($sessionId); - $this->assertNotNull($session, "Session {$sessionId} should exist in database"); + $this->assertNotNull($session, "Session {$sessionId} should exist in Redis"); return $session->lastActiveAt; } - private function getSessionFromDatabase(SessionId $id): ?Session + private function getSessionFromRedis(SessionId $id): ?Session { $redis = $this->container->get(Redis::class); @@ -276,11 +288,8 @@ private function getSessionFromDatabase(SessionId $id): ?Session } } - /** - * @return array - */ - private function getSessionDataFromDatabase(SessionId $id): array + private function createSessionId(): SessionId { - return $this->getSessionFromDatabase($id)->data ?? []; + return new SessionId(Random\uuid()); } } diff --git a/tests/Integration/Http/Responses/BackResponseTest.php b/tests/Integration/Http/Responses/BackResponseTest.php index 61acadb517..26e212facd 100644 --- a/tests/Integration/Http/Responses/BackResponseTest.php +++ b/tests/Integration/Http/Responses/BackResponseTest.php @@ -4,11 +4,13 @@ namespace Tests\Tempest\Integration\Http\Responses; +use PHPUnit\Framework\Attributes\Test; use Tempest\Http\GenericRequest; use Tempest\Http\Header; use Tempest\Http\Method; use Tempest\Http\Request; use Tempest\Http\Responses\Back; +use Tempest\Http\Session\PreviousUrl; use Tempest\Http\Status; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -17,50 +19,95 @@ */ final class BackResponseTest extends FrameworkIntegrationTestCase { - public function test_back_response(): void + private PreviousUrl $previousUrl { + get => $this->container->get(PreviousUrl::class); + } + + #[Test] + public function back_response_with_no_previous_url(): void { - $this->bindRequest(); $response = new Back(); $this->assertSame(Status::FOUND, $response->status); $this->assertEquals(new Header('Location', ['/']), $response->headers['Location']); - $this->assertNotSame(Status::OK, $response->status); } - public function test_back_response_with_referer(): void + #[Test] + public function back_response_with_tracked_url(): void { - $this->bindRequest(referer: $referer = '/referer-test'); - - $response = new Back(); + $this->previousUrl->track( + request: new GenericRequest(method: Method::GET, uri: '/previous-page'), + ); - $this->assertEquals(new Header('Location', [$referer]), $response->headers['Location']); + $this->assertEquals( + new Header('Location', ['/previous-page']), + new Back()->headers['Location'], + ); } - public function test_back_response_with_fallback(): void + #[Test] + public function back_response_with_fallback(): void { - $this->bindRequest(); + $this->assertEquals( + new Header('Location', ['/fallback-url']), + new Back('/fallback-url')->headers['Location'], + ); + } - $referer = '/test'; - $response = new Back($referer); + #[Test] + public function back_response_prefers_tracked_url_over_fallback(): void + { + $this->previousUrl->track( + request: new GenericRequest(method: Method::GET, uri: '/tracked-page'), + ); - $this->assertEquals(new Header('Location', [$referer]), $response->headers['Location']); + $this->assertEquals( + new Header('Location', ['/tracked-page']), + new Back('/fallback-url')->headers['Location'], + ); } - public function test_back_response_for_get_request(): void + #[Test] + public function back_response_with_referer_header(): void { - $this->http - ->get('/test-redirect-back-url') - ->assertRedirect('/test-redirect-back-url'); + $this->container->singleton(Request::class, new GenericRequest( + method: Method::GET, + uri: '/current-page', + headers: ['referer' => '/referer-page'], + )); + + $this->assertEquals( + new Header('Location', ['/referer-page']), + new Back()->headers['Location'], + ); } - public function bindRequest(?string $uri = '/', ?string $referer = null): void + #[Test] + public function back_response_prefers_tracked_url_over_referer(): void { - $headers = $referer ? ['referer' => $referer] : []; + $this->previousUrl->track(new GenericRequest( + method: Method::GET, + uri: '/tracked-page', + headers: ['referer' => '/referer-page'], + )); $this->container->singleton(Request::class, new GenericRequest( method: Method::GET, - uri: $uri, - headers: $headers, + uri: '/current-page', + headers: ['referer' => '/referer-page'], )); + + $this->assertEquals( + new Header('Location', ['/tracked-page']), + new Back()->headers['Location'], + ); + } + + #[Test] + public function back_response_for_get_request(): void + { + $this->http + ->get('/test-redirect-back-url') + ->assertRedirect('/test-redirect-back-url'); } } diff --git a/tests/Integration/Http/Responses/GenericResponseTest.php b/tests/Integration/Http/Responses/GenericResponseTest.php index e5bce79a1a..040bbe82bb 100644 --- a/tests/Integration/Http/Responses/GenericResponseTest.php +++ b/tests/Integration/Http/Responses/GenericResponseTest.php @@ -4,6 +4,7 @@ namespace Tests\Tempest\Integration\Http\Responses; +use PHPUnit\Framework\Attributes\Test; use Tempest\Http\Cookie\Cookie; use Tempest\Http\Cookie\CookieManager; use Tempest\Http\Responses\Ok; @@ -16,26 +17,18 @@ */ final class GenericResponseTest extends FrameworkIntegrationTestCase { - public function test_sessions(): void + #[Test] + public function sessions(): void { - $response = new Ok() - ->addSession('test', 'test') - ->addSession('original', 'original'); + new Ok()->flash('success', 'Operation successful'); $session = $this->container->get(Session::class); - $this->assertSame('test', $session->get('test')); - - $response->removeSession('test'); - - $this->assertNull($session->get('test')); - - $response->destroySession(); - - $this->assertNull($session->get('original')); + $this->assertSame('Operation successful', $session->get('success')); } - public function test_cookies(): void + #[Test] + public function cookies(): void { $response = new Ok()->addCookie(new Cookie('test')); @@ -48,7 +41,8 @@ public function test_cookies(): void $this->assertSame(-1, $cookieManager->get('test')->expiresAt); } - public function test_set_status(): void + #[Test] + public function set_status(): void { $response = new Ok()->setStatus(Status::ACCEPTED); diff --git a/tests/Integration/Http/Responses/InvalidTest.php b/tests/Integration/Http/Responses/InvalidTest.php deleted file mode 100644 index 8608fa928d..0000000000 --- a/tests/Integration/Http/Responses/InvalidTest.php +++ /dev/null @@ -1,68 +0,0 @@ - 'bar'], ['referer' => '/original']); - - $response = new Invalid( - $request, - [ - 'foo' => [ - new IsNotEmptyString(), - ], - ], - ); - - $this->assertSame(Status::FOUND, $response->status); - $this->assertSame('/original', $response->getHeader('Location')->values[0]); - - $session = $this->container->get(Session::class); - - $this->assertArrayHasKey('foo', $session->get(Session::VALIDATION_ERRORS)); - $this->assertArrayHasKey('foo', $session->get(Session::ORIGINAL_VALUES)); - } - - public function test_invalid_with_request(): void - { - $request = new GenericRequest( - method: Method::GET, - uri: '/original', - body: ['foo' => 'bar'], - headers: ['referer' => '/original'], - ); - - $response = new Invalid( - $request, - [ - 'foo' => [ - new IsNotEmptyString(), - ], - ], - ); - - $this->assertSame(Status::FOUND, $response->status); - $this->assertSame('/original', $response->getHeader('Location')->values[0]); - - $session = $this->container->get(Session::class); - - $this->assertArrayHasKey('foo', $session->get(Session::VALIDATION_ERRORS)); - $this->assertArrayHasKey('foo', $session->get(Session::ORIGINAL_VALUES)); - } -} diff --git a/tests/Integration/Http/SessionTest.php b/tests/Integration/Http/SessionTest.php new file mode 100644 index 0000000000..3ee186f965 --- /dev/null +++ b/tests/Integration/Http/SessionTest.php @@ -0,0 +1,123 @@ + $this->container->get(Session::class); + } + + #[Test] + public function create_session_from_container(): void + { + $clock = $this->clock(); + + $this->assertInstanceOf(Session::class, $this->session); + $this->assertTrue($this->session->createdAt->equals($clock->now())); + $this->assertTrue($this->session->lastActiveAt->equals($clock->now())); + } + + #[Test] + public function set_and_get(): void + { + $this->session->set('test', 'value'); + $this->assertEquals('value', $this->session->get('test')); + + $this->session->set('nested', ['key' => 'value']); + $this->assertEquals(['key' => 'value'], $this->session->get('nested')); + } + + #[Test] + public function get_with_default(): void + { + $this->assertEquals('default', $this->session->get('nonexistent', 'default')); + $this->assertNull($this->session->get('nonexistent')); + } + + #[Test] + public function remove(): void + { + $this->session->set('test', 'value'); + $this->assertEquals('value', $this->session->get('test')); + + $this->session->remove('test'); + $this->assertNull($this->session->get('test')); + } + + #[Test] + public function all(): void + { + $this->session->set('key1', 'value1'); + $this->session->set('key2', 'value2'); + + $data = $this->session->all(); + + $this->assertArrayHasKey('key1', $data); + $this->assertArrayHasKey('key2', $data); + $this->assertEquals('value1', $data['key1']); + $this->assertEquals('value2', $data['key2']); + } + + #[Test] + public function flash(): void + { + $this->session->flash('message', 'success'); + $this->assertEquals('success', $this->session->get('message')); + + $this->session->cleanup(); + $this->assertNull($this->session->get('message')); + } + + #[Test] + public function reflash(): void + { + $this->session->flash('test', 'value'); + $this->session->flash('test2', ['key' => 'value']); + + $this->assertEquals('value', $this->session->get('test')); + + $this->session->reflash(); + $this->session->cleanup(); + + $this->assertEquals('value', $this->session->get('test')); + $this->assertEquals(['key' => 'value'], $this->session->get('test2')); + } + + #[Test] + public function consume(): void + { + $this->session->set('token', 'abc123'); + + $this->assertEquals('abc123', $this->session->consume('token')); + $this->assertNull($this->session->get('token')); + } + + #[Test] + public function consume_with_default(): void + { + $this->assertEquals('default', $this->session->consume('nonexistent', 'default')); + } + + #[Test] + public function clear(): void + { + $this->session->set('key1', 'value1'); + $this->session->set('key2', 'value2'); + + $this->assertCount(2, $this->session->all()); + + $this->session->clear(); + + $this->assertEmpty($this->session->all()); + } +} diff --git a/tests/Integration/Http/Static/Fixtures/StaticPageController.php b/tests/Integration/Http/Static/Fixtures/StaticPageController.php index 7b81efb1af..f8c3b15da2 100644 --- a/tests/Integration/Http/Static/Fixtures/StaticPageController.php +++ b/tests/Integration/Http/Static/Fixtures/StaticPageController.php @@ -14,7 +14,7 @@ use Tempest\Vite\Exceptions\ManifestWasNotFound; use function Tempest\Router\uri; -use function Tempest\view; +use function Tempest\View\view; final readonly class StaticPageController { diff --git a/tests/Integration/Http/Static/StaticCleanCommandTest.php b/tests/Integration/Http/Static/StaticCleanCommandTest.php index bfbf434439..2433e77f85 100644 --- a/tests/Integration/Http/Static/StaticCleanCommandTest.php +++ b/tests/Integration/Http/Static/StaticCleanCommandTest.php @@ -21,8 +21,8 @@ protected function setUp(): void { parent::setUp(); - $this->registerRoute(StaticPageController::class); - $this->registerStaticPage(StaticPageController::class); + $this->http->registerRoute(StaticPageController::class); + $this->http->registerStaticPage(StaticPageController::class); } public function test_generate(): void diff --git a/tests/Integration/Http/Static/StaticGenerateCommandTest.php b/tests/Integration/Http/Static/StaticGenerateCommandTest.php index d108a3efe4..d34fc05e82 100644 --- a/tests/Integration/Http/Static/StaticGenerateCommandTest.php +++ b/tests/Integration/Http/Static/StaticGenerateCommandTest.php @@ -21,8 +21,8 @@ protected function setUp(): void { parent::setUp(); - $this->registerRoute(StaticPageController::class); - $this->registerStaticPage(StaticPageController::class); + $this->http->registerRoute(StaticPageController::class); + $this->http->registerStaticPage(StaticPageController::class); } public function test_static_site_generate_command(): void @@ -52,8 +52,8 @@ public function test_static_site_generate_command(): void public function test_failure_status_code(): void { - $this->registerRoute([StaticPageController::class, 'http500']); - $this->registerStaticPage([StaticPageController::class, 'http500']); + $this->http->registerRoute([StaticPageController::class, 'http500']); + $this->http->registerStaticPage([StaticPageController::class, 'http500']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -65,8 +65,8 @@ public function test_failure_status_code(): void public function test_failure_no_textual_content(): void { - $this->registerRoute([StaticPageController::class, 'noTextualContent']); - $this->registerStaticPage([StaticPageController::class, 'noTextualContent']); + $this->http->registerRoute([StaticPageController::class, 'noTextualContent']); + $this->http->registerStaticPage([StaticPageController::class, 'noTextualContent']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -78,8 +78,8 @@ public function test_failure_no_textual_content(): void public function test_failure_no_build(): void { - $this->registerRoute([StaticPageController::class, 'vite']); - $this->registerStaticPage([StaticPageController::class, 'vite']); + $this->http->registerRoute([StaticPageController::class, 'vite']); + $this->http->registerStaticPage([StaticPageController::class, 'vite']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -91,8 +91,8 @@ public function test_failure_no_build(): void public function test_dead_link(): void { - $this->registerRoute([StaticPageController::class, 'deadLink']); - $this->registerStaticPage([StaticPageController::class, 'deadLink']); + $this->http->registerRoute([StaticPageController::class, 'deadLink']); + $this->http->registerStaticPage([StaticPageController::class, 'deadLink']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -105,9 +105,9 @@ public function test_dead_link(): void public function test_dead_link_with_redirect(): void { - $this->registerRoute([StaticPageController::class, 'redirectingRoute']); - $this->registerRoute([StaticPageController::class, 'hasRedirect']); - $this->registerStaticPage([StaticPageController::class, 'hasRedirect']); + $this->http->registerRoute([StaticPageController::class, 'redirectingRoute']); + $this->http->registerRoute([StaticPageController::class, 'hasRedirect']); + $this->http->registerStaticPage([StaticPageController::class, 'hasRedirect']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -118,8 +118,8 @@ public function test_dead_link_with_redirect(): void public function test_allow_dead_links(): void { - $this->registerRoute([StaticPageController::class, 'deadLink']); - $this->registerStaticPage([StaticPageController::class, 'deadLink']); + $this->http->registerRoute([StaticPageController::class, 'deadLink']); + $this->http->registerStaticPage([StaticPageController::class, 'deadLink']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -130,8 +130,8 @@ public function test_allow_dead_links(): void public function test_external_dead_links(): void { - $this->registerRoute([StaticPageController::class, 'deadLink']); - $this->registerStaticPage([StaticPageController::class, 'deadLink']); + $this->http->registerRoute([StaticPageController::class, 'deadLink']); + $this->http->registerStaticPage([StaticPageController::class, 'deadLink']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); @@ -145,8 +145,8 @@ public function test_external_dead_links(): void public function test_ignore_dead_links(): void { - $this->registerRoute([StaticPageController::class, 'allowedDeadLink']); - $this->registerStaticPage([StaticPageController::class, 'allowedDeadLink']); + $this->http->registerRoute([StaticPageController::class, 'allowedDeadLink']); + $this->http->registerStaticPage([StaticPageController::class, 'allowedDeadLink']); $this->container->config(new AppConfig(baseUri: 'https://test.com')); diff --git a/tests/Integration/Http/TrackPreviousUrlMiddlewareTest.php b/tests/Integration/Http/TrackPreviousUrlMiddlewareTest.php new file mode 100644 index 0000000000..a51c485807 --- /dev/null +++ b/tests/Integration/Http/TrackPreviousUrlMiddlewareTest.php @@ -0,0 +1,111 @@ + $this->container->get(PreviousUrl::class); + } + + private TrackPreviousUrlMiddleware $middleware { + get => new TrackPreviousUrlMiddleware($this->previousUrl); + } + + #[Test] + public function middleware_tracks_request_url(): void + { + $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/dashboard'), + next: new HttpMiddlewareCallable(fn () => new GenericResponse(Status::OK)), + ); + + $this->assertEquals('/dashboard', $this->previousUrl->get()); + } + + #[Test] + public function middleware_calls_next_handler(): void + { + $expected = new GenericResponse(Status::OK, body: 'Test response'); + + $response = $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/test'), + next: new HttpMiddlewareCallable(fn () => $expected), + ); + + $this->assertSame($expected, $response); + } + + #[Test] + public function middleware_does_not_track_post_requests(): void + { + $this->middleware->__invoke( + request: new GenericRequest(method: Method::POST, uri: '/form-submit'), + next: new HttpMiddlewareCallable(fn () => new GenericResponse(Status::OK)), + ); + + $this->assertEquals('/', $this->previousUrl->get()); + } + + #[Test] + public function middleware_tracks_multiple_requests_in_sequence(): void + { + $next = new HttpMiddlewareCallable(fn () => new GenericResponse(Status::OK)); + + $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/page1'), + next: $next, + ); + + $this->assertEquals('/page1', $this->previousUrl->get()); + + $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/page2'), + next: $next, + ); + + $this->assertEquals('/page2', $this->previousUrl->get()); + + $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/page3'), + next: $next, + ); + + $this->assertEquals('/page3', $this->previousUrl->get()); + } + + #[Test] + public function middleware_ignores_ajax_requests(): void + { + $this->middleware->__invoke( + request: new GenericRequest(method: Method::GET, uri: '/dashboard'), + next: new HttpMiddlewareCallable(fn () => new GenericResponse(Status::OK)), + ); + + $this->middleware->__invoke( + request: new GenericRequest( + method: Method::GET, + uri: '/api/data', + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ), + next: new HttpMiddlewareCallable(fn () => new GenericResponse(Status::OK)), + ); + + $this->assertEquals('/dashboard', $this->previousUrl->get()); + } +} diff --git a/tests/Integration/Http/ValidationResponseTest.php b/tests/Integration/Http/ValidationResponseTest.php index 70db37844e..918ed888a2 100644 --- a/tests/Integration/Http/ValidationResponseTest.php +++ b/tests/Integration/Http/ValidationResponseTest.php @@ -5,7 +5,8 @@ namespace Tests\Tempest\Integration\Http; use Tempest\Database\Migrations\CreateMigrationsTable; -use Tempest\Http\Session\Session; +use Tempest\Http\ContentType; +use Tempest\Http\Session\FormSession; use Tests\Tempest\Fixtures\Controllers\ValidationController; use Tests\Tempest\Fixtures\Migrations\CreateAuthorTable; use Tests\Tempest\Fixtures\Migrations\CreateBookTable; @@ -25,6 +26,7 @@ final class ValidationResponseTest extends FrameworkIntegrationTestCase public function test_validation_errors_are_listed_in_the_response_body(): void { $this->http + ->as(ContentType::HTML) ->post( uri: uri([ValidationController::class, 'store']), body: ['number' => 11, 'item.number' => 11], @@ -39,6 +41,7 @@ public function test_original_values(): void $values = ['number' => 11, 'item.number' => 11]; $this->http + ->as(contentType::HTML) ->post( uri: uri([ValidationController::class, 'store']), body: $values, @@ -46,14 +49,12 @@ public function test_original_values(): void ) ->assertRedirect(uri([ValidationController::class, 'store'])) ->assertHasValidationError('number') - ->assertHasSession(Session::ORIGINAL_VALUES, function (Session $_session, array $data) use ($values): void { - $this->assertEquals($values, $data); - }); + ->assertHasFormOriginalValues($values); } public function test_update_book(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -81,7 +82,7 @@ public function test_update_book(): void public function test_failing_post_request(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -98,7 +99,7 @@ public function test_failing_post_request(): void uri([ValidationController::class, 'updateBook'], book: 1), body: ['book' => ['title' => 1]], ) - ->assertHasJsonValidationErrors(['title' => ['Value must be between 1 and 120']]); + ->assertHasJsonValidationErrors(['title' => ['title must be between 1 and 120', 'title must be a string']]); $this->assertSame('Timeline Taxi', Book::find(id: 1)->first()->title); } @@ -106,15 +107,16 @@ public function test_failing_post_request(): void public function test_sensitive_fields_are_excluded_from_original_values(): void { $this->http + ->as(ContentType::HTML) ->post( uri: uri([ValidationController::class, 'storeSensitive']), body: ['not_sensitive_param' => '', 'sensitive_param' => 'secret123'], headers: ['referer' => '/test-sensitive-validation'], ) ->assertHasValidationError('not_sensitive_param') - ->assertHasSession(Session::ORIGINAL_VALUES, function (Session $_session, array $data): void { - $this->assertArrayNotHasKey('sensitive_param', $data); - $this->assertArrayHasKey('not_sensitive_param', $data); + ->assertHasForm(function (FormSession $form): void { + $this->assertNull($form->getOriginalValueFor('sensitive_param')); + $this->assertNotNull($form->getOriginalValueFor('not_sensitive_param')); }); } } diff --git a/tests/Integration/Log/GenericLoggerTest.php b/tests/Integration/Log/GenericLoggerTest.php index 8120b8707c..05d392c276 100644 --- a/tests/Integration/Log/GenericLoggerTest.php +++ b/tests/Integration/Log/GenericLoggerTest.php @@ -6,16 +6,24 @@ use Monolog\Level; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\Test; use Psr\Log\LogLevel as PsrLogLevel; use ReflectionClass; +use Tempest\Core\Environment; +use Tempest\DateTime\Duration; use Tempest\EventBus\EventBus; use Tempest\Log\Channels\AppendLogChannel; -use Tempest\Log\Channels\DailyLogChannel; -use Tempest\Log\Channels\WeeklyLogChannel; +use Tempest\Log\Config\DailyLogConfig; +use Tempest\Log\Config\MultipleChannelsLogConfig; +use Tempest\Log\Config\NullLogConfig; +use Tempest\Log\Config\SimpleLogConfig; +use Tempest\Log\Config\WeeklyLogConfig; use Tempest\Log\GenericLogger; -use Tempest\Log\LogConfig; use Tempest\Log\LogLevel; use Tempest\Log\MessageLogged; +use Tempest\Support\Filesystem; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; /** @@ -23,118 +31,121 @@ */ final class GenericLoggerTest extends FrameworkIntegrationTestCase { - public function test_append_log_channel_works(): void - { - $filePath = __DIR__ . '/logs/tempest.log'; - - $config = new LogConfig( - channels: [ - new AppendLogChannel($filePath), - ], - ); - - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); + private EventBus $bus { + get => $this->container->get(EventBus::class); + } - $logger->info('test'); + private Environment $environment { + get => $this->container->get(Environment::class); + } - $this->assertFileExists($filePath); + #[PreCondition] + protected function configure(): void + { + Filesystem\ensure_directory_empty(__DIR__ . '/logs'); + } - $this->assertStringContainsString('test', file_get_contents($filePath)); + #[PostCondition] + protected function cleanup(): void + { + Filesystem\delete_directory(__DIR__ . '/logs'); } - protected function tearDown(): void + #[Test] + public function simple_log_config(): void { - $files = glob(__DIR__ . '/logs/*.log'); + $filePath = __DIR__ . '/logs/tempest.log'; - foreach ($files as $file) { - if (is_file($file)) { - unlink($file); - } - } + $config = new SimpleLogConfig($filePath, prefix: 'tempest'); + + $logger = new GenericLogger($config, $this->environment, $this->bus); + $logger->info('test'); + + $this->assertFileExists($filePath); + $this->assertStringContainsString('test', Filesystem\read_file($filePath)); } - public function test_daily_log_channel_works(): void + #[Test] + public function daily_log_config(): void { + $clock = $this->clock(); $filePath = __DIR__ . '/logs/tempest-' . date('Y-m-d') . '.log'; + $config = new DailyLogConfig(__DIR__ . '/logs/tempest.log', prefix: 'tempest'); - $config = new LogConfig( - channels: [ - new DailyLogChannel(__DIR__ . '/logs/tempest.log'), - ], - ); - - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); - + $logger = new GenericLogger($config, $this->environment, $this->bus); $logger->info('test'); $this->assertFileExists($filePath); + $this->assertStringContainsString('test', Filesystem\read_file($filePath)); + + $clock->plus(Duration::day()); + $logger = new GenericLogger($config, $this->environment, $this->bus); + $logger->info('test'); - $this->assertStringContainsString('test', file_get_contents($filePath)); + $clock->plus(Duration::days(2)); + $logger = new GenericLogger($config, $this->environment, $this->bus); + $logger->info('test'); } - public function test_weekly_log_channel_works(): void + #[Test] + public function weekly_log_config(): void { $filePath = __DIR__ . '/logs/tempest-' . date('Y-W') . '.log'; + $config = new WeeklyLogConfig(__DIR__ . '/logs/tempest.log', prefix: 'tempest'); - $config = new LogConfig( - channels: [ - new WeeklyLogChannel(__DIR__ . '/logs/tempest.log'), - ], - ); - - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); - + $logger = new GenericLogger($config, $this->environment, $this->bus); $logger->info('test'); $this->assertFileExists($filePath); - - $this->assertStringContainsString('test', file_get_contents($filePath)); + $this->assertStringContainsString('test', Filesystem\read_file($filePath)); } - public function test_multiple_same_log_channels_works(): void + #[Test] + public function multiple_same_log_channels(): void { $filePath = __DIR__ . '/logs/multiple-tempest1.log'; $secondFilePath = __DIR__ . '/logs/multiple-tempest2.log'; - $config = new LogConfig( + $config = new MultipleChannelsLogConfig( channels: [ new AppendLogChannel($filePath), new AppendLogChannel($secondFilePath), ], + prefix: 'tempest', ); - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); + $logger = new GenericLogger($config, $this->environment, $this->bus); $logger->info('test'); $this->assertFileExists($filePath); - $this->assertStringContainsString('test', file_get_contents($filePath)); + $this->assertStringContainsString('test', Filesystem\read_file($filePath)); $this->assertFileExists($secondFilePath); - $this->assertStringContainsString('test', file_get_contents($secondFilePath)); + $this->assertStringContainsString('test', Filesystem\read_file($secondFilePath)); } + #[Test] #[DataProvider('psrLogLevelProvider')] #[DataProvider('monologLevelProvider')] #[DataProvider('tempestLevelProvider')] - public function test_log_levels(mixed $level, string $expected): void + public function log_levels(mixed $level, string $expected): void { $filePath = __DIR__ . '/logs/tempest.log'; - $config = new LogConfig( + $config = new SimpleLogConfig( + path: $filePath, prefix: 'tempest', - channels: [ - new AppendLogChannel($filePath), - ], ); - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); + $logger = new GenericLogger($config, $this->environment, $this->bus); $logger->log($level, 'test'); $this->assertFileExists($filePath); - $this->assertStringContainsString('tempest.' . $expected, file_get_contents($filePath)); + $this->assertStringContainsString('tempest.' . $expected, Filesystem\read_file($filePath)); } + #[Test] #[DataProvider('tempestLevelProvider')] - public function test_message_logged_emitted(LogLevel $level, string $_expected): void + public function message_logged_emitted(LogLevel $level, string $_expected): void { $eventBus = $this->container->get(EventBus::class); @@ -144,28 +155,26 @@ public function test_message_logged_emitted(LogLevel $level, string $_expected): $this->assertSame(['foo' => 'bar'], $event->context); }); - $logger = new GenericLogger(new LogConfig(), $eventBus); + $logger = new GenericLogger(new NullLogConfig(), $this->environment, $this->bus); $logger->log($level, 'This is a log message of level: ' . $level->value, context: ['foo' => 'bar']); } - public function test_different_log_levels_works(): void + #[Test] + public function different_log_levels(): void { $filePath = __DIR__ . '/logs/tempest.log'; - $config = new LogConfig( + $config = new SimpleLogConfig( + path: $filePath, prefix: 'tempest', - channels: [ - new AppendLogChannel($filePath), - ], ); - $logger = new GenericLogger($config, $this->container->get(EventBus::class)); + $logger = new GenericLogger($config, $this->environment, $this->bus); $logger->critical('critical'); $logger->debug('debug'); $this->assertFileExists($filePath); - $content = file_get_contents($filePath); - $this->assertStringContainsString('critical', $content); - $this->assertStringContainsString('debug', $content); + $this->assertStringContainsString('critical', Filesystem\read_file($filePath)); + $this->assertStringContainsString('debug', Filesystem\read_file($filePath)); } public static function tempestLevelProvider(): array diff --git a/tests/Integration/Log/LogConfigTest.php b/tests/Integration/Log/LogConfigTest.php deleted file mode 100644 index ada19f1a1d..0000000000 --- a/tests/Integration/Log/LogConfigTest.php +++ /dev/null @@ -1,23 +0,0 @@ -container->get(LogConfig::class); - - $this->assertSame(root_path('log/debug.log'), $logConfig->debugLogPath); - } -} diff --git a/tests/Integration/Mailer/Fixtures/SendWelcomeEmail.php b/tests/Integration/Mailer/Fixtures/SendWelcomeEmail.php index b5d8421351..2447242cbb 100644 --- a/tests/Integration/Mailer/Fixtures/SendWelcomeEmail.php +++ b/tests/Integration/Mailer/Fixtures/SendWelcomeEmail.php @@ -6,7 +6,7 @@ use Tempest\Mail\Envelope; use Tempest\View\View; -use function Tempest\view; +use function Tempest\View\view; final class SendWelcomeEmail implements Email { diff --git a/tests/Integration/Mailer/SentEmailTest.php b/tests/Integration/Mailer/SentEmailTest.php index 8d16e96c3d..79e4b01680 100644 --- a/tests/Integration/Mailer/SentEmailTest.php +++ b/tests/Integration/Mailer/SentEmailTest.php @@ -14,7 +14,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mailer\Fixtures\SendWelcomeEmail; -use function Tempest\view; +use function Tempest\View\view; final class SentEmailTest extends FrameworkIntegrationTestCase { diff --git a/tests/Integration/Mapper/CasterFactoryTest.php b/tests/Integration/Mapper/CasterFactoryTest.php index 119b950a77..a2e9651602 100644 --- a/tests/Integration/Mapper/CasterFactoryTest.php +++ b/tests/Integration/Mapper/CasterFactoryTest.php @@ -10,9 +10,11 @@ use Tempest\Mapper\Casters\IntegerCaster; use Tempest\Mapper\Casters\NativeDateTimeCaster; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; +use Tests\Tempest\Integration\Mapper\Fixtures\InterfaceValueCaster; +use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithInterfaceTypedProperties; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithSerializerProperties; -use function Tempest\reflect; +use function Tempest\Reflection\reflect; final class CasterFactoryTest extends FrameworkIntegrationTestCase { @@ -31,4 +33,12 @@ public function test_for_property(): void $this->assertInstanceOf(EnumCaster::class, $factory->forProperty($class->getProperty('unitEnum'))); $this->assertInstanceOf(EnumCaster::class, $factory->forProperty($class->getProperty('backedEnum'))); } + + public function test_caster_from_interface_attribute(): void + { + $factory = $this->container->get(CasterFactory::class); + $class = reflect(ObjectWithInterfaceTypedProperties::class); + + $this->assertInstanceOf(InterfaceValueCaster::class, $factory->forProperty($class->getProperty('castable'))); + } } diff --git a/tests/Integration/Mapper/Casters/DtoCasterTest.php b/tests/Integration/Mapper/Casters/DtoCasterTest.php deleted file mode 100644 index a741d5dae6..0000000000 --- a/tests/Integration/Mapper/Casters/DtoCasterTest.php +++ /dev/null @@ -1,202 +0,0 @@ - MyObject::class, 'data' => ['name' => 'test']]); - - $dto = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertInstanceOf(MyObject::class, $dto); - $this->assertSame('test', $dto->name); - } - - public function test_cast_with_map(): void - { - $config = new MapperConfig()->serializeAs(MyObject::class, 'my-object'); - - $json = json_encode(['type' => 'my-object', 'data' => ['name' => 'test']]); - - $dto = new DtoCaster($config)->cast($json); - - $this->assertInstanceOf(MyObject::class, $dto); - $this->assertSame('test', $dto->name); - } - - public function test_cannot_cast_with_invalid_json(): void - { - $json = ''; - - $this->expectException(ValueCouldNotBeCast::class); - - new DtoCaster(new MapperConfig())->cast($json); - } - - public function test_cast_nested_objects(): void - { - $json = json_encode([ - 'type' => NestedObjectA::class, - 'data' => [ - 'items' => [ - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Frieren'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Fern'], - ], - ], - ], - ]); - - $dto = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertInstanceOf(NestedObjectA::class, $dto); - $this->assertCount(2, $dto->items); - $this->assertInstanceOf(NestedObjectB::class, $dto->items[0]); - $this->assertSame('Frieren', $dto->items[0]->name); - $this->assertInstanceOf(NestedObjectB::class, $dto->items[1]); - $this->assertSame('Fern', $dto->items[1]->name); - } - - public function test_cast_object_with_nullable_properties(): void - { - $json = json_encode([ - 'type' => ObjectWithNullableProperties::class, - 'data' => [ - 'a' => 'test', - 'b' => 3.14, - 'c' => null, - ], - ]); - - $dto = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertInstanceOf(ObjectWithNullableProperties::class, $dto); - $this->assertSame('test', $dto->a); - $this->assertSame(3.14, $dto->b); - $this->assertNull($dto->c); - } - - public function test_cast_object_with_enums(): void - { - $json = json_encode([ - 'type' => ObjectWithEnum::class, - 'data' => [ - 'method' => 'GET', - ], - ]); - - $dto = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertInstanceOf(ObjectWithEnum::class, $dto); - $this->assertSame(Method::GET, $dto->method); - } - - public function test_cast_array_directly(): void - { - $array = [ - 'type' => MyObject::class, - 'data' => ['name' => 'test'], - ]; - - $dto = new DtoCaster(new MapperConfig())->cast($array); - - $this->assertInstanceOf(MyObject::class, $dto); - $this->assertSame('test', $dto->name); - } - - public function test_cast_top_level_array(): void - { - $json = json_encode([ - [ - 'type' => MyObject::class, - 'data' => ['name' => 'Frieren'], - ], - [ - 'type' => MyObject::class, - 'data' => ['name' => 'Fern'], - ], - ]); - - $dto = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertIsArray($dto); - $this->assertCount(2, $dto); - $this->assertInstanceOf(MyObject::class, $dto[0]); - $this->assertSame('Frieren', $dto[0]->name); - $this->assertInstanceOf(MyObject::class, $dto[1]); - $this->assertSame('Fern', $dto[1]->name); - } - - public function test_cast_with_multiple_mapped_classes(): void - { - $config = new MapperConfig() - ->serializeAs(MyObject::class, 'my-object') - ->serializeAs(NestedObjectB::class, 'nested-b'); - - $json = json_encode([ - 'type' => 'nested-b', - 'data' => ['name' => 'mapped nested'], - ]); - - $dto = new DtoCaster($config)->cast($json); - - $this->assertInstanceOf(NestedObjectB::class, $dto); - $this->assertSame('mapped nested', $dto->name); - } - - public function test_cast_preserves_non_dto_values(): void - { - $originalValue = 42; - - $result = new DtoCaster(new MapperConfig())->cast($originalValue); - - $this->assertSame($originalValue, $result); - } - - public function test_cast_malformed_json_throws_exception(): void - { - $malformedJson = '{"invalid": json}'; - - $this->expectException(ValueCouldNotBeCast::class); - - new DtoCaster(new MapperConfig())->cast($malformedJson); - } - - public function test_serialize_and_cast_roundtrip(): void - { - $original = new NestedObjectA(items: [ - new NestedObjectB(name: 'Frieren'), - new NestedObjectB(name: 'Fern'), - ]); - - $serializer = new DtoSerializer(new MapperConfig()); - $json = $serializer->serialize($original); - - $casted = new DtoCaster(new MapperConfig())->cast($json); - - $this->assertInstanceOf(NestedObjectA::class, $casted); - $this->assertCount(2, $casted->items); - $this->assertInstanceOf(NestedObjectB::class, $casted->items[0]); - $this->assertSame('Frieren', $casted->items[0]->name); - $this->assertInstanceOf(NestedObjectB::class, $casted->items[1]); - $this->assertSame('Fern', $casted->items[1]->name); - } -} diff --git a/tests/Integration/Mapper/Fixtures/ConcreteInterfaceValue.php b/tests/Integration/Mapper/Fixtures/ConcreteInterfaceValue.php new file mode 100644 index 0000000000..bf211d1a74 --- /dev/null +++ b/tests/Integration/Mapper/Fixtures/ConcreteInterfaceValue.php @@ -0,0 +1,17 @@ +value; + } +} diff --git a/tests/Integration/Mapper/Fixtures/DoubleStringCaster.php b/tests/Integration/Mapper/Fixtures/DoubleStringCaster.php index 427b3eefe0..a3789cdb7e 100644 --- a/tests/Integration/Mapper/Fixtures/DoubleStringCaster.php +++ b/tests/Integration/Mapper/Fixtures/DoubleStringCaster.php @@ -8,6 +8,11 @@ final class DoubleStringCaster implements Caster { + public static function for(): string + { + return 'string'; + } + public function cast(mixed $input): string { return $input . $input; diff --git a/tests/Integration/Mapper/Fixtures/DoubleStringSerializer.php b/tests/Integration/Mapper/Fixtures/DoubleStringSerializer.php index b46be7faf8..28a4ab546b 100644 --- a/tests/Integration/Mapper/Fixtures/DoubleStringSerializer.php +++ b/tests/Integration/Mapper/Fixtures/DoubleStringSerializer.php @@ -6,6 +6,11 @@ final class DoubleStringSerializer implements Serializer { + public static function for(): string + { + return 'string'; + } + public function serialize(mixed $input): string { return $input . $input; diff --git a/tests/Integration/Mapper/Fixtures/InterfaceValueCaster.php b/tests/Integration/Mapper/Fixtures/InterfaceValueCaster.php new file mode 100644 index 0000000000..4b863ac90a --- /dev/null +++ b/tests/Integration/Mapper/Fixtures/InterfaceValueCaster.php @@ -0,0 +1,20 @@ +getValue(); + } +} diff --git a/tests/Integration/Mapper/Fixtures/InterfaceWithCastWith.php b/tests/Integration/Mapper/Fixtures/InterfaceWithCastWith.php new file mode 100644 index 0000000000..3c60e8c0f5 --- /dev/null +++ b/tests/Integration/Mapper/Fixtures/InterfaceWithCastWith.php @@ -0,0 +1,15 @@ + 'data']; + } +} diff --git a/tests/Integration/Mapper/Fixtures/MyObject.php b/tests/Integration/Mapper/Fixtures/MyObject.php index f94b013249..cb50dc70ef 100644 --- a/tests/Integration/Mapper/Fixtures/MyObject.php +++ b/tests/Integration/Mapper/Fixtures/MyObject.php @@ -5,7 +5,9 @@ namespace Tests\Tempest\Integration\Mapper\Fixtures; use Tempest\Mapper\CastWith; +use Tempest\Mapper\SerializeAs; +#[SerializeAs(self::class)] #[CastWith(MyObjectCaster::class)] final class MyObject { diff --git a/tests/Integration/Mapper/Fixtures/MyObjectCaster.php b/tests/Integration/Mapper/Fixtures/MyObjectCaster.php index 3ed47c5111..9e737b9ca0 100644 --- a/tests/Integration/Mapper/Fixtures/MyObjectCaster.php +++ b/tests/Integration/Mapper/Fixtures/MyObjectCaster.php @@ -8,6 +8,11 @@ final class MyObjectCaster implements Caster { + public static function for(): false + { + return false; + } + public function cast(mixed $input): MyObject { return new MyObject($input); diff --git a/tests/Integration/Mapper/Fixtures/NestedObjectA.php b/tests/Integration/Mapper/Fixtures/NestedObjectA.php index 439fdf1b1b..30d33ad7c2 100644 --- a/tests/Integration/Mapper/Fixtures/NestedObjectA.php +++ b/tests/Integration/Mapper/Fixtures/NestedObjectA.php @@ -2,6 +2,9 @@ namespace Tests\Tempest\Integration\Mapper\Fixtures; +use Tempest\Mapper\SerializeAs; + +#[SerializeAs(self::class)] final class NestedObjectA { public function __construct( diff --git a/tests/Integration/Mapper/Fixtures/NestedObjectB.php b/tests/Integration/Mapper/Fixtures/NestedObjectB.php index e4d6c35e6e..162a4352d4 100644 --- a/tests/Integration/Mapper/Fixtures/NestedObjectB.php +++ b/tests/Integration/Mapper/Fixtures/NestedObjectB.php @@ -2,6 +2,9 @@ namespace Tests\Tempest\Integration\Mapper\Fixtures; +use Tempest\Mapper\SerializeAs; + +#[SerializeAs(self::class)] final class NestedObjectB { public function __construct( diff --git a/tests/Integration/Mapper/Fixtures/ObjectFactoryACaster.php b/tests/Integration/Mapper/Fixtures/ObjectFactoryACaster.php index a6c94f4fdf..dba240f3db 100644 --- a/tests/Integration/Mapper/Fixtures/ObjectFactoryACaster.php +++ b/tests/Integration/Mapper/Fixtures/ObjectFactoryACaster.php @@ -8,6 +8,11 @@ final class ObjectFactoryACaster implements Caster { + public static function for(): string + { + return 'string'; + } + public function cast(mixed $input): string { return 'casted'; diff --git a/tests/Integration/Mapper/Fixtures/ObjectWithEnum.php b/tests/Integration/Mapper/Fixtures/ObjectWithEnum.php index 80e72f65b7..6c3c3b202a 100644 --- a/tests/Integration/Mapper/Fixtures/ObjectWithEnum.php +++ b/tests/Integration/Mapper/Fixtures/ObjectWithEnum.php @@ -5,7 +5,9 @@ namespace Tests\Tempest\Integration\Mapper\Fixtures; use Tempest\Http\Method; +use Tempest\Mapper\SerializeAs; +#[SerializeAs(self::class)] final class ObjectWithEnum { public Method $method; diff --git a/tests/Integration/Mapper/Fixtures/ObjectWithInterfaceTypedProperties.php b/tests/Integration/Mapper/Fixtures/ObjectWithInterfaceTypedProperties.php new file mode 100644 index 0000000000..5f2bb430f4 --- /dev/null +++ b/tests/Integration/Mapper/Fixtures/ObjectWithInterfaceTypedProperties.php @@ -0,0 +1,13 @@ +in('not-a-context') + ->from([ + 'id' => 1, + 'name' => 'test', + ]); + + $this->assertSame('test', $author->name); + $this->assertSame(1, $author->id->value); + } + + #[Test] + #[TestWith(['custom'], name: 'string')] + #[TestWith([TestMapperContextEnum::VALUE], name: 'enum')] + public function uses_serializers_from_given_context(mixed $context): void + { + $author = new Author( + name: 'test', + ); + + $factory = $this->container->get(Mapper\SerializerFactory::class); + $factory->addSerializer(CustomStringSerializer::class, context: $context, priority: Priority::HIGHEST); + + $serialized = Mapper\map($author) + ->in($context) + ->toJson(); + + $this->assertSame('{"name":"{\"type\":\"string\",\"value\":\"test\"}","type":"a","books":[],"publisher":null,"id":null}', $serialized); + } + + #[Test] + #[TestWith(['custom'], name: 'string')] + #[TestWith([TestMapperContextEnum::VALUE], name: 'enum')] + public function uses_casters_from_given_context(mixed $context): void + { + $factory = $this->container->get(Mapper\CasterFactory::class); + $factory->addCaster(CustomStringSerializer::class, context: $context); + + $author = Mapper\make(Author::class) + ->in($context) + ->from('{"name":"{\"type\":\"string\",\"value\":\"test\"}","type":"a","books":[],"publisher":null,"id":1}'); + + $this->assertInstanceOf(Author::class, $author); + $this->assertSame('test', $author->name); + $this->assertSame(1, $author->id->value); + } +} + +final readonly class CustomStringSerializer implements Mapper\Serializer, Mapper\Caster, Mapper\DynamicSerializer, Mapper\DynamicCaster +{ + public static function accepts(PropertyReflector|TypeReflector $input): bool + { + $type = $input instanceof PropertyReflector + ? $input->getType() + : $input; + + return $type->getName() === 'string'; + } + + public function serialize(mixed $input): string + { + return json_encode(['type' => 'string', 'value' => $input]); + } + + public function cast(mixed $input): mixed + { + $data = json_decode($input, associative: true); + + return $data['value'] ?? null; + } +} + +enum TestMapperContextEnum: string +{ + case VALUE = 'value'; +} diff --git a/tests/Integration/Mapper/MapperTest.php b/tests/Integration/Mapper/MapperTest.php index 06c96e2fbd..dc8bb3c4c9 100644 --- a/tests/Integration/Mapper/MapperTest.php +++ b/tests/Integration/Mapper/MapperTest.php @@ -18,6 +18,7 @@ use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectFactoryA; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectThatShouldUseCasters; +use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithInterfaceTypedProperties; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapFromAttribute; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapToAttribute; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapToCollisions; @@ -27,8 +28,8 @@ use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithStrictProperty; use Tests\Tempest\Integration\Mapper\Fixtures\Person; -use function Tempest\make; -use function Tempest\map; +use function Tempest\Mapper\make; +use function Tempest\Mapper\map; /** * @internal @@ -37,11 +38,10 @@ final class MapperTest extends FrameworkIntegrationTestCase { public function test_make_object_from_class_string(): void { - $author = make(Author::class) - ->from([ - 'id' => 1, - 'name' => 'test', - ]); + $author = make(Author::class)->from([ + 'id' => 1, + 'name' => 'test', + ]); $this->assertSame('test', $author->name); $this->assertSame(1, $author->id->value); @@ -97,14 +97,13 @@ public function test_make_object_with_map_to(): void public function test_make_object_with_has_many_relation(): void { - $author = make(Author::class) - ->from([ - 'name' => 'test', - 'books' => [ - ['title' => 'a'], - ['title' => 'b'], - ], - ]); + $author = make(Author::class)->from([ + 'name' => 'test', + 'books' => [ + ['title' => 'a'], + ['title' => 'b'], + ], + ]); $this->assertSame('test', $author->name); $this->assertCount(2, $author->books); @@ -115,13 +114,12 @@ public function test_make_object_with_has_many_relation(): void public function test_make_object_with_one_to_one_relation(): void { - $book = make(Book::class) - ->from([ - 'title' => 'test', - 'author' => [ - 'name' => 'author', - ], - ]); + $book = make(Book::class)->from([ + 'title' => 'test', + 'author' => [ + 'name' => 'author', + ], + ]); $this->assertSame('test', $book->title); $this->assertSame('author', $book->author->name); @@ -391,4 +389,20 @@ public function test_array_of_objects_to_array(): void actual: $array, ); } + + public function test_cast_with_and_serialize_with_from_interface(): void + { + $object = make(ObjectWithInterfaceTypedProperties::class)->from([ + 'castable' => 'test-value', + 'serializable' => 'another-value', + ]); + + $this->assertSame('casted:test-value', $object->castable->getValue()); + $this->assertSame('casted:another-value', $object->serializable->getValue()); + + $array = map($object)->toArray(); + + $this->assertSame('serialized:casted:test-value', $array['castable']); + $this->assertSame('serialized:casted:another-value', $array['serializable']); + } } diff --git a/tests/Integration/Mapper/Mappers/ArrayToJsonMapperTestCase.php b/tests/Integration/Mapper/Mappers/ArrayToJsonMapperTestCase.php index 6b82871fa4..96fbb61ecd 100644 --- a/tests/Integration/Mapper/Mappers/ArrayToJsonMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/ArrayToJsonMapperTestCase.php @@ -6,7 +6,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/Mappers/ArrayToObjectMapperTestCase.php b/tests/Integration/Mapper/Mappers/ArrayToObjectMapperTestCase.php index 0a3ddf59a3..abcaa8e977 100644 --- a/tests/Integration/Mapper/Mappers/ArrayToObjectMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/ArrayToObjectMapperTestCase.php @@ -19,7 +19,7 @@ use Tests\Tempest\Integration\Mapper\Fixtures\ParentObject; use Tests\Tempest\Integration\Mapper\Fixtures\ParentWithChildrenObject; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/Mappers/JsonFileToObjectMapperTest.php b/tests/Integration/Mapper/Mappers/JsonFileToObjectMapperTest.php index e615d6031d..6dfdbe24b0 100644 --- a/tests/Integration/Mapper/Mappers/JsonFileToObjectMapperTest.php +++ b/tests/Integration/Mapper/Mappers/JsonFileToObjectMapperTest.php @@ -7,7 +7,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/Mappers/JsonToArrayMapperTestCase.php b/tests/Integration/Mapper/Mappers/JsonToArrayMapperTestCase.php index 0124084f96..677006d86f 100644 --- a/tests/Integration/Mapper/Mappers/JsonToArrayMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/JsonToArrayMapperTestCase.php @@ -6,7 +6,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/Mappers/JsonToObjectMapperTestCase.php b/tests/Integration/Mapper/Mappers/JsonToObjectMapperTestCase.php index c130917fd4..d22f709418 100644 --- a/tests/Integration/Mapper/Mappers/JsonToObjectMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/JsonToObjectMapperTestCase.php @@ -5,10 +5,11 @@ namespace Tests\Tempest\Integration\Mapper\Mappers; use Tempest\Mapper\Mappers\JsonToObjectMapper; +use Tempest\Mapper\MappingContext; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal @@ -25,14 +26,14 @@ public function test_json_to_object(): void public function test_invalid_json(): void { - $mapper = new JsonToObjectMapper(); + $mapper = new JsonToObjectMapper(MappingContext::default()); $this->assertFalse($mapper->canMap('invalid', ObjectA::class)); } public function test_invalid_object(): void { - $mapper = new JsonToObjectMapper(); + $mapper = new JsonToObjectMapper(MappingContext::default()); $this->assertFalse($mapper->canMap('{}', 'unknown')); } diff --git a/tests/Integration/Mapper/Mappers/ObjectToArrayMapperTestCase.php b/tests/Integration/Mapper/Mappers/ObjectToArrayMapperTestCase.php index 3de108e643..ebe4d4a71e 100644 --- a/tests/Integration/Mapper/Mappers/ObjectToArrayMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/ObjectToArrayMapperTestCase.php @@ -9,7 +9,7 @@ use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithJsonSerialize; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithNullableProperties; -use function Tempest\map; +use function Tempest\Mapper\map; /** * diff --git a/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTestCase.php b/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTestCase.php index 7b8ff8c54f..860ed03782 100644 --- a/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTestCase.php +++ b/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTestCase.php @@ -7,7 +7,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; -use function Tempest\map; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/ObjectFactoryTest.php b/tests/Integration/Mapper/ObjectFactoryTest.php index 8c18901db5..05d8ef9954 100644 --- a/tests/Integration/Mapper/ObjectFactoryTest.php +++ b/tests/Integration/Mapper/ObjectFactoryTest.php @@ -13,8 +13,8 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; -use function Tempest\make; -use function Tempest\map; +use function Tempest\Mapper\make; +use function Tempest\Mapper\map; /** * @internal diff --git a/tests/Integration/Mapper/SerializerFactoryTest.php b/tests/Integration/Mapper/SerializerFactoryTest.php index 7c9a6382c5..7e381d5d0a 100644 --- a/tests/Integration/Mapper/SerializerFactoryTest.php +++ b/tests/Integration/Mapper/SerializerFactoryTest.php @@ -16,12 +16,14 @@ use Tempest\Mapper\Serializers\StringSerializer; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\DoubleStringSerializer; +use Tests\Tempest\Integration\Mapper\Fixtures\InterfaceValueSerializer; use Tests\Tempest\Integration\Mapper\Fixtures\JsonSerializableObject; use Tests\Tempest\Integration\Mapper\Fixtures\NestedObjectB; +use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithInterfaceTypedProperties; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithSerializerProperties; use Tests\Tempest\Integration\Mapper\Fixtures\SerializableObject; -use function Tempest\reflect; +use function Tempest\Reflection\reflect; final class SerializerFactoryTest extends FrameworkIntegrationTestCase { @@ -66,4 +68,12 @@ public function test_for_property(): void $this->assertInstanceOf(NativeDateTimeSerializer::class, $factory->forProperty($class->getProperty('nativeDateTimeInterfaceProp'))); $this->assertInstanceOf(DateTimeSerializer::class, $factory->forProperty($class->getProperty('dateTimeProp'))); } + + public function test_serializer_from_interface_attribute(): void + { + $factory = $this->container->get(SerializerFactory::class); + $class = reflect(ObjectWithInterfaceTypedProperties::class); + + $this->assertInstanceOf(InterfaceValueSerializer::class, $factory->forProperty($class->getProperty('serializable'))); + } } diff --git a/tests/Integration/Mapper/Serializers/DtoSerializerTest.php b/tests/Integration/Mapper/Serializers/DtoSerializerTest.php deleted file mode 100644 index e8105ee2ed..0000000000 --- a/tests/Integration/Mapper/Serializers/DtoSerializerTest.php +++ /dev/null @@ -1,312 +0,0 @@ -assertSame( - json_encode(['type' => MyObject::class, 'data' => ['name' => 'test']]), - new DtoSerializer(new MapperConfig())->serialize(new MyObject(name: 'test')), - ); - } - - public function test_serialize_with_map(): void - { - $config = new MapperConfig()->serializeAs(MyObject::class, 'my-object'); - - $this->assertSame( - json_encode(['type' => 'my-object', 'data' => ['name' => 'test']]), - new DtoSerializer($config)->serialize(new MyObject(name: 'test')), - ); - } - - public function test_can_serialize_empty_array(): void - { - $result = new DtoSerializer(new MapperConfig())->serialize([]); - - $this->assertSame('[]', $result); - } - - public function test_cannot_serialize_non_object_non_array(): void - { - $this->expectException(ValueCouldNotBeSerialized::class); - - new DtoSerializer(new MapperConfig())->serialize('string'); - } - - public function test_serialize_nested_objects(): void - { - $nestedA = new NestedObjectA(items: [ - new NestedObjectB(name: 'Frieren'), - new NestedObjectB(name: 'Fern'), - ]); - - $expected = json_encode([ - 'type' => NestedObjectA::class, - 'data' => [ - 'items' => [ - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Frieren'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Fern'], - ], - ], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($nestedA), - ); - } - - public function test_serialize_object_with_nullable_properties(): void - { - $object = new ObjectWithNullableProperties( - a: 'test', - b: 3.14, - c: null, - ); - - $expected = json_encode([ - 'type' => ObjectWithNullableProperties::class, - 'data' => [ - 'a' => 'test', - 'b' => 3.14, - 'c' => null, - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($object), - ); - } - - public function test_serialize_object_with_backed_enum(): void - { - $object = new ObjectWithEnum(); - $object->method = Method::POST; - - $expected = json_encode([ - 'type' => ObjectWithEnum::class, - 'data' => [ - 'method' => 'POST', - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($object), - ); - } - - public function test_serialize_object_with_unit_enum(): void - { - $object = new ObjectWithEnum(); - $object->method = Method::GET; - - $expected = json_encode([ - 'type' => ObjectWithEnum::class, - 'data' => [ - 'method' => 'GET', - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($object), - ); - } - - public function test_serialize_complex_nested_structure(): void - { - $nestedA = new NestedObjectA(items: [ - new NestedObjectB(name: 'Frieren'), - new NestedObjectB(name: 'Fern'), - new NestedObjectB(name: 'Stark'), - ]); - - $expected = json_encode([ - 'type' => NestedObjectA::class, - 'data' => [ - 'items' => [ - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Frieren'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Fern'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Stark'], - ], - ], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($nestedA), - ); - } - - public function test_serialize_top_level_array_of_objects(): void - { - $objects = [ - new MyObject(name: 'Frieren'), - new MyObject(name: 'Fern'), - ]; - - $expected = json_encode([ - [ - 'type' => MyObject::class, - 'data' => ['name' => 'Frieren'], - ], - [ - 'type' => MyObject::class, - 'data' => ['name' => 'Fern'], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($objects), - ); - } - - public function test_serialize_json_serializable_object(): void - { - $object = new JsonSerializableObject(); - - $expected = json_encode([ - 'type' => JsonSerializableObject::class, - 'data' => ['a'], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($object), - ); - } - - public function test_serialize_mixed_complex_structure(): void - { - $nestedA = new NestedObjectA(items: [ - new NestedObjectB(name: 'Item 1'), - new NestedObjectB(name: 'Item 2'), - ]); - - $expected = json_encode([ - 'type' => NestedObjectA::class, - 'data' => [ - 'items' => [ - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Item 1'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'Item 2'], - ], - ], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($nestedA), - ); - } - - public function test_serialize_backed_enum_directly(): void - { - $serializer = new DtoSerializer(new MapperConfig()); - - $result = $serializer->serialize([BackedEnumToSerialize::FOO]); - - $this->assertSame('["foo"]', $result); - } - - public function test_serialize_unit_enum_directly(): void - { - $serializer = new DtoSerializer(new MapperConfig()); - - $result = $serializer->serialize([UnitEnumToSerialize::BAR]); - - $this->assertSame('["BAR"]', $result); - } - - public function test_serialize_with_multiple_maps(): void - { - $config = new MapperConfig() - ->serializeAs(MyObject::class, 'my-object') - ->serializeAs(NestedObjectB::class, 'nested-b'); - - $object = new NestedObjectA(items: [ - new NestedObjectB(name: 'mapped'), - ]); - - $expected = json_encode([ - 'type' => NestedObjectA::class, - 'data' => [ - 'items' => [ - [ - 'type' => 'nested-b', - 'data' => ['name' => 'mapped'], - ], - ], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer($config)->serialize($object), - ); - } - - public function test_serialize_array_with_mixed_types(): void - { - $objects = [ - new MyObject(name: 'test1'), - new NestedObjectB(name: 'test2'), - ]; - - $expected = json_encode([ - [ - 'type' => MyObject::class, - 'data' => ['name' => 'test1'], - ], - [ - 'type' => NestedObjectB::class, - 'data' => ['name' => 'test2'], - ], - ]); - - $this->assertSame( - $expected, - new DtoSerializer(new MapperConfig())->serialize($objects), - ); - } -} diff --git a/tests/Integration/Route/ClientTest.php b/tests/Integration/Route/ClientTest.php index 7f281bab25..e5e93dbf18 100644 --- a/tests/Integration/Route/ClientTest.php +++ b/tests/Integration/Route/ClientTest.php @@ -53,6 +53,8 @@ public function test_form_post_request(): void ->withHeader('Referer', 'http://localhost:8088/request-test/form') ->withHeader('Accept', 'application/json') ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withHeader('Sec-Fetch-Site', 'same-origin') + ->withHeader('Sec-Fetch-Mode', 'cors') ->withBody(new StreamFactory()->createStream('name=a a&b.name=b')); try { @@ -72,6 +74,8 @@ public function test_json_post_request(): void ->createRequest('POST', new Uri('http://localhost:8088/request-test/form')) ->withHeader('Accept', 'application/json') ->withHeader('Content-Type', 'application/json') + ->withHeader('Sec-Fetch-Site', 'same-origin') + ->withHeader('Sec-Fetch-Mode', 'cors') ->withBody(new StreamFactory()->createStream('{"name": "a a", "b": {"name": "b"}}')); try { diff --git a/tests/Integration/Route/Fixtures/CustomNotFoundMiddleware.php b/tests/Integration/Route/Fixtures/CustomNotFoundMiddleware.php deleted file mode 100644 index f33e6750cd..0000000000 --- a/tests/Integration/Route/Fixtures/CustomNotFoundMiddleware.php +++ /dev/null @@ -1,22 +0,0 @@ -addHeader('x-not-found', 'indeed'); - - return $response; - } -} diff --git a/tests/Integration/Route/Fixtures/ExceptionThatConvertsToRedirectResponse.php b/tests/Integration/Route/Fixtures/ExceptionThatConvertsToRedirectResponse.php index b24a1464bb..2ddbb55b96 100644 --- a/tests/Integration/Route/Fixtures/ExceptionThatConvertsToRedirectResponse.php +++ b/tests/Integration/Route/Fixtures/ExceptionThatConvertsToRedirectResponse.php @@ -12,7 +12,7 @@ */ final class ExceptionThatConvertsToRedirectResponse extends Exception implements ConvertsToResponse { - public function toResponse(): Response + public function convertToResponse(): Response { return new Redirect('https://tempestphp.com'); } diff --git a/tests/Integration/Route/Fixtures/RateLimitedController.php b/tests/Integration/Route/Fixtures/RateLimitedController.php new file mode 100644 index 0000000000..d417713bd1 --- /dev/null +++ b/tests/Integration/Route/Fixtures/RateLimitedController.php @@ -0,0 +1,40 @@ +container->get(GenericRouter::class); + + $response = $router->dispatch($this->http->makePsrRequest('/inferred/int/123')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('int: 123', $response->body); + } + + #[Test] + public function int_parameter_rejects_non_numeric_values(): void + { + $this->http + ->get('/inferred/int/abc') + ->assertNotFound(); + } + + #[Test] + public function string_parameter_accepts_any_value(): void + { + $router = $this->container->get(GenericRouter::class); + + $response1 = $router->dispatch($this->http->makePsrRequest('/inferred/string/test')); + $this->assertEquals(Status::OK, $response1->status); + $this->assertEquals('string: test', $response1->body); + + $response2 = $router->dispatch($this->http->makePsrRequest('/inferred/string/123')); + $this->assertEquals(Status::OK, $response2->status); + $this->assertEquals('string: 123', $response2->body); + } + + #[Test] + public function float_parameter_accepts_decimal_values(): void + { + $router = $this->container->get(GenericRouter::class); + $response = $router->dispatch($this->http->makePsrRequest('/inferred/float/12.34')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('float: 12.34', $response->body); + } + + #[Test] + public function float_parameter_accepts_integer_values(): void + { + $router = $this->container->get(GenericRouter::class); + $response = $router->dispatch($this->http->makePsrRequest('/inferred/float/42')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('float: 42', $response->body); + } + + #[Test] + public function optional_int_parameter_accepts_numeric_values(): void + { + $router = $this->container->get(GenericRouter::class); + $response = $router->dispatch($this->http->makePsrRequest('/inferred/optional-int/456')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('int: 456', $response->body); + } + + #[Test] + public function optional_int_parameter_rejects_non_numeric_values(): void + { + $this->http + ->get('/inferred/optional-int/xyz') + ->assertNotFound(); + } + + #[Test] + public function optional_int_parameter_accepts_no_value(): void + { + $router = $this->container->get(GenericRouter::class); + $response = $router->dispatch($this->http->makePsrRequest('/inferred/optional-int')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('no id', $response->body); + } + + #[Test] + public function explicit_constraint_is_preserved(): void + { + $this->http + ->get('/inferred/explicit/123') + ->assertOk() + ->assertSee('explicit: 123'); + + $this->http + ->get('/inferred/explicit/1234') + ->assertNotFound(); + + $this->http + ->get('/inferred/explicit/12') + ->assertNotFound(); + } + + #[Test] + public function mixed_parameter_types(): void + { + $router = $this->container->get(GenericRouter::class); + $response = $router->dispatch($this->http->makePsrRequest('/inferred/mixed/42/john')); + + $this->assertEquals(Status::OK, $response->status); + $this->assertEquals('id: 42, name: john', $response->body); + } + + #[Test] + public function mixed_parameter_types_reject_invalid_int(): void + { + $this->http + ->get('/inferred/mixed/abc/john') + ->assertNotFound(); + } +} diff --git a/tests/Integration/Route/NotFoundTest.php b/tests/Integration/Route/NotFoundTest.php deleted file mode 100644 index 45a1c1ec07..0000000000 --- a/tests/Integration/Route/NotFoundTest.php +++ /dev/null @@ -1,26 +0,0 @@ -http->get('unknown-route')->assertNotFound(); - } - - public function test_custom_not_found_middleware(): void - { - $routeConfig = $this->container->get(RouteConfig::class); - $routeConfig->middleware->add(CustomNotFoundMiddleware::class); - - $this->http->get('unknown-route')->assertHasHeader('x-not-found'); - } -} diff --git a/tests/Integration/Route/RateLimitMiddlewareTest.php b/tests/Integration/Route/RateLimitMiddlewareTest.php new file mode 100644 index 0000000000..84ad70ae72 --- /dev/null +++ b/tests/Integration/Route/RateLimitMiddlewareTest.php @@ -0,0 +1,149 @@ +rateLimiter = new TestingRateLimiter(); + + // Unregister any existing singleton and initializer, then add our singleton + if ($this->container instanceof GenericContainer) { + $this->container->unregister(RateLimiter::class); + $this->container->removeInitializer(RateLimiterInitializer::class); + } + $this->container->singleton(RateLimiter::class, fn () => $this->rateLimiter); + } + + public function test_allows_requests_within_limit(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + + // First 3 requests should succeed + for ($i = 0; $i < 3; $i++) { + $response = $this->http->get('/rate-limited'); + $this->assertSame(Status::OK, $response->status); + $this->assertSame('success', $response->body); + } + } + + public function test_blocks_requests_exceeding_limit(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + + // Make 3 requests (the limit) + for ($i = 0; $i < 3; $i++) { + $this->http->get('/rate-limited'); + } + + // 4th request should be blocked + $response = $this->http->get('/rate-limited'); + $this->assertSame(Status::TOO_MANY_REQUESTS, $response->status); + } + + public function test_includes_rate_limit_headers(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + + $response = $this->http->get('/rate-limited'); + + $this->assertSame(Status::OK, $response->status); + $response->assertHasHeader('X-RateLimit-Limit'); + $response->assertHasHeader('X-RateLimit-Remaining'); + $response->assertHasHeader('X-RateLimit-Reset'); + $response->assertHeaderContains('X-RateLimit-Limit', '3'); + $response->assertHeaderContains('X-RateLimit-Remaining', '2'); + } + + public function test_includes_retry_after_header_when_limited(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + + // Exhaust the limit + for ($i = 0; $i < 3; $i++) { + $this->http->get('/rate-limited'); + } + + $response = $this->http->get('/rate-limited'); + + $this->assertSame(Status::TOO_MANY_REQUESTS, $response->status); + $response->assertHasHeader('Retry-After'); + $response->assertHasHeader('X-RateLimit-Limit'); + $response->assertHeaderContains('X-RateLimit-Remaining', '0'); + } + + public function test_routes_without_rate_limit_are_not_affected(): void + { + $this->http->registerRoute([RateLimitedController::class, 'noLimit']); + + // Should be able to make unlimited requests + for ($i = 0; $i < 100; $i++) { + $response = $this->http->get('/no-rate-limit'); + $this->assertSame(Status::OK, $response->status); + } + } + + public function test_different_routes_have_separate_limits(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + $this->http->registerRoute([RateLimitedController::class, 'limitedCustomKey']); + + // Exhaust limit on first route + for ($i = 0; $i < 3; $i++) { + $this->http->get('/rate-limited'); + } + + // Second route should still work (different key) + $response = $this->http->get('/rate-limited-custom-key'); + $this->assertSame(Status::OK, $response->status); + } + + public function test_custom_key_is_used(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limitedCustomKey']); + + // Make 2 requests (the limit for this route) + $response1 = $this->http->get('/rate-limited-custom-key'); + $response2 = $this->http->get('/rate-limited-custom-key'); + + $this->assertSame(Status::OK, $response1->status); + $this->assertSame(Status::OK, $response2->status); + + // 3rd request should be blocked + $response3 = $this->http->get('/rate-limited-custom-key'); + $this->assertSame(Status::TOO_MANY_REQUESTS, $response3->status); + } + + public function test_remaining_count_decrements(): void + { + $this->http->registerRoute([RateLimitedController::class, 'limited']); + + $response1 = $this->http->get('/rate-limited'); + $response1->assertHeaderContains('X-RateLimit-Remaining', '2'); + + $response2 = $this->http->get('/rate-limited'); + $response2->assertHeaderContains('X-RateLimit-Remaining', '1'); + + $response3 = $this->http->get('/rate-limited'); + $response3->assertHeaderContains('X-RateLimit-Remaining', '0'); + } +} diff --git a/tests/Integration/Route/RequestTest.php b/tests/Integration/Route/RequestTest.php index ea7c0e5085..01d8f2357d 100644 --- a/tests/Integration/Route/RequestTest.php +++ b/tests/Integration/Route/RequestTest.php @@ -85,7 +85,7 @@ public function test_from_factory(): void $this->assertEquals(Method::POST->value, $request->getMethod()); $this->assertEquals('/test', $request->getUri()->getPath()); $this->assertEquals(['test' => 'test'], $request->getParsedBody()); - $this->assertEquals(['x-test' => ['test']], $request->getHeaders()); + $this->assertArrayIsEqualToArrayIgnoringListOfKeys(['x-test' => ['test']], $request->getHeaders(), ['sec-fetch-site', 'sec-fetch-mode']); $this->assertCount(1, $request->getCookieParams()); $this->assertArrayHasKey('test', $request->getCookieParams()); $this->assertSame('test', $this->container->get(Encrypter::class)->decrypt($request->getCookieParams()['test'])); @@ -106,6 +106,19 @@ public function test_custom_request_test(): void $this->assertEquals('test-title test-text', $response->body); } + public function test_headers_with_underscores(): void + { + $this->http + ->get( + uri: '/header-with-underscores', + headers: [ + 'tempest_session_id' => 'test', + ], + ) + ->assertOk() + ->assertHeaderMatches('tempest_session_id', 'test'); + } + public function test_generic_request_can_map_to_custom_request(): void { $response = $this->http @@ -123,7 +136,7 @@ public function test_generic_request_can_map_to_custom_request(): void public function test_custom_request_test_with_validation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -151,7 +164,7 @@ public function test_custom_request_test_with_validation(): void public function test_custom_request_test_with_nested_validation(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, diff --git a/tests/Integration/Route/RequestToObjectMapperTest.php b/tests/Integration/Route/RequestToObjectMapperTest.php index d22b9a2c34..176ccc11f6 100644 --- a/tests/Integration/Route/RequestToObjectMapperTest.php +++ b/tests/Integration/Route/RequestToObjectMapperTest.php @@ -18,7 +18,7 @@ use Tests\Tempest\Integration\Route\Fixtures\RequestWithEnum; use Tests\Tempest\Integration\Route\Fixtures\RequestWithTypedQueryParam; -use function Tempest\map; +use function Tempest\Mapper\map; use function Tempest\Support\arr; final class RequestToObjectMapperTest extends FrameworkIntegrationTestCase @@ -30,7 +30,7 @@ public function test_request(): void try { map($request)->to(RequestObjectA::class); } catch (ValidationFailed $validationFailed) { - $this->assertInstanceOf(IsNotNull::class, $validationFailed->failingRules['b'][0]); + $this->assertInstanceOf(IsNotNull::class, $validationFailed->failingRules['b'][0]->rule); } } @@ -140,7 +140,7 @@ public function test_missing_enum_value(): void try { map($request)->to(RequestWithEnum::class); } catch (ValidationFailed $validationFailed) { - $this->assertInstanceOf(IsNotNull::class, $validationFailed->failingRules['enumParam'][0]); + $this->assertInstanceOf(IsNotNull::class, $validationFailed->failingRules['enumParam'][0]->rule); } } } diff --git a/tests/Integration/Route/RouterTest.php b/tests/Integration/Route/RouterTest.php index a4478c43aa..df3b99ed4d 100644 --- a/tests/Integration/Route/RouterTest.php +++ b/tests/Integration/Route/RouterTest.php @@ -4,18 +4,18 @@ namespace Tests\Tempest\Integration\Route; -use Exception; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Stream; use Laminas\Diactoros\Uri; use Tempest\Database\Migrations\CreateMigrationsTable; -use Tempest\Http\HttpRequestFailed; +use Tempest\Http\ContentType; use Tempest\Http\Responses\Ok; -use Tempest\Http\Session\VerifyCsrfMiddleware; use Tempest\Http\Status; use Tempest\Router\GenericRouter; use Tempest\Router\RouteConfig; use Tempest\Router\Router; +use Tempest\Router\SecFetchMode; +use Tempest\Router\SecFetchSite; use Tests\Tempest\Fixtures\Controllers\TestGlobalMiddleware; use Tests\Tempest\Fixtures\Controllers\TestMiddleware; use Tests\Tempest\Fixtures\Migrations\CreateAuthorTable; @@ -83,7 +83,7 @@ public function test_with_view(): void public function test_route_binding(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, @@ -164,7 +164,9 @@ public function test_json_request(): void method: 'POST', body: new Stream(fopen(__DIR__ . '/request.json', 'r')), headers: [ - 'Content-Type' => 'application/json', + 'Content-Type' => ContentType::JSON->value, + 'Sec-Fetch-Site' => SecFetchSite::SAME_ORIGIN->value, + 'Sec-Fetch-Mode' => SecFetchMode::CORS->value, ], )); @@ -190,43 +192,9 @@ public function test_can_add_response_processor(): void ->assertOk(); } - public function test_error_response_processor_throws_http_exceptions_when_instructed(): void - { - $this->expectException(HttpRequestFailed::class); - $this->expectExceptionCode(404); - - $this->http - ->throwExceptions() - ->get('/non-existent'); - } - - public function test_error_response_processor_throws_http_exceptions_if_there_is_a_body(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('oops'); - - $this->registerRoute([Http500Controller::class, 'throwsException']); - - $this->http - ->throwExceptions() - ->get('/throws-exception'); - } - - public function test_throws_http_exception_when_returning_server_error(): void - { - $this->expectException(HttpRequestFailed::class); - $this->expectExceptionCode(500); - - $this->registerRoute([Http500Controller::class, 'serverError']); - - $this->http - ->throwExceptions() - ->get('/returns-server-error'); - } - public function test_error_response_processor_does_not_throw_http_exceptions_if_there_is_a_body(): void { - $this->registerRoute([Http500Controller::class, 'serverErrorWithBody']); + $this->http->registerRoute([Http500Controller::class, 'serverErrorWithBody']); $this->http ->get('/returns-server-error-with-body') @@ -236,7 +204,7 @@ public function test_error_response_processor_does_not_throw_http_exceptions_if_ public function test_converts_to_response(): void { - $this->registerRoute([Http500Controller::class, 'convertsToResponse']); + $this->http->registerRoute([Http500Controller::class, 'convertsToResponse']); $this->http ->get('/returns-converts-to-response') @@ -244,10 +212,20 @@ public function test_converts_to_response(): void ->assertHeaderContains('Location', 'https://tempestphp.com'); } + public function test_router_returns_json_exception_when_accepts_json(): void + { + $this->http->registerRoute([Http500Controller::class, 'throwsException']); + + $this->http + ->get('/throws-exception', headers: ['Accept' => 'application/json']) + ->assertStatus(Status::INTERNAL_SERVER_ERROR) + ->assertJsonHasKeys('message'); + } + public function test_head_requests(): void { - $this->registerRoute([HeadController::class, 'implicitHead']); - $this->registerRoute([HeadController::class, 'explicitHead']); + $this->http->registerRoute([HeadController::class, 'implicitHead']); + $this->http->registerRoute([HeadController::class, 'explicitHead']); $this->http ->head('/implicit-head') @@ -265,8 +243,7 @@ public function test_stateless_decorator(): void $this->http ->get('/stateless') ->assertOk() - ->assertDoesNotHaveCookie('tempest_session_id') - ->assertDoesNotHaveCookie(VerifyCsrfMiddleware::CSRF_COOKIE_KEY); + ->assertDoesNotHaveCookie('tempest_session_id'); } public function test_prefix_decorator(): void @@ -289,7 +266,7 @@ public function test_without_middleware_decorator(): void $this->http ->get('/without-decorated-middleware') ->assertOk() - ->assertDoesNotHaveCookie(VerifyCsrfMiddleware::CSRF_COOKIE_KEY); + ->assertDoesNotHaveHeader('set-cookie'); } public function test_optional_parameter_with_required_parameter(): void diff --git a/tests/Integration/Router/PreventCrossSiteRequestsMiddlewareTest.php b/tests/Integration/Router/PreventCrossSiteRequestsMiddlewareTest.php new file mode 100644 index 0000000000..3f568d1787 --- /dev/null +++ b/tests/Integration/Router/PreventCrossSiteRequestsMiddlewareTest.php @@ -0,0 +1,187 @@ +container->get(RouteConfig::class) + ->middleware->add(PreventCrossSiteRequestsMiddleware::class); + } + + #[Test] + public function safe_methods_are_always_allowed(): void + { + $this->http->get('/test')->assertOk(); + $this->http->head('/test')->assertOk(); + $this->http->options('/test')->assertOk(); + } + + #[Test] + public function post_with_same_origin_is_allowed(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::SAME_ORIGIN, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertOk(); + } + + #[Test] + public function post_with_same_site_is_allowed(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::SAME_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertOk(); + } + + #[Test] + public function post_with_none_site_is_allowed(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::NONE, + 'sec-fetch-mode' => SecFetchMode::NAVIGATE, + ]) + ->assertOk(); + } + + #[Test] + public function post_with_cross_site_navigation_is_allowed(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::NAVIGATE, + ]) + ->assertOk(); + } + + #[Test] + public function post_with_cross_site_cors_is_blocked(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertForbidden(); + } + + #[Test] + public function put_with_cross_site_cors_is_blocked(): void + { + $this->http + ->put('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertForbidden(); + } + + #[Test] + public function patch_with_cross_site_cors_is_blocked(): void + { + $this->http + ->patch('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertForbidden(); + } + + #[Test] + public function delete_with_cross_site_cors_is_blocked(): void + { + $this->http + ->delete('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertForbidden(); + } + + #[Test] + public function post_without_sec_fetch_headers_is_blocked(): void + { + $this->http + ->withoutSecFetchHeaders() + ->post('/test') + ->assertForbidden(); + } + + #[Test] + public function post_with_cross_site_no_cors_is_blocked(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::NO_CORS, + ]) + ->assertForbidden(); + } + + #[Test] + public function post_with_cross_site_websocket_is_blocked(): void + { + $this->http + ->post('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::CROSS_SITE, + 'sec-fetch-mode' => SecFetchMode::WEBSOCKET, + ]) + ->assertForbidden(); + } + + #[Test] + public function delete_with_same_origin_is_allowed(): void + { + $this->http + ->delete('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::SAME_ORIGIN, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertOk(); + } + + #[Test] + public function put_with_same_site_is_allowed(): void + { + $this->http + ->put('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::SAME_SITE, + 'sec-fetch-mode' => SecFetchMode::CORS, + ]) + ->assertOk(); + } + + #[Test] + public function patch_with_none_site_is_allowed(): void + { + $this->http + ->patch('/test', headers: [ + 'sec-fetch-site' => SecFetchSite::NONE, + 'sec-fetch-mode' => SecFetchMode::NAVIGATE, + ]) + ->assertOk(); + } +} diff --git a/tests/Integration/Testing/Http/HttpRouterTesterIntegrationTest.php b/tests/Integration/Testing/Http/HttpRouterTesterIntegrationTest.php index f634eb9a49..739ee93daa 100644 --- a/tests/Integration/Testing/Http/HttpRouterTesterIntegrationTest.php +++ b/tests/Integration/Testing/Http/HttpRouterTesterIntegrationTest.php @@ -6,21 +6,28 @@ use Exception; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\Test; +use Tempest\Http\Request; +use Tempest\Http\Responses\Ok; +use Tempest\Router\Post; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; +use Tests\Tempest\Integration\Route\Fixtures\Http500Controller; /** * @internal */ final class HttpRouterTesterIntegrationTest extends FrameworkIntegrationTestCase { - public function test_get_requests(): void + #[Test] + public function get_requests(): void { $this->http ->get('/test') ->assertOk(); } - public function test_get_requests_failure(): void + #[Test] + public function get_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -29,14 +36,16 @@ public function test_get_requests_failure(): void ->assertOk(); } - public function test_head_requests(): void + #[Test] + public function head_requests(): void { $this->http ->head('/test') ->assertOk(); } - public function test_head_requests_failure(): void + #[Test] + public function head_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -45,14 +54,16 @@ public function test_head_requests_failure(): void ->assertOk(); } - public function test_post_requests(): void + #[Test] + public function post_requests(): void { $this->http ->post('/test') ->assertOk(); } - public function test_post_requests_failure(): void + #[Test] + public function post_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -61,14 +72,16 @@ public function test_post_requests_failure(): void ->assertOk(); } - public function test_put_requests(): void + #[Test] + public function put_requests(): void { $this->http ->put('/test') ->assertOk(); } - public function test_put_requests_failure(): void + #[Test] + public function put_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -77,14 +90,16 @@ public function test_put_requests_failure(): void ->assertOk(); } - public function test_delete_requests(): void + #[Test] + public function delete_requests(): void { $this->http ->delete('/test') ->assertOk(); } - public function test_delete_requests_failure(): void + #[Test] + public function delete_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -93,14 +108,16 @@ public function test_delete_requests_failure(): void ->assertOk(); } - public function test_connect_requests(): void + #[Test] + public function connect_requests(): void { $this->http ->connect('/test') ->assertOk(); } - public function test_connect_requests_failure(): void + #[Test] + public function connect_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -109,14 +126,16 @@ public function test_connect_requests_failure(): void ->assertOk(); } - public function test_options_requests(): void + #[Test] + public function options_requests(): void { $this->http ->options('/test') ->assertOk(); } - public function test_options_requests_failure(): void + #[Test] + public function options_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -125,22 +144,16 @@ public function test_options_requests_failure(): void ->assertOk(); } - public function test_trace_requests(): void + #[Test] + public function trace_requests(): void { $this->http ->trace('/test') ->assertOk(); } - public function test_throw_exceptions(): void - { - $this->expectException(Exception::class); - - $this->http - ->get('/fail'); - } - - public function test_trace_requests_failure(): void + #[Test] + public function trace_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -149,14 +162,16 @@ public function test_trace_requests_failure(): void ->assertOk(); } - public function test_patch_requests(): void + #[Test] + public function patch_requests(): void { $this->http ->patch('/test') ->assertOk(); } - public function test_patch_requests_failure(): void + #[Test] + public function patch_requests_failure(): void { $this->expectException(AssertionFailedError::class); @@ -165,7 +180,20 @@ public function test_patch_requests_failure(): void ->assertOk(); } - public function test_query(): void + #[Test] + public function has_exception(): void + { + $this->http->registerRoute([Http500Controller::class, 'throwsException']); + + $response = $this->http + ->get('/throws-exception') + ->assertServerError(); + + $this->assertInstanceOf(Exception::class, $response->throwable); + } + + #[Test] + public function query(): void { $this->assertSame($this->http->get('/test?foo=baz', query: ['foo' => 'bar'])->request->uri, '/test?foo=bar'); $this->assertSame($this->http->get('/test?jon=doe', query: ['foo' => 'bar'])->request->uri, '/test?jon=doe&foo=bar'); @@ -178,4 +206,27 @@ public function test_query(): void $this->assertSame($this->http->patch('/test', query: ['foo' => 'bar'])->request->uri, '/test?foo=bar'); $this->assertSame($this->http->head('/test', query: ['foo' => 'bar'])->request->uri, '/test?foo=bar'); } + + #[Test] + public function raw_body_string(): void + { + $this->http->registerRoute([TestController::class, 'handleRawBody']); + + $response = $this->http + ->post('/raw-body', body: 'ok') + ->assertOk(); + + $this->assertSame('ok', $response->body); + $this->assertSame('ok', $response->request->raw); + $this->assertSame([], $response->request->body); + } +} + +final class TestController +{ + #[Post('/raw-body')] + public function handleRawBody(Request $request): Ok + { + return new Ok($request->raw ?? ''); + } } diff --git a/tests/Integration/Validator/ExistsRuleTest.php b/tests/Integration/Validator/ExistsRuleTest.php index cca2db2a39..c4f328521f 100644 --- a/tests/Integration/Validator/ExistsRuleTest.php +++ b/tests/Integration/Validator/ExistsRuleTest.php @@ -22,7 +22,7 @@ final class ExistsRuleTest extends FrameworkIntegrationTestCase #[PreCondition] protected function configure(): void { - $this->migrate( + $this->database->migrate( CreateMigrationsTable::class, CreatePublishersTable::class, CreateAuthorTable::class, diff --git a/tests/Integration/Validator/TranslationKeyTest.php b/tests/Integration/Validator/TranslationKeyTest.php new file mode 100644 index 0000000000..52bafe0ed0 --- /dev/null +++ b/tests/Integration/Validator/TranslationKeyTest.php @@ -0,0 +1,42 @@ +container->get(Catalog::class)->add(Locale::default(), 'validation_error.has_length.book_title', 'The length of the title of the book is invalid.'); + $validator = $this->container->get(Validator::class); + + try { + $validator->validateObject(new ValidatableObject(title: 'foo')); + $this->fail('Expected `ValidationFailed` exception was not thrown.'); + } catch (ValidationFailed $validationFailed) { + $this->assertArrayHasKey('title', $validationFailed->failingRules); + $this->assertSame('book_title', $validationFailed->failingRules['title'][0]->key); + $this->assertSame('foo', $validationFailed->failingRules['title'][0]->value); + $this->assertNull($validationFailed->failingRules['title'][0]->field); + $this->assertSame('The length of the title of the book is invalid.', $validator->getErrorMessage($validationFailed->failingRules['title'][0])); + } + } +} + +final class ValidatableObject +{ + public function __construct( + #[Rules\HasLength(min: 5, max: 50)] + #[TranslationKey('book_title')] + public string $title, + ) {} +} diff --git a/tests/Integration/View/BladeViewRendererTest.php b/tests/Integration/View/BladeViewRendererTest.php index 822cee148c..3aec90c9c1 100644 --- a/tests/Integration/View/BladeViewRendererTest.php +++ b/tests/Integration/View/BladeViewRendererTest.php @@ -10,7 +10,7 @@ use Tempest\View\ViewRenderer; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\view; +use function Tempest\View\view; /** * @internal diff --git a/tests/Integration/View/Components/FormComponentTest.php b/tests/Integration/View/Components/FormComponentTest.php index 07f237abe0..7093b95ef7 100644 --- a/tests/Integration/View/Components/FormComponentTest.php +++ b/tests/Integration/View/Components/FormComponentTest.php @@ -9,44 +9,43 @@ final class FormComponentTest extends FrameworkIntegrationTestCase { public function test_form(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('assertStringContainsString('method="POST"', $html); - $this->assertStringContainsString('#csrf_token', $html); } public function test_form_with_body(): void { - $html = $this->render('hi'); + $html = $this->view->render('hi'); $this->assertStringContainsString('hi', $html); } public function test_form_with_string_method(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('method="GET"', $html); } public function test_form_with_enum_method(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('method="GET"', $html); } public function test_form_with_action(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('action="/submit" method="POST"', $html); } public function test_form_with_enctype(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('enctype="application/x-www-form-urlencoded"', $html); } diff --git a/tests/Integration/View/Components/IconComponentTest.php b/tests/Integration/View/Components/IconComponentTest.php index 961b3e00a4..e9fb36fded 100644 --- a/tests/Integration/View/Components/IconComponentTest.php +++ b/tests/Integration/View/Components/IconComponentTest.php @@ -4,7 +4,6 @@ namespace Tests\Tempest\Integration\View\Components; -use Tempest\Core\AppConfig; use Tempest\Core\ConfigCache; use Tempest\Core\Environment; use Tempest\DateTime\Duration; @@ -15,7 +14,7 @@ use Tempest\Icon\IconConfig; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\view; +use function Tempest\View\view; final class IconComponentTest extends FrameworkIntegrationTestCase { @@ -43,7 +42,7 @@ public function test_it_renders_an_icon(): void $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -65,7 +64,7 @@ public function test_it_downloads_the_icon_from_a_custom_api(): void $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -73,7 +72,7 @@ public function test_fallback_without_name(): void { $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -88,7 +87,7 @@ public function test_it_caches_icons_on_the_first_render(): void $this->container->register(HttpClient::class, fn () => $mockHttpClient); - $this->render(''); + $this->view->render(''); $iconCache = $this->container->get(IconCache::class); $cachedIcon = $iconCache->get('icon-ph-eye'); @@ -109,11 +108,11 @@ public function test_it_renders_an_icon_from_cache(): void $this->container->register(HttpClient::class, fn () => $mockHttpClient); // Trigger first render, which should cache the icon - $this->render(''); + $this->view->render(''); $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -127,11 +126,11 @@ public function test_it_renders_a_debug_comment_in_local_env_when_icon_does_not_ ->willReturn(new GenericResponse(status: Status::NOT_FOUND, body: '')); $this->container->register(HttpClient::class, fn () => $mockHttpClient); - $this->container->singleton(AppConfig::class, fn () => new AppConfig(environment: Environment::LOCAL)); + $this->container->singleton(Environment::class, Environment::LOCAL); $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -145,11 +144,11 @@ public function test_it_renders_an_empty_string__in_non_local_env_when_icon_does ->willReturn(new GenericResponse(status: Status::NOT_FOUND, body: '')); $this->container->register(HttpClient::class, fn () => $mockHttpClient); - $this->container->singleton(AppConfig::class, fn () => new AppConfig(environment: Environment::PRODUCTION)); + $this->container->singleton(Environment::class, Environment::PRODUCTION); $this->assertSame( '', - $this->render(''), + $this->view->render(''), ); } @@ -166,7 +165,7 @@ public function test_it_forwards_the_class_attribute(): void $this->assertSame( '', - $this->render( + $this->view->render( '', ), ); @@ -183,7 +182,7 @@ public function test_with_dynamic_data(): void $this->container->register(HttpClient::class, fn () => $mockHttpClient); - $rendered = $this->render( + $rendered = $this->view->render( '', iconName: 'ph:eye', ); @@ -196,7 +195,7 @@ public function test_with_dynamic_data(): void public function test_icon_renders_inside_named_slot_in_a_layout(): void { - $this->registerViewComponent('x-test-layout', '
'); + $this->view->registerViewComponent('x-test-layout', '
'); $mockHttpClient = $this->createMock(HttpClient::class); $mockHttpClient @@ -208,7 +207,7 @@ public function test_icon_renders_inside_named_slot_in_a_layout(): void $this->container->register(HttpClient::class, fn () => $mockHttpClient); $view = view(__DIR__ . '/../../../Fixtures/Views/view-with-icon-inside-named-slot.view.php'); - $html = $this->render($view); + $html = $this->view->render($view); $this->assertSnippetsMatch( '
Test', diff --git a/tests/Integration/View/Components/InputComponentTest.php b/tests/Integration/View/Components/InputComponentTest.php index 3f037198cb..256fbf05ae 100644 --- a/tests/Integration/View/Components/InputComponentTest.php +++ b/tests/Integration/View/Components/InputComponentTest.php @@ -2,7 +2,8 @@ namespace Tests\Tempest\Integration\View\Components; -use Tempest\Http\Session\Session; +use Tempest\Http\Session\FormSession; +use Tempest\Validation\FailingRule; use Tempest\Validation\Rules\HasLength; use Tempest\Validation\Rules\IsInteger; use Tempest\Validation\Rules\IsString; @@ -13,7 +14,7 @@ final class InputComponentTest extends FrameworkIntegrationTestCase { public function test_simple_input(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('', $html); $this->assertStringContainsString('render(''); + $html = $this->view->render(''); $this->assertStringContainsString('', $html); } public function test_with_id(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertStringContainsString('
HTML, show: false); @@ -287,7 +287,7 @@ public function test_falsy_bool_attribute(mixed $value): void #[TestWith(['$show'])] public function test_truthy_bool_attribute(mixed $value): void { - $html = $this->render(<<view->render(<< HTML, show: true); @@ -298,7 +298,7 @@ public function test_truthy_bool_attribute(mixed $value): void public function test_multiple_boolean_attribute(): void { - $html = $this->render(<<view->render(<< HTML); @@ -309,14 +309,14 @@ public function test_multiple_boolean_attribute(): void public function test_expression_attribute_in_raw_element(): void { - $this->registerViewComponent( + $this->view->registerViewComponent( 'x-test', <<<'HTML'
HTML, ); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'
foo

bar

@@ -334,14 +334,14 @@ public function test_echo_in_attributes(): void { $this->assertSame( '
', - $this->render(<<view->render(<< HTML), ); $this->assertSame( '
', - $this->render(<<view->render(<< HTML), ); @@ -349,13 +349,13 @@ public function test_echo_in_attributes(): void public function test_boolean_attributes_in_view_component(): void { - $this->registerViewComponent('x-test', <<view->registerViewComponent('x-test', << HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -366,11 +366,11 @@ public function test_boolean_attributes_in_view_component(): void public function test_global_variables_are_kept(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
{{ $item }}
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' diff --git a/tests/Integration/View/TempestViewRendererTest.php b/tests/Integration/View/TempestViewRendererTest.php index c5c03ca4b7..134326e4c5 100644 --- a/tests/Integration/View/TempestViewRendererTest.php +++ b/tests/Integration/View/TempestViewRendererTest.php @@ -15,7 +15,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Router\uri; -use function Tempest\view; +use function Tempest\View\view; /** * @internal @@ -33,22 +33,22 @@ public function test_view_renderer(): void { $this->assertSame( '

Hello

', - $this->render('

Hello

'), + $this->view->render('

Hello

'), ); $this->assertSame( '

<span>Hello</span>

', - $this->render(view('

{{ $this->foo }}

')->data(foo: 'Hello')), + $this->view->render(view('

{{ $this->foo }}

')->data(foo: 'Hello')), ); $this->assertSame( '

', - $this->render(view('

{{ $this->foo }}

')), + $this->view->render(view('

{{ $this->foo }}

')), ); $this->assertSame( '

Hello

', - $this->render(view('

{!! $this->foo !!}

')->data(foo: 'Hello')), + $this->view->render(view('

{!! $this->foo !!}

')->data(foo: 'Hello')), ); } @@ -69,12 +69,12 @@ public function test_if_attribute(): void { $this->assertSame( '', - $this->render(view('
Hello
')->data(show: false)), + $this->view->render(view('
Hello
')->data(show: false)), ); $this->assertSame( '
Hello
', - $this->render(view('
Hello
')->data(show: true)), + $this->view->render(view('
Hello
')->data(show: true)), ); } @@ -82,33 +82,33 @@ public function test_isset_attribute(): void { $this->assertSame( '', - $this->render(view('
Hello
')), + $this->view->render(view('
Hello
')), ); $this->assertSame( '
else
', - $this->render(view('
Hello
else
')), + $this->view->render(view('
Hello
else
')), ); $this->assertSame( '
elseif
', - $this->render(view('
Hello
elseif
else
')), + $this->view->render(view('
Hello
elseif
else
')), ); $this->assertSame( '
else
', - $this->render(view('
Hello
elseif
else
')), + $this->view->render(view('
Hello
elseif
else
')), ); $this->assertSame( '
Hello
', - $this->render(view('
Hello
', foo: true)), + $this->view->render(view('
Hello
', foo: true)), ); } public function test_if_with_other_expression_attributes(): void { - $html = $this->render('
Hello
', show: true, data: 'test'); + $html = $this->view->render('
Hello
', show: true, data: 'test'); $this->assertSame( '
Hello
', @@ -118,7 +118,7 @@ public function test_if_with_other_expression_attributes(): void public function test_else_with_other_expression_attributes(): void { - $html = $this->render('
Hello
Nothing to see
', show: false, data: 'test'); + $html = $this->view->render('
Hello
Nothing to see
', show: false, data: 'test'); $this->assertSame( '
Nothing to see
', @@ -130,39 +130,43 @@ public function test_elseif_attribute(): void { $this->assertSame( '
A
', - $this->render(view('
A
B
None
')->data(a: true, b: true)), + $this->view->render(view('
A
B
None
')->data(a: true, b: true)), ); $this->assertSame( '
A
', - $this->render(view('
A
B
None
')->data(a: true, b: false)), + $this->view->render(view('
A
B
None
')->data(a: true, b: false)), ); $this->assertSame( '
B
', - $this->render(view('
A
B
None
')->data(a: false, b: true)), + $this->view->render(view('
A
B
None
')->data(a: false, b: true)), ); $this->assertSame( '
None
', - $this->render(view('
A
B
None
')->data(a: false, b: false)), + $this->view->render(view('
A
B
None
')->data(a: false, b: false)), ); $this->assertSame( '
C
', - $this->render( + $this->view->render( view('
A
B
C
None
')->data(a: false, b: false, c: true), ), ); $this->assertSame( '
B
', - $this->render(view('
A
B
C
None
')->data(a: false, b: true, c: true)), + $this->view->render(view('
A
B
C
None
')->data( + a: false, + b: true, + c: true, + )), ); $this->assertSame( '
None
', - $this->render( + $this->view->render( view('
A
B
C
None
')->data(a: false, b: false, c: false), ), ); @@ -170,7 +174,7 @@ public function test_elseif_attribute(): void public function test_else_if_with_other_expression_attributes(): void { - $html = $this->render('
Hello
Nothing to see
', show: false, data: 'test'); + $html = $this->view->render('
Hello
Nothing to see
', show: false, data: 'test'); $this->assertSame( '
Nothing to see
', @@ -182,12 +186,12 @@ public function test_else_attribute(): void { $this->assertSame( '
True
', - $this->render(view('
True
False
')->data(show: true)), + $this->view->render(view('
True
False
')->data(show: true)), ); $this->assertSame( '
False
', - $this->render(view('
True
False
')->data(show: false)), + $this->view->render(view('
True
False
')->data(show: false)), ); } @@ -198,13 +202,13 @@ public function test_foreach_attribute(): void
a
b
HTML, - $this->render(view('
{{ $foo }}
')->data(items: ['a', 'b'])), + $this->view->render(view('
{{ $foo }}
')->data(items: ['a', 'b'])), ); } public function test_foreach_consumes_attribute(): void { - $html = $this->render( + $html = $this->view->render( <<<'HTML' @@ -249,14 +253,14 @@ public function test_forelse_attribute(): void <<<'HTML'
Empty
HTML, - $this->render(view('
{{ $foo }}
Empty
')->data(items: [])), + $this->view->render(view('
{{ $foo }}
Empty
')->data(items: [])), ); $this->assertSame( <<<'HTML'
a
HTML, - $this->render(view('
{{ $foo }}
Empty
')->data(items: ['a'])), + $this->view->render(view('
{{ $foo }}
Empty
')->data(items: ['a'])), ); } @@ -266,7 +270,7 @@ public function test_forelse_with_other_expression_attribute(): void <<<'HTML'
Empty
HTML, - $this->render('
{{ $foo }}
Empty
', items: [], data: 'test'), + $this->view->render('
{{ $foo }}
Empty
', items: [], data: 'test'), ); } @@ -276,7 +280,7 @@ public function test_default_slot(): void <<<'HTML'
Test
HTML, - $this->render( + $this->view->render( <<<'HTML' Test @@ -288,7 +292,7 @@ public function test_default_slot(): void public function test_implicit_default_slot(): void { - $this->assertStringEqualsStringIgnoringLineEndings( + $this->assertSnippetsMatch( <<<'HTML'
@@ -296,7 +300,7 @@ public function test_implicit_default_slot(): void
HTML, - $this->render( + $this->view->render( <<<'HTML' Test @@ -329,7 +333,7 @@ public function test_multiple_slots(): void injected styles HTML, - $this->render( + $this->view->render( <<<'HTML' Test @@ -358,7 +362,7 @@ public function test_pre(): void c HTML, - $this->render( + $this->view->render( <<<'HTML'
a
                         b
@@ -371,14 +375,14 @@ public function test_pre(): void
 
     public function test_use_statements_are_grouped(): void
     {
-        $html = $this->render('');
+        $html = $this->view->render('');
 
         $this->assertStringContainsString('/', $html);
     }
 
     public function test_raw_and_escaped(): void
     {
-        $html = $this->render(view(__DIR__ . '/../../Fixtures/Views/raw-escaped.view.php', var: '

hi

')); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/raw-escaped.view.php', var: '

hi

')); $this->assertStringEqualsStringIgnoringLineEndings(<<<'HTML' <h1>hi</h1> @@ -389,7 +393,7 @@ public function test_raw_and_escaped(): void public function test_html_string(): void { - $html = $this->render(view(__DIR__ . '/../../Fixtures/Views/raw-escaped.view.php', var: HtmlString::createTag('h1', content: 'hi'))); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/raw-escaped.view.php', var: HtmlString::createTag('h1', content: 'hi'))); $this->assertStringEqualsStringIgnoringLineEndings( expected: <<<'HTML' @@ -405,7 +409,7 @@ public function test_no_double_else_attributes(): void { $this->expectException(ElementWasInvalid::class); - $this->render( + $this->view->render( <<<'HTML'
@@ -416,14 +420,14 @@ public function test_no_double_else_attributes(): void public function test_else_must_be_after_if_or_elseif(): void { - $this->render( + $this->view->render( <<<'HTML'
HTML, ); - $this->render( + $this->view->render( <<<'HTML'
@@ -433,7 +437,7 @@ public function test_else_must_be_after_if_or_elseif(): void $this->expectException(ElementWasInvalid::class); - $this->render( + $this->view->render( <<<'HTML'
HTML, @@ -442,7 +446,7 @@ public function test_else_must_be_after_if_or_elseif(): void public function test_elseif_must_be_after_if_or_elseif(): void { - $this->render( + $this->view->render( <<<'HTML'
@@ -452,7 +456,7 @@ public function test_elseif_must_be_after_if_or_elseif(): void $this->expectException(ElementWasInvalid::class); - $this->render( + $this->view->render( <<<'HTML'
HTML, @@ -461,7 +465,7 @@ public function test_elseif_must_be_after_if_or_elseif(): void public function test_forelse_must_be_before_foreach(): void { - $this->render( + $this->view->render( view(<<<'HTML'
@@ -470,7 +474,7 @@ public function test_forelse_must_be_before_foreach(): void $this->expectException(ElementWasInvalid::class); - $this->render( + $this->view->render( <<<'HTML'
HTML, @@ -479,7 +483,7 @@ public function test_forelse_must_be_before_foreach(): void public function test_no_double_forelse_attributes(): void { - $this->render( + $this->view->render( view(<<<'HTML'
@@ -488,7 +492,7 @@ public function test_no_double_forelse_attributes(): void $this->expectException(ElementWasInvalid::class); - $this->render( + $this->view->render( view(<<<'HTML'
@@ -505,7 +509,7 @@ public function test_render_element_with_attribute_with_dash(): void HTML, ); - $html = $this->render($view); + $html = $this->view->render($view); $this->assertStringContainsString( '
', @@ -520,16 +524,16 @@ public function test_view_component_with_multiple_attributes(): void
b
'; - $html = $this->render(view('')); + $html = $this->view->render(view('')); $this->assertSnippetsMatch($expected, $html); - $html = $this->render(view('')); + $html = $this->view->render(view('')); $this->assertSnippetsMatch($expected, $html); - $html = $this->render(view('')); + $html = $this->view->render(view('')); $this->assertSnippetsMatch($expected, $html); - $html = $this->render(view('')); + $html = $this->view->render(view('')); $this->assertSnippetsMatch($expected, $html); } @@ -543,7 +547,7 @@ public function test_slot_with_comment(): void HTML, - $this->render( + $this->view->render( <<<'HTML' @@ -556,21 +560,21 @@ public function test_slot_with_comment(): void public function test_self_closing_component_tags_are_compiled(): void { - $this->registerViewComponent('x-foo', '
foo
'); + $this->view->registerViewComponent('x-foo', '
foo
'); $this->assertSnippetsMatch( '
foo
foo
', - $this->render(''), + $this->view->render(''), ); $this->assertSnippetsMatch( '
foo
foo
', - $this->render(''), + $this->view->render(''), ); $this->assertSnippetsMatch( '
foo
foo
', - $this->render('', hello: null), + $this->view->render('', hello: null), ); } @@ -593,7 +597,7 @@ public function test_html_tags(): void HTML; - $html = $this->render($view); + $html = $this->view->render($view); $this->assertStringContainsString('', $html); $this->assertStringContainsString('', $html); @@ -605,14 +609,14 @@ public function test_html_tags(): void public function test_view_processors(): void { - $html = $this->render('
{{ $global }}
'); + $html = $this->view->render('
{{ $global }}
'); $this->assertStringEqualsStringIgnoringLineEndings('
test
', $html); } public function test_with_at_symbol_in_html_tag(): void { - $rendered = $this->render( + $rendered = $this->view->render( view(''), ); @@ -626,7 +630,7 @@ public function test_with_at_symbol_in_html_tag(): void public function test_with_colon_symbol_in_html_tag(): void { - $rendered = $this->render( + $rendered = $this->view->render( view(''), ); @@ -640,7 +644,7 @@ public function test_with_colon_symbol_in_html_tag(): void public function test_loop_variable_can_be_used_within_the_looped_tag(): void { - $html = $this->render( + $html = $this->view->render( view( <<<'HTML' @@ -669,7 +673,7 @@ public function test_loop_variable_can_be_used_within_the_looped_tag(): void public function test_if_and_foreach_precedence(): void { - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -682,7 +686,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('
A
C
', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -696,7 +700,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('
A
B
C
', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -710,7 +714,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('
A
B
C
', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -724,7 +728,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -738,7 +742,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -752,7 +756,7 @@ public function test_if_and_foreach_precedence(): void $this->assertSnippetsMatch('
A
B
C
', $html); - $html = $this->render( + $html = $this->view->render( <<<'HTML'
{{ $item->name }}
HTML, @@ -768,14 +772,14 @@ public function test_if_and_foreach_precedence(): void public function test_escape_expression_attribute(): void { - $html = $this->render('
'); + $html = $this->view->render('
'); $this->assertSnippetsMatch('
', $html); } public function test_unclosed_php_tag(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' render(<<<'HTML' + $html = $this->view->render(<<<'HTML'

{{-- this is a comment --}}this is rendered text

{{-- this is a comment --}} HTML); @@ -793,7 +797,7 @@ public function test_view_comments(): void public function test_multiline_view_comments(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' {{-- this is a comment
@@ -830,7 +834,7 @@ public function test_parse_rss_feed(): void XML; - $parsed = $this->render($rss, posts: [ + $parsed = $this->view->render($rss, posts: [ ['title' => '

A

', 'url' => 'https://tempestphp.com/a'], ['title' => 'B', 'url' => 'https://tempestphp.com/b'], ]); @@ -854,7 +858,7 @@ public function test_parse_rss_feed(): void public function test_attributes_with_single_quotes(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'
HTML); @@ -863,7 +867,7 @@ public function test_attributes_with_single_quotes(): void public function test_zero_in_attribute(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'
HTML); @@ -887,4 +891,43 @@ public function test_discovery_locations_are_passed_to_compiler(): void $this->assertSnippetsMatch('
Hi
', $html); } + + public function test_whitespace_between_inline_elements_is_preserved(): void + { + /** @var TempestViewRenderer $renderer */ + $renderer = $this->get(TempestViewRenderer::class); + + $this->assertSame( + '

Test Test

', + $renderer->render('

Test Test

'), + ); + } + + public function test_whitespace_introduced_by_line_breaks_is_preserved(): void + { + /** @var TempestViewRenderer $renderer */ + $renderer = $this->get(TempestViewRenderer::class); + + $this->assertSame( + '

Test +Test

', + $renderer->render('

Test +Test

'), + ); + } + + public function test_whitespace_with_blank_lines_between_inline_elements_is_preserved(): void + { + /** @var TempestViewRenderer $renderer */ + $renderer = $this->get(TempestViewRenderer::class); + + $this->assertSame( + '

Test + +Test

', + $renderer->render('

Test + +Test

'), + ); + } } diff --git a/tests/Integration/View/TwigViewRendererTest.php b/tests/Integration/View/TwigViewRendererTest.php index 246b86e739..68fbb9b2f1 100644 --- a/tests/Integration/View/TwigViewRendererTest.php +++ b/tests/Integration/View/TwigViewRendererTest.php @@ -10,7 +10,7 @@ use Tempest\View\ViewRenderer; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\view; +use function Tempest\View\view; /** * @internal diff --git a/tests/Integration/View/ViewCacheTest.php b/tests/Integration/View/ViewCacheTest.php index 86b609b996..a594a6166b 100644 --- a/tests/Integration/View/ViewCacheTest.php +++ b/tests/Integration/View/ViewCacheTest.php @@ -4,6 +4,7 @@ namespace Tests\Tempest\Integration\View; +use PHPUnit\Framework\Attributes\Test; use Tempest\View\ViewCache; use Tempest\View\ViewCachePool; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -42,9 +43,65 @@ protected function tearDown(): void rmdir(self::DIRECTORY); } + putenv('ENVIRONMENT=testing'); + putenv('VIEW_CACHE='); + putenv('INTERNAL_CACHES='); + parent::tearDown(); } + #[Test] + public function enabled_by_default_in_production(): void + { + putenv('ENVIRONMENT=production'); + + $this->container->unregister(ViewCache::class); + + $this->assertTrue($this->container->get(ViewCache::class)->enabled); + } + + #[Test] + public function enabled_by_default_in_staging(): void + { + putenv('ENVIRONMENT=staging'); + + $this->container->unregister(ViewCache::class); + + $this->assertTrue($this->container->get(ViewCache::class)->enabled); + } + + #[Test] + public function disabled_by_default_locally(): void + { + putenv('ENVIRONMENT=local'); + + $this->container->unregister(ViewCache::class); + + $this->assertFalse($this->container->get(ViewCache::class)->enabled); + } + + #[Test] + public function overriden_by_internal_caches_in_production(): void + { + putenv('ENVIRONMENT=production'); + putenv('INTERNAL_CACHES=false'); + + $this->container->unregister(ViewCache::class); + + $this->assertFalse($this->container->get(ViewCache::class)->enabled); + } + + #[Test] + public function overriden_by_view_cache_locally(): void + { + putenv('ENVIRONMENT=local'); + putenv('VIEW_CACHE=true'); + + $this->container->unregister(ViewCache::class); + + $this->assertTrue($this->container->get(ViewCache::class)->enabled); + } + public function test_view_cache(): void { $path = $this->viewCache->getCachedViewPath('path', fn () => 'hi'); diff --git a/tests/Integration/View/ViewComponentDiscoveryTest.php b/tests/Integration/View/ViewComponentDiscoveryTest.php index 5164c49e3b..5953dff517 100644 --- a/tests/Integration/View/ViewComponentDiscoveryTest.php +++ b/tests/Integration/View/ViewComponentDiscoveryTest.php @@ -29,7 +29,7 @@ public function test_vendor_components_get_overwritten(): void isVendorComponent: false, )); - $this->assertSame('overwritten', $this->render('')); + $this->assertSame('overwritten', $this->view->render('')); } public function test_project_view_components_cannot_be_overwritten_by_other_project_view_component(): void @@ -76,7 +76,7 @@ public function test_project_view_components_will_not_be_overwritten_by_vendor_v isVendorComponent: true, )); - $this->assertSame('overwritten', $this->render('')); + $this->assertSame('overwritten', $this->view->render('')); } public function test_auto_registration(): void @@ -86,7 +86,7 @@ public function test_auto_registration(): void $discovery->discoverPath(new DiscoveryLocation('', ''), __DIR__ . '/x-auto-registered.view.php'); $discovery->apply(); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -100,7 +100,7 @@ public function test_auto_registration_with_x_component(): void $discovery->discoverPath(new DiscoveryLocation('', ''), __DIR__ . '/x-auto-registered-with-declaration.view.php'); $discovery->apply(); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); diff --git a/tests/Integration/View/ViewComponentTest.php b/tests/Integration/View/ViewComponentTest.php index 504b72de0d..4da32987b1 100644 --- a/tests/Integration/View/ViewComponentTest.php +++ b/tests/Integration/View/ViewComponentTest.php @@ -6,9 +6,9 @@ use Generator; use PHPUnit\Framework\Attributes\DataProvider; -use Tempest\Core\AppConfig; use Tempest\Core\Environment; -use Tempest\Http\Session\Session; +use Tempest\Http\Session\FormSession; +use Tempest\Validation\FailingRule; use Tempest\Validation\Rules\IsAlphaNumeric; use Tempest\Validation\Rules\IsBetween; use Tempest\Validation\Validator; @@ -19,7 +19,7 @@ use Tests\Tempest\Fixtures\Views\DocsView; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\view; +use function Tempest\View\view; /** * @internal @@ -38,17 +38,17 @@ public function test_view_components(string $component, string $rendered): void { $this->assertSnippetsMatch( expected: $rendered, - actual: $this->render(view($component)), + actual: $this->view->render(view($component)), ); } public function test_view_component_with_php_code_in_attribute(): void { - $this->registerViewComponent('x-test', '
'); + $this->view->registerViewComponent('x-test', '
'); $this->assertSame( expected: '
', - actual: $this->render( + actual: $this->view->render( <<<'HTML' HTML, @@ -61,13 +61,13 @@ public function test_view_component_with_php_code_in_slot(): void { $this->assertSame( expected: '
bar
', - actual: $this->render(view('{{ $this->foo }}')->data(foo: 'bar')), + actual: $this->view->render(view('{{ $this->foo }}')->data(foo: 'bar')), ); } public function test_view_can_access_dynamic_slots(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
{{ $slot->name }}
{{ $slot->attributes['language'] }}
@@ -76,7 +76,7 @@ public function test_view_can_access_dynamic_slots(): void
HTML); - $html = $this->render(<<<'HTML_WRAP' + $html = $this->view->render(<<<'HTML_WRAP' PHP Body HTML Body @@ -91,14 +91,14 @@ public function test_view_can_access_dynamic_slots(): void public function test_dynamic_slots_are_cleaned_up(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
{{ $slot->name }}
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -116,12 +116,12 @@ public function test_dynamic_slots_are_cleaned_up(): void public function test_dynamic_slots_include_the_default_slot(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
{{ $slots['default']->name }}
{{ $slots['default']->content }}
HTML); - $html = $this->render('Hello'); + $html = $this->view->render('Hello'); $this->assertSnippetsMatch( <<<'HTML' @@ -134,20 +134,20 @@ public function test_dynamic_slots_include_the_default_slot(): void public function test_slots_with_nested_view_components(): void { - $this->registerViewComponent('x-a', <<<'HTML' + $this->view->registerViewComponent('x-a', <<<'HTML'
A{{ $slot->name }}
HTML); - $this->registerViewComponent('x-b', <<<'HTML' + $this->view->registerViewComponent('x-b', <<<'HTML'
B{{ $slot->name }}
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -170,12 +170,12 @@ public function test_slots_is_a_reserved_variable(): void $this->expectException(ViewVariableWasReserved::class); $this->expectExceptionMessage('Cannot use reserved variable name `slots`'); - $this->render('', slots: []); + $this->view->render('', slots: []); } public function test_scope_does_not_leak_data(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -189,7 +189,7 @@ public function test_scope_does_not_leak_data(): void public function test_component_with_anther_component_included(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertSnippetsMatch(<<<'HTML' hi @@ -201,7 +201,7 @@ public function test_component_with_anther_component_included(): void public function test_component_with_anther_component_included_with_slot(): void { - $html = $this->render('test'); + $html = $this->view->render('test'); $this->assertSnippetsMatch(<<<'HTML' hi @@ -215,22 +215,19 @@ public function test_component_with_anther_component_included_with_slot(): void public function test_view_component_with_injected_view(): void { - $between = new IsBetween(min: 1, max: 10); - $alphaNumeric = new IsAlphaNumeric(); + $between = new FailingRule(new IsBetween(min: 1, max: 10)); + $alphaNumeric = new FailingRule(new IsAlphaNumeric()); - $session = $this->container->get(Session::class); - - $session->flash( - Session::VALIDATION_ERRORS, + $formSession = $this->container->get(FormSession::class); + $formSession->setErrors( ['name' => [$between, $alphaNumeric]], ); - $session->flash( - Session::ORIGINAL_VALUES, + $formSession->setOriginalValues( ['name' => 'original name'], ); - $html = $this->render(view( + $html = $this->view->render(view( <<<'HTML' HTML, @@ -247,24 +244,24 @@ public function test_component_with_if(): void { $this->assertSame( expected: '
true
', - actual: $this->render('truefalse', show: true), + actual: $this->view->render('truefalse', show: true), ); $this->assertSame( expected: '
false
', - actual: $this->render('truefalse', show: false), + actual: $this->view->render('truefalse', show: false), ); } public function test_component_with_foreach(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); $this->assertSnippetsMatch( expected: '
a
b
', - actual: $this->render('{{ $foo }}', items: ['a', 'b']), + actual: $this->view->render('{{ $foo }}', items: ['a', 'b']), ); } @@ -274,7 +271,7 @@ public function test_anonymous_view_component(): void <<hi HTML, - $this->render('hi'), + $this->view->render('hi'), ); } @@ -282,13 +279,13 @@ public function test_with_header(): void { $this->assertSame( '/', - $this->render(''), + $this->view->render(''), ); } public function test_with_passed_variable(): void { - $rendered = $this->render( + $rendered = $this->view->render( view('')->data( variable: 'test', ), @@ -304,7 +301,7 @@ public function test_with_passed_variable(): void public function test_with_passed_data(): void { - $rendered = $this->render( + $rendered = $this->view->render( view(''), ); @@ -318,7 +315,7 @@ public function test_with_passed_data(): void public function test_with_passed_php_data(): void { - $rendered = $this->render( + $rendered = $this->view->render( view(<< HTML), @@ -336,7 +333,7 @@ public function test_view_component_with_nested_property_to_view(): void { $view = new DocsView(new Chapter('Current Title')); - $html = $this->render($view); + $html = $this->view->render($view); $this->assertStringContainsString('Current Title', $html); } @@ -345,14 +342,14 @@ public function test_view_component_with_nested_call_to_view(): void { $view = new DocsView(new Chapter('Current Title')); - $html = $this->render($view); + $html = $this->view->render($view); $this->assertStringContainsString('Next Title', $html); } public function test_with_passed_variable_within_loop(): void { - $rendered = $this->render( + $rendered = $this->view->render( <<<'HTML' HTML, @@ -368,7 +365,7 @@ public function test_with_passed_variable_within_loop(): void public function test_inline_view_variables_passed_to_component(): void { - $html = $this->render(view(__DIR__ . '/../../Fixtures/Views/view-defined-local-vars-b.view.php')); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/view-defined-local-vars-b.view.php')); $this->assertStringContainsString('fromPHP', $html); $this->assertStringContainsString('fromString', $html); @@ -377,7 +374,7 @@ public function test_inline_view_variables_passed_to_component(): void public function test_view_component_attribute_variables_without_this(): void { - $html = $this->render(view(__DIR__ . '/../../Fixtures/Views/view-component-attribute-without-this-b.view.php')); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/view-component-attribute-without-this-b.view.php')); $this->assertSame(<<render(view(__DIR__ . '/../../Fixtures/Views/view-component-with-non-self-closing-slot-b.view.php')); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/view-component-with-non-self-closing-slot-b.view.php')); $this->assertSnippetsMatch(<<registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML' {{ $metaType ?? 'nothing' }} HTML); - $this->assertSame('test', $this->render('')); - $this->assertSame('test', $this->render('')); - $this->assertSame('test', $this->render('')); + $this->assertSame('test', $this->view->render('')); + $this->assertSame('test', $this->view->render('')); + $this->assertSame('test', $this->view->render('')); } public function test_php_code_in_attribute(): void { $this->expectException(DataAttributeWasInvalid::class); - $html = $this->render(view(__DIR__ . '/../../Fixtures/Views/button-usage.view.php')); + $html = $this->view->render(view(__DIR__ . '/../../Fixtures/Views/button-usage.view.php')); } public function test_template_component(): void { - $html = $this->render( + $html = $this->view->render( <<<'HTML'
item {{ $item }}
@@ -473,31 +470,40 @@ public static function view_components(): Generator public function test_full_html_document_as_component(): void { - $this->registerViewComponent('x-layout', <<<'HTML' - - - Tempest View - - - - - + $this->view->registerViewComponent('x-layout', <<<'HTML' + + + Tempest View + + + + + HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' Hello World HTML); - $this->assertStringContainsString('Tempest View', $html); + $this->assertStringContainsString(<<<'HTML' + + + Tempest View + + + HTML, $html); $this->assertStringContainsString('Hello World', $html); - $this->assertStringContainsString('', $html); + $this->assertStringContainsString('', $html); + $this->assertStringContainsString('', $html); } public function test_empty_slots_are_commented_out(): void { - $this->registerViewComponent('x-layout', <<<'HTML' + $this->container->singleton(Environment::class, Environment::LOCAL); + + $this->view->registerViewComponent('x-layout', <<<'HTML' @@ -507,7 +513,7 @@ public function test_empty_slots_are_commented_out(): void HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -519,9 +525,9 @@ public function test_empty_slots_are_commented_out(): void public function test_empty_slots_are_removed_in_production(): void { - $this->container->get(AppConfig::class)->environment = Environment::PRODUCTION; + $this->container->singleton(Environment::class, Environment::PRODUCTION); - $this->registerViewComponent('x-layout', <<<'HTML' + $this->view->registerViewComponent('x-layout', <<<'HTML' @@ -531,7 +537,7 @@ public function test_empty_slots_are_removed_in_production(): void HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -543,11 +549,11 @@ public function test_empty_slots_are_removed_in_production(): void public function test_custom_components_in_head(): void { - $this->registerViewComponent('x-custom-link', <<<'HTML' + $this->view->registerViewComponent('x-custom-link', <<<'HTML' HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -564,11 +570,11 @@ public function test_custom_components_in_head(): void public function test_head_injection(): void { - $this->registerViewComponent('x-custom-link', <<<'HTML' + $this->view->registerViewComponent('x-custom-link', <<<'HTML' HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -591,11 +597,11 @@ public function test_head_injection(): void public function test_attributes_variable_in_view_component(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -606,11 +612,11 @@ public function test_attributes_variable_in_view_component(): void public function test_fallthrough_attributes(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -621,11 +627,11 @@ public function test_fallthrough_attributes(): void public function test_merged_fallthrough_attributes(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -636,11 +642,11 @@ public function test_merged_fallthrough_attributes(): void public function test_fallthrough_attributes_with_other_attributes(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -651,14 +657,14 @@ public function test_fallthrough_attributes_with_other_attributes(): void public function test_file_name_component(): void { - $html = $this->render(''); + $html = $this->view->render(''); $this->assertSame('
hi!
', $html); } public function test_array_attribute(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'
HTML); @@ -669,11 +675,11 @@ public function test_array_attribute(): void public function test_merge_class(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -684,11 +690,11 @@ public function test_merge_class(): void public function test_merge_class_from_template_to_component(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -699,7 +705,7 @@ public function test_merge_class_from_template_to_component(): void public function test_does_not_duplicate_br(): void { - $this->registerViewComponent('x-html-base', <<<'HTML' + $this->view->registerViewComponent('x-html-base', <<<'HTML' @@ -710,7 +716,7 @@ public function test_does_not_duplicate_br(): void HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'

@@ -725,7 +731,7 @@ public function test_does_not_duplicate_br(): void public function test_renders_minified_html_with_void_elements(): void { - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -736,16 +742,16 @@ public function test_renders_minified_html_with_void_elements(): void public function test_multiple_instances_of_custom_component_using_slots(): void { - $this->registerViewComponent('x-foo-bar', 'FOO-BAR'); + $this->view->registerViewComponent('x-foo-bar', 'FOO-BAR'); - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -762,13 +768,13 @@ public function test_multiple_instances_of_custom_component_using_slots(): void public function test_slots_with_hyphens(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML'
HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' Hi @@ -785,13 +791,13 @@ public function test_slots_with_hyphens(): void public function test_nested_table_components(): void { - $this->registerViewComponent('x-my-table-thead', ''); - $this->registerViewComponent('x-my-table-tbody', ''); - $this->registerViewComponent('x-my-table-tr', ''); - $this->registerViewComponent('x-my-table-td', ''); - $this->registerViewComponent('x-my-table-th', ''); + $this->view->registerViewComponent('x-my-table-thead', ''); + $this->view->registerViewComponent('x-my-table-tbody', ''); + $this->view->registerViewComponent('x-my-table-tr', ''); + $this->view->registerViewComponent('x-my-table-td', ''); + $this->view->registerViewComponent('x-my-table-th', ''); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -824,9 +830,9 @@ public function test_nested_table_components(): void public function test_dynamic_view_component_with_string_name(): void { - $this->registerViewComponent('x-test', '
{{ $prop }}
'); + $this->view->registerViewComponent('x-test', '
{{ $prop }}
'); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML); @@ -835,9 +841,9 @@ public function test_dynamic_view_component_with_string_name(): void public function test_dynamic_view_component_with_expression_name(): void { - $this->registerViewComponent('x-test', '
{{ $prop }}
'); + $this->view->registerViewComponent('x-test', '
{{ $prop }}
'); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML, name: 'x-test'); @@ -846,9 +852,9 @@ public function test_dynamic_view_component_with_expression_name(): void public function test_dynamic_view_component_with_variable_attribute(): void { - $this->registerViewComponent('x-test', '
{{ $prop }}
'); + $this->view->registerViewComponent('x-test', '
{{ $prop }}
'); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' HTML, name: 'x-test', input: 'test'); @@ -857,9 +863,9 @@ public function test_dynamic_view_component_with_variable_attribute(): void public function test_dynamic_view_component_with_slot(): void { - $this->registerViewComponent('x-test', '
'); + $this->view->registerViewComponent('x-test', '
'); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' test HTML, name: 'x-test'); @@ -868,10 +874,10 @@ public function test_dynamic_view_component_with_slot(): void public function test_nested_slots(): void { - $this->registerViewComponent('x-a', ''); - $this->registerViewComponent('x-b', ''); + $this->view->registerViewComponent('x-a', ''); + $this->view->registerViewComponent('x-b', ''); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' hi @@ -882,16 +888,16 @@ public function test_nested_slots(): void public function test_nested_slots_with_escaping(): void { - $this->registerViewComponent('x-a', ''); - $this->registerViewComponent('x-b', <<<'HTML' + $this->view->registerViewComponent('x-a', ''); + $this->view->registerViewComponent('x-b', <<<'HTML' - {{ get(AppConfig::class)->environment->value }} + {{ get(Environment::class)->value }} HTML); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -902,9 +908,9 @@ public function test_nested_slots_with_escaping(): void public function test_repeated_local_var_across_view_components(): void { - $this->registerViewComponent('x-test', '
{{ $thing }}
'); + $this->view->registerViewComponent('x-test', '
{{ $thing }}
'); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -919,16 +925,16 @@ public function test_repeated_local_var_across_view_components(): void public function test_escape_expression_attribute_in_view_components(): void { - $this->registerViewComponent('x-test', '
'); + $this->view->registerViewComponent('x-test', '
'); - $html = $this->render(''); + $html = $this->view->render(''); $this->assertSnippetsMatch('
', $html); } public function test_default_slot_value(): void { - $this->registerViewComponent('x-test', <<<'HTML' + $this->view->registerViewComponent('x-test', <<<'HTML' Default Default A Default B @@ -940,7 +946,7 @@ public function test_default_slot_value(): void Overwritten A Overwritten B HTML, - $this->render(' + $this->view->render(' Overwritten Overwritten A Overwritten B @@ -953,7 +959,7 @@ public function test_default_slot_value(): void Default A Overwritten B HTML, - $this->render(' + $this->view->render(' Overwritten Overwritten B '), @@ -963,14 +969,14 @@ public function test_default_slot_value(): void Default Default A Default B - HTML, $this->render('')); + HTML, $this->view->render('')); } public function test_view_variables_are_passed_into_the_component(): void { - $this->registerViewComponent('x-a', ''); + $this->view->registerViewComponent('x-a', ''); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' {{ $title }} @@ -981,10 +987,10 @@ public function test_view_variables_are_passed_into_the_component(): void public function test_capture_outer_scope_view_component_variables(): void { - $this->registerViewComponent('x-a', ''); - $this->registerViewComponent('x-b', ''); + $this->view->registerViewComponent('x-a', ''); + $this->view->registerViewComponent('x-b', ''); - $html = $this->render( + $html = $this->view->render( <<<'HTML' @@ -997,4 +1003,12 @@ public function test_capture_outer_scope_view_component_variables(): void $this->assertSnippetsMatch('a b', $html); } + + public function test_fallthrough_attribute_without_value(): void + { + $this->view->registerViewComponent('x-test', '
hi
'); + + $this->assertSnippetsMatch('', $this->view->render('')); + $this->assertSnippetsMatch('
hi
', $this->view->render('')); + } } diff --git a/tests/Integration/View/ViewTest.php b/tests/Integration/View/ViewTest.php index 209f36fac4..90a8c587d4 100644 --- a/tests/Integration/View/ViewTest.php +++ b/tests/Integration/View/ViewTest.php @@ -10,7 +10,7 @@ use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use function Tempest\Router\uri; -use function Tempest\view; +use function Tempest\View\view; /** * @internal @@ -21,7 +21,7 @@ public function test_render(): void { $view = view(__DIR__ . '/../../Fixtures/Views/overview.view.php')->data(name: 'Brent'); - $html = $this->render($view); + $html = $this->view->render($view); $this->assertStringContainsString( 'Brent!', @@ -38,7 +38,7 @@ public function test_render_with_view_model(): void { $view = new ViewModel('Brent'); - $html = $this->render($view); + $html = $this->view->render($view); $expected = <<vite->setRootDirectory(__DIR__ . '/Fixtures/tmp'); } - public function test_vite_tags(): void + #[Test] + public function vite_tags(): void { $this->vite->call( - callback: function (): void { - $tags = vite_tags('src/main.ts'); - - $this->assertInstanceOf(HtmlString::class, $tags); - $this->assertSame( - expected: implode('', [ - '', - '', - ]), - actual: (string) vite_tags('src/main.ts'), - ); - }, + callback: fn () => $this->assertSame( + expected: [ + '', + '', + ], + actual: get_tags('src/main.ts'), + ), files: [ 'public/vite-tempest' => ['url' => 'http://localhost:5173'], 'src/main.ts' => '', diff --git a/tests/Integration/Vite/ViteTagsComponentTest.php b/tests/Integration/Vite/ViteTagsComponentTest.php index 11ae39b6a2..ae70a1ca6f 100644 --- a/tests/Integration/Vite/ViteTagsComponentTest.php +++ b/tests/Integration/Vite/ViteTagsComponentTest.php @@ -29,7 +29,7 @@ public function test_dev_entrypoint(): void entrypoints: ['src/foo.ts', 'src/bar.css'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -62,7 +62,7 @@ public function test_dev_entrypoints(): void entrypoints: ['src/foo.ts', 'src/bar.css'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -95,7 +95,7 @@ public function test_dev_entrypoints_from_config(): void entrypoints: ['src/foo.ts', 'src/bar.css'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -128,7 +128,7 @@ public function test_dev_entrypoints_from_config_and_react_refresh_from_bridgefi entrypoints: ['src/foo.ts', 'src/bar.css'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -172,7 +172,7 @@ public function test_production_entrypoint_from_config(): void entrypoints: ['src/foo.ts'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -200,7 +200,7 @@ public function test_production_entrypoint(): void entrypoints: ['src/foo.ts'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML' @@ -228,7 +228,7 @@ public function test_production_entrypoints(): void entrypoints: ['src/foo.ts'], )); - $html = $this->render(<<<'HTML' + $html = $this->view->render(<<<'HTML'