diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..75a203b --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b53305e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +**/node_modules +**/.next +**/build +**/.venv +**/__pycache__ +**/dist +**/coverage +**/.test-data +.runtime +.cache +.tools +**/.env* +**/*.db* +**/*.log diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8f6e20a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: Build and verify +on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.ref || github.ref_name }} + cancel-in-progress: true +permissions: + contents: read +jobs: + native-and-app: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + env: + OPENBLAS_NUM_THREADS: '2' + OMP_NUM_THREADS: '2' + NEXT_TELEMETRY_DISABLED: '1' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: npm run setup + - run: npm run build + - run: npm test + - run: npm run test:ui + - run: npm audit --prefix api --omit=dev --audit-level=high + - run: npm audit --prefix frontend --omit=dev --audit-level=high + docker: + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + COMPOSE_PARALLEL_LIMIT: '1' + steps: + - uses: actions/checkout@v4 + - run: docker compose -p verify -f docker/docker-compose.yml build + - run: docker compose -p verify -f docker/docker-compose.yml up -d --wait --wait-timeout 120 + - run: node scripts/docker-smoke.mjs + - if: always() + run: docker compose -p verify -f docker/docker-compose.yml logs --tail 50 + - if: always() + run: docker compose -p verify -f docker/docker-compose.yml down --volumes diff --git a/.gitignore b/.gitignore index 85b83b8..af81109 100644 --- a/.gitignore +++ b/.gitignore @@ -94,4 +94,9 @@ AGENTS.md start-local.bat stop-local.bat -scripts/ + +# Runtime data and local verification artifacts +.runtime/ +.tools/ +*.db-wal +*.db-shm diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4926aa8 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{ "singleQuote": true, "trailingComma": "all", "printWidth": 100 } diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1286064 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.18) +project(log_pipeline VERSION 2.0 LANGUAGES C CXX) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5) +endif() +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +foreach(config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${config} ${CMAKE_BINARY_DIR}/bin) +endforeach() +include(FetchContent) +find_package(Threads REQUIRED) +FetchContent_Declare(json URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.tar.gz) +FetchContent_Declare(httplib URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.18.7.tar.gz SOURCE_SUBDIR unused) +FetchContent_Declare(sha2 URL https://github.com/okdshin/PicoSHA2/archive/161cb3fc4170fa7a3eca9e582cebd27cc4d1fe29.tar.gz SOURCE_SUBDIR unused) +FetchContent_MakeAvailable(json httplib sha2) +if(NOT LOG_SQLITE_DIR) + FetchContent_Declare(sqlite_source URL https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.3.tar.gz SOURCE_SUBDIR unused) + FetchContent_MakeAvailable(sqlite_source) + set(LOG_SQLITE_DIR ${sqlite_source_SOURCE_DIR}/sqlite3) +endif() +add_library(log_sqlite STATIC ${LOG_SQLITE_DIR}/sqlite3.c) +target_include_directories(log_sqlite PUBLIC ${LOG_SQLITE_DIR}) +target_compile_definitions(log_sqlite PRIVATE SQLITE_THREADSAFE=1 SQLITE_DEFAULT_WAL_SYNCHRONOUS=2) +target_link_libraries(log_sqlite PUBLIC Threads::Threads ${CMAKE_DL_LIBS}) +add_executable(log_queue queue/src/main.cpp queue/src/sqlite_queue.cpp) +add_executable(log_agent agent/src/main.cpp agent/src/log_parser.cpp) +target_include_directories(log_agent PRIVATE agent/include) +foreach(target log_queue log_agent) + target_include_directories(${target} PRIVATE native/include ${httplib_SOURCE_DIR} ${sha2_SOURCE_DIR}) + target_link_libraries(${target} PRIVATE nlohmann_json::nlohmann_json Threads::Threads) + if(WIN32) + target_link_libraries(${target} PRIVATE ws2_32) + target_compile_definitions(${target} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) + endif() + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /EHsc /utf-8) + else() + target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic) + endif() +endforeach() +target_link_libraries(log_queue PRIVATE log_sqlite) diff --git a/README.md b/README.md index b9f20ed..b49f34c 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,133 @@ -# Log Aggregation System +# Log aggregation and investigation -A distributed log aggregation system similar to Splunk/Datadog, built with C++, TypeScript, and Python. +A local log pipeline with a C++20 file agent and durable SQLite queue, a Node.js indexing worker and API, Python anomaly detection, and an interactive Next.js workspace. -## Features +## Run locally -- **High-throughput log collection** - C++ agent tails log files and batches entries -- **Custom message queue** - Persistent queue for reliable log delivery -- **Full-text search** - SQLite FTS5 for fast log queries -- **ML anomaly detection** - Python service with Isolation Forest -- **Real-time dashboard** - Next.js frontend with observability UI +Install Node.js 22 LTS, Python 3.12, and a C++20 compiler. On Linux, install the compiler toolchain (`build-essential` on Debian/Ubuntu). On Windows, install Visual Studio 2022 Build Tools with **Desktop development with C++** and the Windows SDK. Python and Node must be on PATH. -## Tech Stack +From the repository root, run once: -| Component | Technology | -|-----------|------------| -| Log Agent | C++17, Winsock | -| Message Queue | C++17, Thread Pool | -| API Server | TypeScript, Node.js, Express | -| Storage | SQLite + FTS5 | -| Anomaly Detection | Python, FastAPI, scikit-learn | -| Frontend | Next.js 15, React, Tailwind CSS | -| Deployment | Docker Compose | +```sh +npm run setup +``` -## Quick Start +Then start the simulation and every service together: -### Prerequisites +```sh +npm run dev +``` -- C++17 compiler (MSVC on Windows) -- CMake 3.16+ -- Node.js 20+ -- Python 3.11+ -- Docker Desktop (optional, for containerized deployment) +Open **http://localhost:3001**. The command builds the native executables and API, starts the queue, agent, indexer, detector, and UI, then generates 5,000 logs/sec. Ctrl+C stops the processes it owns. Startup refuses occupied ports; it does not terminate unrelated processes. The first native build downloads pinned dependency releases and takes longer. -### Option 1: Windows Batch Scripts (Recommended) +A generator write failure stops the owned stack and exits with an error. No BAT startup sequence is required. Earlier component launchers and source files are retained for reference; the root commands use the current pipeline. -**Full local development stack:** -```cmd -test-local.bat +```sh +npm run dev -- --rate=20000 ``` -Starts API, anomaly service, and frontend locally with health checks. -**Start services individually:** -```cmd -start-local.bat # Start all services in background -stop-local.bat # Stop all running services -``` +Runtime data, logs, checkpoints, and the local anomaly model live in `.runtime/demo`. Use a new `DATA_DIR` for an isolated run. `API_PORT`, `QUEUE_PORT`, `ANOMALY_PORT`, and `FRONTEND_PORT` override ports; set them using your shell's environment syntax. The defaults are 3000, 8081, 5000, and 3001 respectively. API, queue, and detector bind to loopback in the local launcher. The development UI listens on all interfaces for VM preview access. -**Full Docker stack:** -```cmd -test-docker.bat -``` -Builds and starts all containers with health checks. +## Workspace -### Option 2: Docker Compose +- **Explore:** literal phrase search, severity/source/time filters, cursor pagination, original event data, parsed fields, and links to individual historical events. +- **Overview:** measured acceptance/indexing rates, durable backlog, observed API search latency, source shares, component health, and open alerts. +- **Alerts:** a three-pane investigation layout with an alert queue, detection evidence, and related logs. Acknowledgment persists in SQLite. New alerts do not replace the alert being inspected. -```bash -cd docker -docker-compose up --build -``` +Charts use actual observations. The renderer moves smoothly between samples without inventing extra measurements, preserves gaps, and supports exact sample inspection, series toggles, and reduced motion. Search percentiles are a rolling sample of observed search requests, not a promised latency. History accumulates while the workspace is open and persists across its navigation links. -Services: -- Frontend: http://localhost:3001 -- API: http://localhost:3000 -- Anomaly: http://localhost:8000 -- Queue: http://localhost:8080 +Each animated pipeline bead represents **10,000 logs** crossing that stage, using durable counters. The view does not replay the initial database total as new traffic. Pausing freezes frontend polling and motion; ingestion and detection continue in the background. Alert-related log rows are a bounded sample from the evidence window, separate from the detector's full error-event count. -### Option 3: Run Services Individually +## Delivery and storage -**C++ Agent:** -```bash -cd agent && mkdir -p build && cd build && cmake .. && cmake --build . --config Debug -./Debug/log_agent.exe --file test.log --queue-host localhost --queue-port 8080 +```text +NDJSON / text file → C++20 agent → SQLite durable queue + ↓ leased batches + Node indexing worker + ↓ committed rows + SQLite WAL + FTS5 + ↙ ↘ + REST API Python detectors + ↓ + Explore / Overview / Alerts ``` -**C++ Queue:** -```bash -cd queue && mkdir -p build && cd build && cmake .. && cmake --build . --config Debug -./Debug/log_queue.exe --port 8080 --db ./data/queue.db -``` +The agent coalesces input into bounded batches and preserves original event payloads. Checkpoints are replaced atomically after durable queue acceptance. File identity and byte offsets supply stable event IDs when the input has none. Truncation and file replacement are detected; incomplete trailing lines wait for a newline. Overlong or permanently rejected input fails explicitly without advancing its checkpoint. -**TypeScript API:** -```bash -cd api && npm install && npm run dev -``` +The queue uses WAL with `synchronous=FULL`, transactional capacity checks, payload-bound batch receipts, expiring leases, and token-fenced acknowledgments. An expired consumer cannot acknowledge a new consumer's lease. Capacity pressure returns HTTP 429 and the agent retries while keeping the source position unchanged. -**Python Anomaly Detection:** -```bash -cd anomaly -python -m venv venv -venv\Scripts\activate -pip install -r requirements.txt -python -m src.main -``` +The indexing worker commits valid rows and quarantined invalid records before acknowledging. Stable IDs make re-delivery idempotent. The delivery contract is **at least once**, with duplicate suppression in the index. Malformed records are retained in `rejected_logs`; they do not silently disappear or prevent valid records from advancing. Receipts and indexed logs currently have no automatic retention policy, so storage grows with the run. -**Next.js Frontend:** -```bash -cd frontend && npm install && npm run dev -``` +The API stays on a separate connection and worker thread from ingestion. FTS5 phrase search uses a bounded recent slice with a complete global fallback. Cursor pages are ordered by timestamp and rowid and bound to the search filters. The UI freezes older pages while paging so new traffic does not repeatedly shift them. -## Development Scripts +Python tracks incremental input by insertion order rather than UUID sort order. Error-rate evidence is computed over the complete rolling database window; volume comparisons use historical hourly aggregates. Source-distribution and model detectors operate on sampled polling batches. Statistical alerts describe observations and thresholds, not verified incidents. -| Script | Purpose | -|--------|---------| -| `test-local.bat` | Start local dev stack with health checks | -| `test-docker.bat` | Start Docker stack with health checks | -| `start-local.bat` | Start services in background | -| `stop-local.bat` | Stop all running services | -| `api/scripts/seed.ts` | Generate dummy log/alert data | +This is a local demonstration without authentication or multi-tenant isolation. Keep the service ports private when running outside your machine. -## Testing - -### API Tests -```bash -cd api -npm test # All tests -npm test -- logs.test.ts # Single file -npm test -- --testNamePattern="logs" # Pattern match -``` +## Verification -### Anomaly Tests -```bash -cd anomaly -venv\Scripts\activate -pytest # All tests -pytest -v # Verbose output +```sh +npm run build # native Release, API TypeScript, production UI +npm test # API, frontend, Python, cross-process recovery tests +npm run test:integration # queue/agent/indexer crash and replay scenarios +npm run test:ui # real npm run dev startup, proxies, workflow API, shutdown +npm run bench -- --count=100000 --rate=20000 --seconds=15 ``` -### C++ Integration Test -```bash -cd queue/build/Debug -mkdir -p data -./log_queue.exe --port 8080 --db ./data/queue.db & -curl -X POST http://localhost:8080/api/logs/batch \ - -H "Content-Type: application/json" \ - -d "[\"log1\",\"log2\",\"log3\"]" -curl http://localhost:8080/api/logs/pending -``` +The integration suite exercises source replay, fragmented HTTP bodies, payload conflicts, queue crashes, lease expiry and stale acknowledgments, consumer crashes, source replacement, incomplete and oversized lines, poison records, capacity backpressure, cursor uniqueness, and SQLite integrity. Tests use isolated runtime directories and ports. -## API Endpoints +GitHub Actions builds and runs the same entrypoints on Ubuntu and Windows, plus a Linux Docker deployment. A Linux VM run alone does not establish Windows compatibility; check the workflow results for the branch. -### Logs +Benchmark output is written to `benchmarks/latest.json`, with per-run artifacts under `.runtime/bench-*`. Results include HTTP search p50/p95, bulk startup time, paced delivery including drain time, queue observations, environment, and durability settings. The benchmark excludes the UI and anomaly detector. Performance depends on disk, CPU, workload, phrase frequency, and concurrent activity; use recorded measurements rather than treating the simulator's offered rate as achieved throughput. -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/logs` | Ingest single log | -| POST | `/api/logs/batch` | Ingest log batch | -| GET | `/api/logs` | Search logs with filters | -| GET | `/api/logs/stats` | Get log statistics | +### Recorded VM benchmarks -**Query Parameters (GET /api/logs):** -- `q` - Full-text search query -- `level` - Filter by log level (ERROR, WARN, INFO, DEBUG) -- `source` - Filter by source -- `start` / `end` - Time range (ISO 8601) -- `limit` / `offset` - Pagination +The latest post-review run measured **11,971 indexed logs/sec** for 609,191 logs generated over 33.9 seconds, including the final drain (50.9 seconds total). Both SQLite stores used WAL with `synchronous=FULL`; the UI and detector were excluded. -### Alerts +Searches returned 50 rows over HTTP on a 100K-log corpus, with five warmups and 30 timed requests per case: -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/alerts` | List alerts | -| GET | `/api/alerts/:id` | Get single alert | -| PUT | `/api/alerts/:id/acknowledge` | Acknowledge alert | +| Query | Median | p95 | +| ---------------- | -----: | ------: | +| literal phrase | 6.6 ms | 18.2 ms | +| common phrase | 8.4 ms | 12.1 ms | +| source and level | 3.2 ms | 10.3 ms | -### Anomaly Detection +The separate 100K bulk run took 5.8 seconds including startup (17.2K logs/sec). An earlier revision measured 22.4K logs/sec; its report is preserved in `benchmarks/before-normalization.json`. The initial normalization run is also retained in `benchmarks/normalization-initial.json`. -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/health` | Service health check | -| GET | `/status` | Detector status and metrics | -| POST | `/detect` | Trigger manual detection | +These short VM runs do not isolate normalization overhead from VM load and storage variability, and the earlier throughput is not a guarantee for the current revision. All reports retain their tested source commits and observations. Use `benchmarks/latest.json` for the latest measurement. -## Architecture +## Docker -``` -┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ -│ Log Agent │────▶│ C++ Queue │────▶│ Node.js API │ -│ (C++) │ │ (Custom) │ │ (TypeScript) │ -└─────────────┘ └──────────────┘ └─────────────────┘ - │ - ┌─────────────────────┼─────────────────────┐ - ▼ ▼ ▼ - ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ - │ SQLite │ │ Python │ │ Frontend │ - │ + FTS5 │ │ Anomaly │ │ (Next.js) │ - └─────────────┘ └─────────────┘ └─────────────┘ +```sh +docker compose -f docker/docker-compose.yml build +docker compose -f docker/docker-compose.yml up -d --wait ``` -**Data Flow:** -1. Agent tails log files and sends batches to Queue -2. Queue persists logs and exposes HTTP API -3. API consumer polls Queue and writes to SQLite with FTS5 -4. Anomaly service reads logs, detects anomalies, writes alerts -5. Frontend displays logs, stats, and alerts in real-time +Run `node scripts/docker-smoke.mjs` against the started stack to verify production pages, live file ingestion, FTS filters, and the detector proxy. `SIM_RATE` controls container simulation rate (default 5,000 logs/sec). -## Environment Variables +Open http://localhost:3001. The stack includes the simulator and uses named volumes for events and databases. Only the UI port is published, on loopback. Images run the services without root privileges. -### API (`api/.env`) -``` -PORT=3000 -QUEUE_HOST=localhost -QUEUE_PORT=8080 -DB_PATH=./data/logs.db -POLL_INTERVAL_MS=1000 +```sh +docker compose -f docker/docker-compose.yml down ``` -### Anomaly (`anomaly/.env`) -``` -API_BASE_URL=http://localhost:3000 -DB_PATH=./data/logs.db -POLL_INTERVAL_SECONDS=30 -``` +Add `--volumes` only when you want to discard this stack's stored data. Container builds support an optional BuildKit secret named `ca_bundle` for environments with a custom certificate authority; it is mounted during downloads and is not copied into the images. -### Frontend (`frontend/.env.local`) -``` -NEXT_PUBLIC_API_URL=http://localhost:3000 -``` - -## Linting & Formatting - -```bash -# API -cd api && npm run lint +## API -# Frontend -cd frontend && npm run lint - -# Python -cd anomaly && black src/ && flake8 src/ -``` +The frontend proxies API calls, queue metrics, and detector status to their internal services. -## License +| Endpoint | Purpose | +| ---------------------------------- | -------------------------------------------------------------------------------- | +| `POST /api/logs` | Insert a single event directly | +| `GET /api/logs/page` | Cursor search: `q`, `level`, `source`, `startDate`, `endDate`, `limit`, `cursor` | +| `GET /api/logs` | Compatible offset search with an exact total | +| `GET /api/logs/:id` | Retrieve one event | +| `GET /api/logs/stats` | Source and severity statistics | +| `GET /api/alerts` | Filter alerts by acknowledgment, severity, source, and text | +| `POST /api/alerts/:id/acknowledge` | Persist acknowledgment | +| `GET /api/alerts/stats` | Aggregate and open severity counts | +| `GET /metrics` | Indexer, queue, and observed search metrics | +| `GET /health` | API readiness | -MIT +Native queue endpoints are `POST /api/logs/batch`, `GET /api/logs/pending`, `POST /api/logs/ack`, `GET /api/queue/stats`, and `GET /health`. They form an internal leased-delivery protocol rather than the browser API. diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 70447f9..456585d 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -1,33 +1,3 @@ -cmake_minimum_required(VERSION 3.16) -project(log_agent VERSION 1.0.0 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Fetch nlohmann/json library -include(FetchContent) -FetchContent_Declare( - json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(json) - -# Build as executable with all components -add_executable(log_agent - src/main.cpp - src/file_tailer.cpp - src/log_parser.cpp - src/batcher.cpp - src/queue_client.cpp -) - -target_include_directories(log_agent PRIVATE ${CMAKE_SOURCE_DIR}/include) - -target_link_libraries(log_agent PRIVATE nlohmann_json::nlohmann_json) - -if(WIN32) - target_link_libraries(log_agent PRIVATE ws2_32) -endif() +cmake_minimum_required(VERSION 3.18) +project(log_pipeline_entry LANGUAGES C CXX) +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/.." "${CMAKE_CURRENT_BINARY_DIR}/workspace") diff --git a/agent/include/log_parser.hpp b/agent/include/log_parser.hpp index 851ac6b..8bdc12f 100644 --- a/agent/include/log_parser.hpp +++ b/agent/include/log_parser.hpp @@ -1,8 +1,9 @@ #pragma once -#include -#include #include +#include +#include +#include #include namespace log_agent { @@ -16,18 +17,19 @@ struct LogEntry { }; class LogParser { -public: + public: LogParser() = default; - - std::optional parse(const std::string& line) const; - -private: - std::optional parseJson(const std::string& line) const; - std::optional parsePlainText(const std::string& line) const; - - static std::string normalizeLevel(const std::string& level); + + std::optional parse(const std::string &line) const; + void normalizeJson(nlohmann::json &entry) const; + + private: + std::optional parseJson(const std::string &line) const; + std::optional parsePlainText(const std::string &line) const; + + static std::string normalizeLevel(const std::string &level); static std::string detectLevel(std::string_view line); - static std::chrono::system_clock::time_point parseTimestamp(const std::string& ts); + static std::chrono::system_clock::time_point parseTimestamp(const std::string &ts); }; -} // namespace log_agent +} // namespace log_agent diff --git a/agent/src/log_parser.cpp b/agent/src/log_parser.cpp index cf5c771..c64c315 100644 --- a/agent/src/log_parser.cpp +++ b/agent/src/log_parser.cpp @@ -1,165 +1,150 @@ #include "log_parser.hpp" -#include -#include #include #include -#include +#include +#include #include +#include +#include #include -#include namespace log_agent { namespace { - // Static level map - created once - const std::unordered_map& getLevelMap() { - static const std::unordered_map kLevelMap = { - {"error", "error"}, {"err", "error"}, {"fatal", "error"}, - {"critical", "error"}, {"severe", "error"}, - {"warn", "warn"}, {"warning", "warn"}, - {"info", "info"}, {"information", "info"}, {"notice", "info"}, - {"debug", "debug"}, {"trace", "debug"}, {"verbose", "debug"} - }; - return kLevelMap; - } - - // Case-insensitive search helper - bool containsIgnoreCase(std::string_view haystack, std::string_view needle) { - if (needle.size() > haystack.size()) return false; - auto it = std::search( - haystack.begin(), haystack.end(), - needle.begin(), needle.end(), - [](unsigned char ch1, unsigned char ch2) { - return std::tolower(ch1) == std::tolower(ch2); - } - ); - return it != haystack.end(); - } +// Static level map - created once +const std::unordered_map &getLevelMap() { + static const std::unordered_map kLevelMap = { + {"error", "error"}, {"err", "error"}, {"fatal", "error"}, {"critical", "error"}, + {"severe", "error"}, {"warn", "warn"}, {"warning", "warn"}, {"info", "info"}, + {"information", "info"}, {"notice", "info"}, {"debug", "debug"}, {"trace", "debug"}, + {"verbose", "debug"}}; + return kLevelMap; } -std::optional LogParser::parse(const std::string& line) const { +// Case-insensitive search helper +bool containsIgnoreCase(std::string_view haystack, std::string_view needle) { + if (needle.size() > haystack.size()) + return false; + auto it = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), + [](unsigned char ch1, unsigned char ch2) { + return std::tolower(ch1) == std::tolower(ch2); + }); + return it != haystack.end(); +} +} // namespace + +std::optional LogParser::parse(const std::string &line) const { // Skip empty lines - if (line.empty() || std::all_of(line.begin(), line.end(), [](unsigned char c) { return std::isspace(c); })) { + if (line.empty() || + std::all_of(line.begin(), line.end(), [](unsigned char c) { return std::isspace(c); })) { return std::nullopt; } - + // Try JSON first if (auto jsonResult = parseJson(line)) { return jsonResult; } - + // Fall back to plain text return parsePlainText(line); } -std::optional LogParser::parseJson(const std::string& line) const { - try { - // Parse with nlohmann/json - handles all edge cases - auto j = nlohmann::json::parse(line); - - LogEntry entry; - entry.raw = line; - - // Extract level (supports multiple field names and types) - std::string levelStr; - if (j.contains("level") && !j["level"].is_null()) { - if (j["level"].is_string()) { - levelStr = j["level"].get(); - } else if (j["level"].is_number_integer()) { - // Map numeric levels: 0=debug, 1=info, 2=warn, 3=error - int levelNum = j["level"].get(); - switch (levelNum) { - case 0: levelStr = "debug"; break; - case 1: levelStr = "info"; break; - case 2: levelStr = "warn"; break; - case 3: levelStr = "error"; break; - default: levelStr = "info"; - } +void LogParser::normalizeJson(nlohmann::json &entry) const { + if (!entry.is_object()) + return; + const auto alias = [&](const char *canonical, std::initializer_list names) { + const auto value = entry.find(canonical); + if (value != entry.end() && !value->is_null()) + return; + for (const auto *name : names) { + const auto alternative = entry.find(name); + if (alternative != entry.end() && !alternative->is_null()) { + entry[canonical] = *alternative; + return; } - } else if (j.contains("severity") && !j["severity"].is_null()) { - levelStr = j["severity"].get(); - } else if (j.contains("log_level") && !j["log_level"].is_null()) { - levelStr = j["log_level"].get(); - } else { - return std::nullopt; // Required field missing - } - entry.level = normalizeLevel(levelStr); - - // Extract message (handle nulls) - if (j.contains("message") && !j["message"].is_null()) { - entry.message = j["message"].get(); - } else if (j.contains("msg") && !j["msg"].is_null()) { - entry.message = j["msg"].get(); - } else if (j.contains("text") && !j["text"].is_null()) { - entry.message = j["text"].get(); - } else { - return std::nullopt; // Required field missing } - - // Optional: source - if (j.contains("source") && !j["source"].is_null()) { - entry.source = j["source"].get(); - } else if (j.contains("service") && !j["service"].is_null()) { - entry.source = j["service"].get(); - } else if (j.contains("app") && !j["app"].is_null()) { - entry.source = j["app"].get(); - } else { - entry.source = "unknown"; + }; + alias("message", {"msg", "text"}); + alias("level", {"severity", "log_level"}); + alias("source", {"service", "app"}); + alias("timestamp", {"time", "ts"}); + if (auto position = entry.find("level"); position != entry.end()) { + auto &level = *position; + if (level.is_number_integer()) { + const auto n = level.get(); + level = n == 0 ? "debug" : n == 2 ? "warn" : n == 3 ? "error" : "info"; + } else if (level.is_string()) { + const auto &text = level.get_ref(); + if (text != "info" && text != "error" && text != "warn" && text != "debug") + level = normalizeLevel(text); } - - // Optional: timestamp from log (if present) - if (j.contains("timestamp") && !j["timestamp"].is_null()) { - if (j["timestamp"].is_number()) { - // Unix timestamp (milliseconds or seconds) - auto ts = j["timestamp"].get(); - if (ts > 1000000000000LL) { // Milliseconds (13+ digits) - entry.timestamp = std::chrono::system_clock::time_point( - std::chrono::milliseconds(ts)); - } else { // Seconds (10 digits) - entry.timestamp = std::chrono::system_clock::time_point( - std::chrono::seconds(ts)); - } - } else if (j["timestamp"].is_string()) { - entry.timestamp = parseTimestamp(j["timestamp"].get()); - } else { - entry.timestamp = std::chrono::system_clock::now(); + } + if (auto position = entry.find("timestamp"); position != entry.end()) { + auto ×tamp = *position; + if (timestamp.is_number()) { + const double value = timestamp.get(); + // The worker consumes milliseconds. Keep ISO strings intact so their + // fractional seconds and UTC offsets survive normalization. + if (value > -1000000000000.0 && value < 1000000000000.0) + timestamp = value * 1000.0; + } else if (timestamp.is_string()) { + const auto &text = timestamp.get_ref(); + if (!text.empty() && std::all_of(text.begin(), text.end(), + [](unsigned char c) { return std::isdigit(c); })) { + timestamp = std::chrono::duration_cast( + parseTimestamp(text).time_since_epoch()) + .count(); } - } else if (j.contains("time") && !j["time"].is_null()) { - entry.timestamp = parseTimestamp(j["time"].get()); - } else if (j.contains("ts") && !j["ts"].is_null()) { - entry.timestamp = parseTimestamp(j["ts"].get()); - } else { - entry.timestamp = std::chrono::system_clock::now(); } - + } +} + +std::optional LogParser::parseJson(const std::string &line) const { + try { + auto j = nlohmann::json::parse(line); + normalizeJson(j); + if (!j.is_object() || !j.contains("message") || !j.contains("level")) + return std::nullopt; + LogEntry entry; + entry.raw = line; + entry.message = j.at("message").get(); + entry.level = j.at("level").get(); + entry.source = j.contains("source") && !j["source"].is_null() + ? j["source"].get() + : "unknown"; + if (j.contains("timestamp") && j["timestamp"].is_number()) + entry.timestamp = std::chrono::system_clock::time_point( + std::chrono::milliseconds(j["timestamp"].get())); + else if (j.contains("timestamp") && j["timestamp"].is_string()) + entry.timestamp = parseTimestamp(j["timestamp"].get()); + else + entry.timestamp = std::chrono::system_clock::now(); return entry; - - } catch (const nlohmann::json::exception&) { - // Not valid JSON + } catch (const nlohmann::json::exception &) { return std::nullopt; } } -std::optional LogParser::parsePlainText(const std::string& line) const { +std::optional LogParser::parsePlainText(const std::string &line) const { LogEntry entry; entry.raw = line; entry.timestamp = std::chrono::system_clock::now(); entry.source = "plain_text"; - + // Case-insensitive level detection without copying entry.level = detectLevel(line); entry.message = line; - + return entry; } -std::string LogParser::normalizeLevel(const std::string& level) { +std::string LogParser::normalizeLevel(const std::string &level) { // Convert to lowercase for consistency std::string lower = level; - std::transform(lower.begin(), lower.end(), lower.begin(), + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); - - const auto& kLevelMap = getLevelMap(); + + const auto &kLevelMap = getLevelMap(); auto it = kLevelMap.find(lower); return (it != kLevelMap.end()) ? it->second : "info"; } @@ -187,13 +172,14 @@ std::string LogParser::detectLevel(std::string_view line) { if (containsIgnoreCase(line, "trace")) { return "debug"; } - + return "info"; } -std::chrono::system_clock::time_point LogParser::parseTimestamp(const std::string& ts) { +std::chrono::system_clock::time_point LogParser::parseTimestamp(const std::string &ts) { // Check for Unix timestamp (all digits) - if (!ts.empty() && std::all_of(ts.begin(), ts.end(), [](unsigned char c) { return std::isdigit(c); })) { + if (!ts.empty() && + std::all_of(ts.begin(), ts.end(), [](unsigned char c) { return std::isdigit(c); })) { // Validate length to prevent overflow/performance issues if (ts.length() > 20) { return std::chrono::system_clock::now(); @@ -202,62 +188,62 @@ std::chrono::system_clock::time_point LogParser::parseTimestamp(const std::strin long long timestamp = std::stoll(ts); if (ts.length() > 10) { // Milliseconds - return std::chrono::system_clock::time_point( - std::chrono::milliseconds(timestamp)); + return std::chrono::system_clock::time_point(std::chrono::milliseconds(timestamp)); } else { // Seconds - return std::chrono::system_clock::time_point( - std::chrono::seconds(timestamp)); + return std::chrono::system_clock::time_point(std::chrono::seconds(timestamp)); } } catch (...) { return std::chrono::system_clock::now(); } } - + // Try ISO8601 format: "2024-03-01T10:30:00Z" or "2024-03-01T10:30:00.000Z" std::tm tm = {}; std::istringstream ss(ts); - + // Parse main datetime (up to seconds) ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"); - + if (ss.fail()) { // Try space-separated format: "2024-03-01 10:30:00" std::istringstream ss2(ts); ss2 >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S"); - + if (ss2.fail()) { return std::chrono::system_clock::now(); } } - + // Convert to time_t (assumes UTC for Z suffix, local otherwise) // Note: timegm() is non-standard but widely available // For strict C++17 portability, we'd need to handle timezone manually time_t tt; - #ifdef _WIN32 - tt = _mkgmtime(&tm); // Windows UTC version - #else - tt = timegm(&tm); // Unix UTC version - #endif - +#ifdef _WIN32 + tt = _mkgmtime(&tm); // Windows UTC version +#else + tt = timegm(&tm); // Unix UTC version +#endif + if (tt == -1) { return std::chrono::system_clock::now(); } - + auto tp = std::chrono::system_clock::from_time_t(tt); - + // Handle milliseconds if present (.123) if (ss.peek() == '.') { - ss.ignore(); // Skip '.' + ss.ignore(); // Skip '.' std::string msStr; - ss >> msStr; // Read the rest as string - + ss >> msStr; // Read the rest as string + if (!msStr.empty()) { // Pad or truncate to 3 digits (milliseconds) - while (msStr.length() < 3) msStr += '0'; - if (msStr.length() > 3) msStr = msStr.substr(0, 3); - + while (msStr.length() < 3) + msStr += '0'; + if (msStr.length() > 3) + msStr = msStr.substr(0, 3); + try { int ms = std::stoi(msStr); tp += std::chrono::milliseconds(ms); @@ -266,8 +252,8 @@ std::chrono::system_clock::time_point LogParser::parseTimestamp(const std::strin } } } - + return tp; } -} // namespace log_agent +} // namespace log_agent diff --git a/agent/src/main.cpp b/agent/src/main.cpp index d3ee74f..b443423 100644 --- a/agent/src/main.cpp +++ b/agent/src/main.cpp @@ -1,252 +1,174 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "batcher.hpp" -#include "file_tailer.hpp" +#include "../../native/include/common.hpp" #include "log_parser.hpp" -#include "queue_client.hpp" - -namespace { - // Signal-safe flag for shutdown - volatile sig_atomic_t g_shutdown_requested = 0; - - constexpr auto kPollInterval = std::chrono::milliseconds(100); - - void signalHandler(int) { - g_shutdown_requested = 1; - } - - void printUsage(const char* programName) { - std::cout << "Usage: " << programName << " [options]\n" - << "Options:\n" - << " --file Log file to tail (required)\n" - << " --queue-host Queue server host (default: localhost)\n" - << " --queue-port Queue server port (default: 8080)\n" - << " --batch-size Batch size (default: 100)\n" - << " --flush-interval Flush interval in ms (default: 1000)\n" - << " -h, --help Show this help message\n"; - } - - struct Config { - std::string log_file; - std::string queue_host = "localhost"; - int queue_port = 8080; - size_t batch_size = 100; - std::chrono::milliseconds flush_interval{1000}; - }; - - Config parseArgs(int argc, char* argv[]) { - Config config; - - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - - if (arg == "-h" || arg == "--help") { - printUsage(argv[0]); - exit(0); - } else if (arg == "--file") { - if (i + 1 >= argc || argv[i + 1][0] == '-') { - std::cerr << "Error: --file requires a value" << std::endl; - exit(1); - } - config.log_file = argv[++i]; - } else if (arg == "--queue-host") { - if (i + 1 >= argc || argv[i + 1][0] == '-') { - std::cerr << "Error: --queue-host requires a value" << std::endl; - exit(1); - } - config.queue_host = argv[++i]; - } else if (arg == "--queue-port") { - if (i + 1 >= argc || argv[i + 1][0] == '-') { - std::cerr << "Error: --queue-port requires a value" << std::endl; - exit(1); - } - try { - config.queue_port = std::stoi(argv[++i]); - if (config.queue_port < 1 || config.queue_port > 65535) { - std::cerr << "Error: Port must be between 1 and 65535" << std::endl; - exit(1); - } - } catch (const std::exception&) { - std::cerr << "Error: Invalid port number: " << argv[i] << std::endl; - exit(1); - } - } else if (arg == "--batch-size") { - if (i + 1 >= argc || argv[i + 1][0] == '-') { - std::cerr << "Error: --batch-size requires a value" << std::endl; - exit(1); - } - try { - config.batch_size = std::stoul(argv[++i]); - } catch (const std::exception&) { - std::cerr << "Error: Invalid batch size: " << argv[i] << std::endl; - exit(1); - } - } else if (arg == "--flush-interval") { - if (i + 1 >= argc || argv[i + 1][0] == '-') { - std::cerr << "Error: --flush-interval requires a value" << std::endl; - exit(1); - } - try { - config.flush_interval = std::chrono::milliseconds(std::stoul(argv[++i])); - } catch (const std::exception&) { - std::cerr << "Error: Invalid flush interval: " << argv[i] << std::endl; - exit(1); - } - } else { - std::cerr << "Unknown option: " << arg << std::endl; - printUsage(argv[0]); - exit(1); - } - } - - return config; - } - - // Thread-safe time formatting - std::string formatTimestamp(std::chrono::system_clock::time_point tp) { - auto time_t = std::chrono::system_clock::to_time_t(tp); - char time_str[100]; - - #ifdef _WIN32 - std::tm time_info; - localtime_s(&time_info, &time_t); - std::strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%S", &time_info); - #else - std::tm time_info; - localtime_r(&time_t, &time_info); - std::strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%S", &time_info); - #endif - - return std::string(time_str); - } +#include +#include +using namespace pipeline; +std::string source_key(const std::filesystem::path &file) { +#ifdef _WIN32 + HANDLE handle = CreateFileW(file.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) + throw std::runtime_error("Cannot open source"); + BY_HANDLE_FILE_INFORMATION info{}; + bool ok = GetFileInformationByHandle(handle, &info); + CloseHandle(handle); + if (!ok) + throw std::runtime_error("Cannot identify source"); + return fingerprint(std::filesystem::absolute(file).generic_string() + ":" + + std::to_string(info.dwVolumeSerialNumber) + ":" + + std::to_string(info.nFileIndexHigh) + ":" + + std::to_string(info.nFileIndexLow)); +#else + struct stat info{}; + if (::stat(file.c_str(), &info) != 0) + throw std::runtime_error("Cannot stat source"); +#endif +#ifndef _WIN32 + return fingerprint(std::filesystem::absolute(file).generic_string() + ":" + + std::to_string(info.st_dev) + ":" + std::to_string(info.st_ino)); +#endif } - -int main(int argc, char* argv[]) { +int main(int argc, char **argv) { try { - // Parse command line arguments - Config config = parseArgs(argc, argv); - - if (config.log_file.empty()) { - std::cerr << "Error: --file is required" << std::endl; - printUsage(argv[0]); - return 1; + signals(); + std::filesystem::path file = option(argc, argv, "--file", "data/events.ndjson"); + std::filesystem::path checkpoint = + option(argc, argv, "--checkpoint", file.string() + ".checkpoint.json"); + auto port = number(option(argc, argv, "--queue-port", "8081"), 1, 65535); + auto batch_size = number(option(argc, argv, "--batch-size", "2048"), 1, 4096); + httplib::Client client(option(argc, argv, "--queue-host", "127.0.0.1"), port); + client.set_connection_timeout(1, 0); + client.set_read_timeout(5, 0); + client.set_write_timeout(5, 0); + uint64_t offset = 0, generation = 0; + std::string key; + if (std::filesystem::exists(checkpoint)) { + std::ifstream in(checkpoint); + auto state = json::parse(in); + offset = state.at("offset"); + key = state.at("source"); + generation = state.value("generation", 0ULL); } - - std::cout << "Log Agent Starting..." << std::endl; - std::cout << " Log file: " << config.log_file << std::endl; - std::cout << " Queue: " << config.queue_host << ":" << config.queue_port << std::endl; - std::cout << " Batch size: " << config.batch_size << std::endl; - std::cout << " Flush interval: " << config.flush_interval.count() << "ms" << std::endl; - - // Set up signal handlers - if (std::signal(SIGINT, signalHandler) == SIG_ERR) { - std::cerr << "Failed to install SIGINT handler" << std::endl; - return 1; - } - if (std::signal(SIGTERM, signalHandler) == SIG_ERR) { - std::cerr << "Failed to install SIGTERM handler" << std::endl; - return 1; - } - - // Create queue client - log_agent::QueueClient queue_client(config.queue_host, config.queue_port); - - // Create batcher with callback to send to queue - // Note: Lambda captures by reference are safe because Batcher doesn't store - // the callback for async execution - it only calls it synchronously from its - // own internal thread or from flush() - std::unique_ptr batcher = std::make_unique( - config.batch_size, - config.flush_interval, - [&queue_client](const std::vector& batch) { - if (!batch.empty()) { - std::cout << "Sending batch of " << batch.size() << " logs" << std::endl; - if (!queue_client.send(batch)) { - std::cerr << "Failed to send batch to queue" << std::endl; + auto last_flush = std::chrono::steady_clock::now(); + while (!stopped) { + if (!std::filesystem::exists(file)) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + continue; + } + auto current_key = source_key(file); + auto size = std::filesystem::file_size(file); + if (current_key != key) { + key = current_key; + offset = 0; + generation = 0; + } + if (size < offset) { + offset = 0; + ++generation; + } + if (size == offset) { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + continue; + } + if (size - offset < 512 * 1024 && + std::chrono::steady_clock::now() - last_flush < std::chrono::milliseconds(100)) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + std::ifstream in(file, std::ios::binary); + in.seekg(static_cast(offset)); + std::string buffer( + static_cast(std::min(2 * 1024 * 1024, size - offset)), '\0'); + in.read(buffer.data(), static_cast(buffer.size())); + buffer.resize(static_cast(in.gcount())); + if (!in && !in.eof()) + throw std::runtime_error("Source read failed"); + std::string log_body = "["; + unsigned log_count = 0; + size_t cursor = 0; + uint64_t end = offset; + while (log_count < batch_size) { + auto newline = buffer.find('\n', cursor); + if (newline == std::string::npos) + break; + auto line = buffer.substr(cursor, newline - cursor); + if (line.size() > 1024 * 1024) + throw std::runtime_error("Source line exceeds 1 MiB; checkpoint unchanged"); + if (!line.empty() && line.back() == '\r') + line.pop_back(); + if (!line.empty()) { + auto entry = json::parse(line, nullptr, false); + if (!entry.is_object()) { + auto parsed = log_agent::LogParser{}.parse(line); + entry = {{"message", parsed ? parsed->message : line}, + {"level", parsed ? parsed->level : "info"}, + {"source", file.filename().string()}}; + if (parsed) + entry["timestamp"] = + std::chrono::duration_cast( + parsed->timestamp.time_since_epoch()) + .count(); } + log_agent::LogParser{}.normalizeJson(entry); + if (!entry.contains("message") || !entry["message"].is_string()) + entry["message"] = line; + if (!entry.contains("source")) + entry["source"] = file.filename().string(); + if (!entry.contains("id") || !entry["id"].is_string() || + entry["id"].get_ref().empty()) + entry["id"] = fingerprint(key + ":" + std::to_string(generation) + ":" + + std::to_string(offset + cursor) + ":" + line); + if (!entry.contains("raw")) + entry["raw"] = line; + auto encoded = entry.dump(); + if (log_count && log_body.size() + encoded.size() > 6 * 1024 * 1024) + break; + if (log_count) + log_body += ","; + log_body += encoded; + ++log_count; } + cursor = newline + 1; + end = offset + cursor; + } + if (end == offset) { + if (buffer.size() > 1024 * 1024) + throw std::runtime_error( + "Incomplete source line exceeds 1 MiB; checkpoint unchanged"); + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + continue; } - ); - - // Create log parser - log_agent::LogParser parser; - - // Create file tailer - // Note: Lambda captures by reference are safe because FileTailer only calls - // the callback synchronously from its internal thread - std::unique_ptr tailer = std::make_unique( - config.log_file, - [&parser, &batcher, &config](const std::string& line) { - try { - // Parse the log line - auto entry = parser.parse(line); - if (entry) { - // Convert to JSON and add to batcher - nlohmann::json json_entry; - json_entry["message"] = entry->message; - json_entry["level"] = entry->level; - json_entry["source"] = entry->source; - json_entry["timestamp"] = formatTimestamp(entry->timestamp); - - if (entry->raw) { - json_entry["raw"] = *entry->raw; - } - - batcher->add(json_entry.dump()); - } else { - // If parsing failed, send raw line - nlohmann::json json_entry; - json_entry["message"] = line; - json_entry["level"] = "unknown"; - json_entry["source"] = config.log_file; - json_entry["timestamp"] = formatTimestamp(std::chrono::system_clock::now()); - - batcher->add(json_entry.dump()); + if (log_count) { + log_body += "]"; + auto id = fingerprint(key + ":" + std::to_string(offset) + ":" + + std::to_string(end) + ":" + log_body); + auto payload = "{\"id\":" + json(id).dump() + ",\"logs\":" + log_body + "}"; + bool accepted = false; + while (!stopped) { + auto response = client.Post("/api/logs/batch", payload, "application/json"); + if (response && response->status >= 200 && response->status < 300) { + accepted = true; + break; } - } catch (const std::exception& e) { - std::cerr << "Error processing log line: " << e.what() << std::endl; + if (response && (response->status == 400 || response->status == 409 || + response->status == 413)) + throw std::runtime_error( + "Queue rejected source batch; checkpoint unchanged: " + response->body); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + if (!accepted) + break; } - ); - - // Start the tailer - tailer->start(); - - std::cout << "Agent is running. Press Ctrl+C to stop." << std::endl; - - // Main loop - wait for shutdown signal - while (!g_shutdown_requested) { - std::this_thread::sleep_for(kPollInterval); - } - - // Shutdown gracefully - std::cout << "\nShutting down..." << std::endl; - - // Stop the tailer (this will flush incomplete line) - tailer->stop(); - tailer.reset(); - - // Flush any remaining batch - if (batcher) { - batcher->flush(); - batcher.reset(); + atomic_write( + checkpoint, + json({{"offset", end}, {"source", key}, {"generation", generation}}).dump()); + offset = end; + last_flush = std::chrono::steady_clock::now(); } - - std::cout << "Agent stopped." << std::endl; return 0; - - } catch (const std::exception& e) { - std::cerr << "Fatal error: " << e.what() << std::endl; + } catch (const std::exception &error) { + std::cerr << error.what() << std::endl; return 1; } } diff --git a/anomaly/requirements.txt b/anomaly/requirements.txt index 9f8b2d8..d9c4945 100644 --- a/anomaly/requirements.txt +++ b/anomaly/requirements.txt @@ -1,4 +1,5 @@ -fastapi[standard]==0.115.0 +fastapi==0.162.0 +uvicorn==0.54.0 pydantic-settings==2.7.0 structlog==25.1.0 scikit-learn==1.6.0 @@ -6,7 +7,7 @@ numpy==2.2.0 joblib==1.4.0 httpx==0.28.0 -pytest==8.3.0 -pytest-asyncio==0.24.0 +pytest==9.2.0 +pytest-asyncio==1.4.1.post1 ruff==0.8.0 mypy==1.14.0 diff --git a/anomaly/src/db/database.py b/anomaly/src/db/database.py index 2bd4689..92659c9 100644 --- a/anomaly/src/db/database.py +++ b/anomaly/src/db/database.py @@ -44,14 +44,14 @@ def _enable_wal_mode_unlocked(self) -> None: def get_cursor(self) -> Generator[sqlite3.Cursor, None, None]: with self._lock: cursor = self.connection.cursor() - try: - yield cursor - finally: - cursor.close() + try: + yield cursor + finally: + cursor.close() def execute(self, query: str, params: tuple = ()) -> sqlite3.Cursor: with self._lock: - return self._connection.execute(query, params) + return self.connection.execute(query, params) def commit(self) -> None: with self._lock: diff --git a/anomaly/src/db/log_repository.py b/anomaly/src/db/log_repository.py index bb0d09d..db897b0 100644 --- a/anomaly/src/db/log_repository.py +++ b/anomaly/src/db/log_repository.py @@ -1,7 +1,7 @@ """Log repository for reading logs from SQLite database.""" from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta from typing import Any import structlog @@ -65,27 +65,22 @@ def fetch_logs_since( since_id: str | None, limit: int = 1000, ) -> list[LogEntry]: + position = 0 if since_id: - rows = self.db.execute( - """ - SELECT id, timestamp, level, source, message, raw - FROM logs - WHERE id > ? - ORDER BY id ASC - LIMIT ? - """, - (since_id, limit), - ).fetchall() - else: - rows = self.db.execute( - """ - SELECT id, timestamp, level, source, message, raw - FROM logs - ORDER BY id ASC - LIMIT ? - """, - (limit,), - ).fetchall() + row = self.db.execute( + "SELECT rowid AS position FROM logs WHERE id = ?", (since_id,) + ).fetchone() + if row: + position = row["position"] + else: + saved = self.db.execute( + "SELECT value FROM anomaly_state WHERE key = 'last_processed_rowid'" + ).fetchone() + position = int(saved["value"]) if saved else 0 + rows = self.db.execute( + "SELECT id, timestamp, level, source, message, raw FROM logs WHERE rowid > ? ORDER BY rowid ASC LIMIT ?", + (position, limit), + ).fetchall() return [LogEntry(**dict(row)) for row in rows] @@ -112,6 +107,28 @@ def fetch_recent_logs(self, hours: float = 1.0, limit: int = 10000) -> list[LogE since = datetime.utcnow() - timedelta(hours=hours) return self.fetch_logs_by_timerange(since, datetime.utcnow(), limit) + def latest_window_error(self, start: datetime, end: datetime) -> str | None: + row = self.db.execute( + "SELECT id FROM logs WHERE lower(level) = 'error' AND timestamp >= ? " + "AND timestamp <= ? ORDER BY timestamp DESC, rowid DESC LIMIT 1", + ( + start.astimezone(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z"), + end.astimezone(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z"), + ), + ).fetchone() + return row["id"] if row else None + + def window_counts(self, start: datetime, end: datetime) -> dict[str, int]: + def utc_text(value: datetime) -> str: + return value.astimezone(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z") + + rows = self.db.execute( + "SELECT lower(level) AS level, count(*) AS count FROM logs " + "WHERE timestamp >= ? AND timestamp <= ? GROUP BY lower(level)", + (utc_text(start), utc_text(end)), + ).fetchall() + return {row["level"]: row["count"] for row in rows} + def get_log_count_by_level(self, since: datetime) -> dict[str, int]: rows = self.db.execute( """ @@ -157,7 +174,9 @@ def get_last_processed_id(self) -> str | None: def set_last_processed_id(self, log_id: str | None) -> None: with self.db.get_cursor() as cursor: if log_id is None: - cursor.execute("DELETE FROM anomaly_state WHERE key = 'last_processed_id'") + cursor.execute( + "DELETE FROM anomaly_state WHERE key IN ('last_processed_id', 'last_processed_rowid')" + ) else: cursor.execute( """ @@ -166,8 +185,26 @@ def set_last_processed_id(self, log_id: str | None) -> None: """, (log_id, datetime.utcnow().isoformat()), ) + row = cursor.execute( + "SELECT rowid AS position FROM logs WHERE id = ?", (log_id,) + ).fetchone() + if row: + cursor.execute( + "INSERT OR REPLACE INTO anomaly_state VALUES ('last_processed_rowid', ?, ?)", + (str(row["position"]), datetime.utcnow().isoformat()), + ) self.db.commit() + def hourly_counts(self, hours: float) -> dict[str, int]: + end = datetime.now(UTC) + start = end.replace(minute=0, second=0, microsecond=0) - timedelta(hours=hours) + rows = self.db.execute( + "SELECT strftime('%Y-%m-%d %H:00', timestamp) AS hour, count(*) AS count " + "FROM logs WHERE timestamp >= ? AND timestamp <= ? GROUP BY hour", + (start.isoformat().replace("+00:00", "Z"), end.isoformat().replace("+00:00", "Z")), + ).fetchall() + return {row["hour"]: row["count"] for row in rows if row["hour"]} + def get_volume_stats(self, hours: float = 24.0) -> dict[str, Any]: since = datetime.utcnow() - timedelta(hours=hours) rows = self.db.execute( diff --git a/anomaly/src/detectors/statistical.py b/anomaly/src/detectors/statistical.py index 97e0966..f0d697e 100644 --- a/anomaly/src/detectors/statistical.py +++ b/anomaly/src/detectors/statistical.py @@ -38,13 +38,25 @@ def detect(self, logs: list[LogEntry]) -> list[AnomalyResult]: log_time = datetime.fromisoformat(ts) if log_time.tzinfo is None: log_time = log_time.replace(tzinfo=UTC) - if log_time >= cutoff: + if cutoff <= log_time <= datetime.now(UTC): total_recent += 1 if log.level.lower() == "error": recent_errors += 1 except (ValueError, AttributeError): continue + return self.detect_counts(recent_errors, total_recent, record_run=False) + + def detect_counts( + self, + recent_errors: int, + total_recent: int, + *, + record_run: bool = True, + window_end: datetime | None = None, + ) -> list[AnomalyResult]: + if record_run: + self._record_run() if total_recent == 0: return [] @@ -62,6 +74,7 @@ def detect(self, logs: list[LogEntry]) -> list[AnomalyResult]: "errors_per_minute": errors_per_minute, "threshold": self.error_rate_threshold, "window_minutes": self.window_minutes, + "window_end": (window_end or datetime.now(UTC)).isoformat(), }, ) self._record_alerts(1) @@ -102,17 +115,23 @@ def detect(self, logs: list[LogEntry]) -> list[AnomalyResult]: except (ValueError, AttributeError): continue - if len(hourly_counts) < 2: - return [] + return self.detect_hourly(hourly_counts, record_run=False) - counts = list(hourly_counts.values()) + def detect_hourly( + self, hourly_counts: dict[str, int], *, record_run: bool = True + ) -> list[AnomalyResult]: + if record_run: + self._record_run() + current_hour = datetime.now(UTC).strftime("%Y-%m-%d %H:00") + # Compare with completed hours; including the spike in its own baseline + # suppresses the score. Require real historical observations first. + counts = [count for hour, count in hourly_counts.items() if hour < current_hour] + if len(counts) < 2: + return [] mean_count = statistics.mean(counts) - std_count = statistics.stdev(counts) if len(counts) > 1 else 0 - + std_count = statistics.stdev(counts) if std_count == 0: return [] - - current_hour = datetime.now(UTC).strftime("%Y-%m-%d %H:00") current_count = hourly_counts.get(current_hour, 0) z_score = (current_count - mean_count) / std_count diff --git a/anomaly/src/main.py b/anomaly/src/main.py index 0df2174..14f2115 100644 --- a/anomaly/src/main.py +++ b/anomaly/src/main.py @@ -110,17 +110,6 @@ async def lifespan(app: FastAPI): detectors=detectors, ) - def signal_handler(signum, frame): - logger.info("shutdown_signal_received", signal=signum) - if worker: - worker.stop() - if db: - db.close() - sys.exit(0) - - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGINT, signal_handler) - worker.start() logger.info("service_started") diff --git a/anomaly/src/workers/polling_worker.py b/anomaly/src/workers/polling_worker.py index d998506..88aa235 100644 --- a/anomaly/src/workers/polling_worker.py +++ b/anomaly/src/workers/polling_worker.py @@ -1,7 +1,7 @@ """Background polling worker for anomaly detection.""" import threading -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from typing import Any import structlog @@ -10,6 +10,7 @@ from ..db.alert_repository import AlertRepository from ..db.log_repository import LogRepository from ..detectors.base import AnomalyResult, BaseDetector +from ..detectors.statistical import ErrorBurstDetector, VolumeSpikeDetector logger = structlog.get_logger() @@ -45,6 +46,7 @@ def start(self) -> None: logger.warning("worker_already_running") return + self._shutdown_event.clear() self._running_event.set() with self._state_lock: self.start_time = datetime.now(UTC) @@ -99,7 +101,20 @@ def _poll_and_detect(self) -> None: continue try: - anomalies = detector.detect(logs) + if isinstance(detector, ErrorBurstDetector): + end = datetime.now(UTC) + counts = self.log_repo.window_counts( + end - timedelta(minutes=detector.window_minutes), end + ) + anomalies = detector.detect_counts( + counts.get("error", 0), sum(counts.values()), window_end=end + ) + elif isinstance(detector, VolumeSpikeDetector): + anomalies = detector.detect_hourly( + self.log_repo.hourly_counts(detector.baseline_hours) + ) + else: + anomalies = detector.detect(logs) all_anomalies.extend(anomalies) except Exception as e: logger.error( @@ -122,8 +137,12 @@ def _poll_and_detect(self) -> None: ) def _create_alert(self, anomaly: AnomalyResult) -> None: + pattern = { + "error_burst": "High error rate detected:", + "volume_spike": "Log volume spike detected:", + }.get(anomaly.anomaly_type, anomaly.message[:50]) existing = self.alert_repo.find_recent_similar( - pattern=anomaly.message[:50], + pattern=pattern, severity=anomaly.severity, hours=self.settings.deduplication_window_hours, ) @@ -136,12 +155,25 @@ def _create_alert(self, anomaly: AnomalyResult) -> None: ) return + anchor = anomaly.log_id + if anchor == "system": + if anomaly.anomaly_type == "error_burst" and anomaly.details: + end = datetime.fromisoformat(anomaly.details["window_end"]) + anchor = self.log_repo.latest_window_error( + end - timedelta(minutes=anomaly.details["window_minutes"]), end + ) + else: + recent = self.log_repo.fetch_recent_logs(limit=1) + anchor = recent[0].id if recent else None + if not anchor: + return + try: self.alert_repo.create( - log_id=anomaly.log_id, + log_id=anchor, severity=anomaly.severity, message=anomaly.message, - details=anomaly.details, + details={**(anomaly.details or {}), "anomaly_type": anomaly.anomaly_type}, ) with self._state_lock: self.alerts_created += 1 diff --git a/anomaly/tests/test_detectors.py b/anomaly/tests/test_detectors.py index fd08280..dbdfa68 100644 --- a/anomaly/tests/test_detectors.py +++ b/anomaly/tests/test_detectors.py @@ -43,7 +43,14 @@ def test_detect_no_logs(self): def test_detect_insufficient_logs(self): detector = VolumeSpikeDetector() logs = [ - LogEntry(id="1", timestamp=datetime.utcnow().isoformat(), level="info", source="app", message="test", raw=None) + LogEntry( + id="1", + timestamp=datetime.utcnow().isoformat(), + level="info", + source="app", + message="test", + raw=None, + ) for _ in range(5) ] result = detector.detect(logs) @@ -65,7 +72,14 @@ def test_detect_no_logs(self): def test_detect_insufficient_logs(self): detector = SourceAnomalyDetector(min_logs_per_source=100) logs = [ - LogEntry(id="1", timestamp=datetime.utcnow().isoformat(), level="info", source="app", message="test", raw=None) + LogEntry( + id="1", + timestamp=datetime.utcnow().isoformat(), + level="info", + source="app", + message="test", + raw=None, + ) for _ in range(10) ] result = detector.detect(logs) @@ -85,18 +99,37 @@ def test_detect_source_anomaly(self): ) for i in range(90) ] - logs.extend([ - LogEntry( - id=f"log-other-{i}", - timestamp=now.isoformat(), - level="info", - source="other", - message="test", - raw=None, - ) - for i in range(10) - ]) + logs.extend( + [ + LogEntry( + id=f"log-other-{i}", + timestamp=now.isoformat(), + level="info", + source="other", + message="test", + raw=None, + ) + for i in range(10) + ] + ) result = detector.detect(logs) assert len(result) == 1 assert result[0].anomaly_type == "source_anomaly" assert result[0].severity == "low" + + +def test_volume_spike_compares_complete_historical_buckets(): + from datetime import UTC, timedelta + + now = datetime.now(UTC) + buckets = { + (now - timedelta(hours=i)).strftime("%Y-%m-%d %H:00"): count + for i, count in [(1, 100), (2, 110), (3, 90)] + } + buckets[now.strftime("%Y-%m-%d %H:00")] = 1000 + detector = VolumeSpikeDetector() + result = detector.detect_hourly(buckets) + assert len(result) == 1 + assert result[0].details["mean_count"] == 100 + assert result[0].details["current_count"] == 1000 + assert detector.detect_hourly({now.strftime("%Y-%m-%d %H:00"): 1000}) == [] diff --git a/anomaly/tests/test_repositories.py b/anomaly/tests/test_repositories.py index b0aec44..0e39acd 100644 --- a/anomaly/tests/test_repositories.py +++ b/anomaly/tests/test_repositories.py @@ -17,7 +17,14 @@ def test_insert_and_fetch_logs(self, log_repo: LogRepository): INSERT INTO logs (id, timestamp, level, source, message, raw) VALUES (?, ?, ?, ?, ?, ?) """, - (f"log-{i}", (now - timedelta(minutes=i)).isoformat(), "info", "app", f"Message {i}", None), + ( + f"log-{i}", + (now - timedelta(minutes=i)).isoformat(), + "info", + "app", + f"Message {i}", + None, + ), ) log_repo.db.commit() @@ -32,7 +39,14 @@ def test_fetch_logs_since_id(self, log_repo: LogRepository): INSERT INTO logs (id, timestamp, level, source, message, raw) VALUES (?, ?, ?, ?, ?, ?) """, - (f"log-{i}", (now - timedelta(minutes=i)).isoformat(), "info", "app", f"Message {i}", None), + ( + f"log-{i}", + (now - timedelta(minutes=i)).isoformat(), + "info", + "app", + f"Message {i}", + None, + ), ) log_repo.db.commit() @@ -58,7 +72,14 @@ def test_get_log_count_by_level(self, log_repo: LogRepository): INSERT INTO logs (id, timestamp, level, source, message, raw) VALUES (?, ?, ?, ?, ?, ?) """, - (f"log-{i}", (now - timedelta(minutes=i)).isoformat(), level, "app", f"Message {i}", None), + ( + f"log-{i}", + (now - timedelta(minutes=i)).isoformat(), + level, + "app", + f"Message {i}", + None, + ), ) log_repo.db.commit() @@ -159,3 +180,17 @@ def test_get_recent_alerts(self, alert_repo: AlertRepository): alerts = alert_repo.get_recent_alerts(hours=1.0, limit=3) assert len(alerts) == 3 + + +def test_incremental_cursor_follows_insertion_not_uuid_order(log_repo): + for key in ["z-first", "a-second", "m-third"]: + log_repo.db.execute( + "INSERT INTO logs VALUES (?, ?, 'info', 'app', 'event', NULL)", + (key, datetime.utcnow().isoformat()), + ) + log_repo.db.commit() + log_repo.set_last_processed_id("z-first") + assert [log.id for log in log_repo.fetch_logs_since("z-first")] == ["a-second", "m-third"] + log_repo.db.execute("DELETE FROM logs WHERE id = 'z-first'") + log_repo.db.commit() + assert [log.id for log in log_repo.fetch_logs_since("z-first")] == ["a-second", "m-third"] diff --git a/anomaly/tests/test_worker.py b/anomaly/tests/test_worker.py index c819e77..1a7cd17 100644 --- a/anomaly/tests/test_worker.py +++ b/anomaly/tests/test_worker.py @@ -33,7 +33,9 @@ def detect(self, logs: list[LogEntry]) -> list[AnomalyResult]: class TestPollingWorker: - def test_worker_stats(self, settings: Settings, log_repo: LogRepository, alert_repo: AlertRepository): + def test_worker_stats( + self, settings: Settings, log_repo: LogRepository, alert_repo: AlertRepository + ): detector = MockDetector() worker = PollingWorker( settings=settings, @@ -48,7 +50,9 @@ def test_worker_stats(self, settings: Settings, log_repo: LogRepository, alert_r assert stats["polls_count"] == 0 assert stats["logs_processed"] == 0 - def test_worker_start_stop(self, settings: Settings, log_repo: LogRepository, alert_repo: AlertRepository): + def test_worker_start_stop( + self, settings: Settings, log_repo: LogRepository, alert_repo: AlertRepository + ): detector = MockDetector() worker = PollingWorker( settings=settings, @@ -196,3 +200,27 @@ def detect(self, logs): assert working.detect_calls == 1 assert worker.logs_processed == 1 + + def test_error_rate_uses_full_window_not_incremental_batch( + self, settings, log_repo, alert_repo + ): + from datetime import UTC, timedelta + from src.detectors.statistical import ErrorBurstDetector + + now = datetime.now(UTC) + for i in range(120): + stamp = now - timedelta(minutes=1 if i < 60 else 10) + log_repo.db.execute( + "INSERT INTO logs VALUES (?, ?, 'error', 'auth', 'timeout', NULL)", + (f"window-{i}", stamp.isoformat(timespec="milliseconds").replace("+00:00", "Z")), + ) + log_repo.db.commit() + # Every polling batch is below the threshold, but the full window is above it. + settings.batch_size = 10 + worker = PollingWorker(settings, log_repo, alert_repo, [ErrorBurstDetector()]) + worker._poll_and_detect() + alerts = alert_repo.get_recent_alerts() + assert len(alerts) == 1 + assert alerts[0].details["error_count"] == 60 + assert alerts[0].details["errors_per_minute"] == 12 + assert alerts[0].details["window_end"] diff --git a/api/package-lock.json b/api/package-lock.json index 4405018..225fc31 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -10,8 +10,7 @@ "dependencies": { "better-sqlite3": "^11.0.0", "cors": "^2.8.5", - "express": "^4.21.0", - "uuid": "^10.0.0" + "express": "^4.21.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.11", @@ -21,7 +20,6 @@ "@types/nock": "^10.0.3", "@types/node": "^22.0.0", "@types/supertest": "^7.2.0", - "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", @@ -34,13 +32,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -49,9 +47,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -59,21 +57,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.13.tgz", + "integrity": "sha512-j1nvOQCZYSgp7HLmisnCXFTpVvFODiijQ9QHkORfGKWH6YDtskTEeCJS7eV545GZ9UBzrq0e2xsOd5QQ5PjIqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-compilation-targets": "^7.29.13", + "@babel/helper-module-transforms": "^7.29.13", + "@babel/helpers": "^7.29.13", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.13", + "@babel/types": "^7.29.11", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -100,14 +98,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.13.tgz", + "integrity": "sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.13", + "@babel/types": "^7.29.11", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -128,14 +126,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.13.tgz", + "integrity": "sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -145,9 +143,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", "dev": true, "license": "ISC", "bin": { @@ -155,9 +153,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -165,29 +163,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.13.tgz", + "integrity": "sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.13" }, "engines": { "node": ">=6.9.0" @@ -197,9 +195,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -207,9 +205,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz", + "integrity": "sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==", "dev": true, "license": "MIT", "engines": { @@ -217,9 +215,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -227,9 +225,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -237,27 +235,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.13.tgz", + "integrity": "sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.13.tgz", + "integrity": "sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.11" }, "bin": { "parser": "bin/babel-parser.js" @@ -364,13 +362,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -490,13 +488,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -506,33 +504,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.13.tgz", + "integrity": "sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11", "debug": "^4.3.1" }, "engines": { @@ -540,14 +538,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -573,40 +571,6 @@ "node": ">=12" } }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -659,9 +623,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -740,9 +704,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -811,29 +775,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -881,9 +859,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", "dev": true, "license": "MIT", "engines": { @@ -1007,9 +985,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { @@ -1083,17 +1061,17 @@ } }, "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1101,39 +1079,39 @@ } }, "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1149,9 +1127,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", "engines": { @@ -1159,70 +1137,70 @@ } }, "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.2.0" + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" + "expect": "30.5.2", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/get-type": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", + "@jest/types": "30.5.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", "engines": { @@ -1230,62 +1208,78 @@ } }, "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.0.1" + "jest-regex-util": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz", + "integrity": "sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.3.10", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1313,65 +1307,28 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jest/reporters/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/@jest/reporters/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -1382,13 +1339,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1398,14 +1355,15 @@ } }, "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.31", "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", "graceful-fs": "^4.2.11" }, "engines": { @@ -1424,14 +1382,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1440,15 +1398,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", + "@jest/test-result": "30.5.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1456,24 +1414,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -1494,14 +1451,14 @@ } }, "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -1602,19 +1559,6 @@ "node": ">=18" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -1663,6 +1607,293 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1675,22 +1906,22 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/pkgr" } }, "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, "license": "MIT" }, @@ -1705,9 +1936,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1742,17 +1973,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2057,13 +2277,6 @@ "@types/superagent": "^8.1.0" } }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -2082,20 +2295,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz", + "integrity": "sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/type-utils": "8.72.1", + "@typescript-eslint/utils": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2105,22 +2318,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.72.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz", + "integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", "debug": "^4.4.3" }, "engines": { @@ -2132,18 +2345,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz", + "integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.72.1", + "@typescript-eslint/types": "^8.72.1", "debug": "^4.4.3" }, "engines": { @@ -2154,18 +2367,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz", + "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2176,9 +2389,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz", + "integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==", "dev": true, "license": "MIT", "engines": { @@ -2189,21 +2402,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz", + "integrity": "sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/utils": "8.72.1", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2214,13 +2427,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz", + "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==", "dev": true, "license": "MIT", "engines": { @@ -2232,21 +2445,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz", + "integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.72.1", + "@typescript-eslint/tsconfig-utils": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2256,20 +2469,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz", + "integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2280,17 +2493,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz", + "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.72.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2322,9 +2535,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.13.0.tgz", + "integrity": "sha512-60E5mvmIiW+i1E/HYeSX4iE4A8s9NJSGYPu7UM0DJght1c2tTkWwuyDyAwzsEXhKM/XbbndfnTiXdfrf27d2hw==", "cpu": [ "arm" ], @@ -2336,9 +2549,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.13.0.tgz", + "integrity": "sha512-qlh/JR3Tuy1cJ7xtf5pR4ykvLiK6os7X7EL2+zXZbrVc/s+yB8DoFdQrCAW9/a2TOxPVbR7QVNpkPWh7UWIsEQ==", "cpu": [ "arm64" ], @@ -2350,9 +2563,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.13.0.tgz", + "integrity": "sha512-yynnnnJXUC99WeD1GfvuXPKsKNfvgzGYC2eRayFnUfsHVROjqrbFOkFQLZiSx1H8+PLy5BzU8vqyPyLqbRfeBQ==", "cpu": [ "arm64" ], @@ -2364,9 +2577,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.13.0.tgz", + "integrity": "sha512-t8PXE0Ip9tmGyhRlfVsnEbzGM5iSvJIeNIXciUACjzpg0mmPs13fGv2x7W8sBa0S8zbIiohxKXS9fL/LXW/ERQ==", "cpu": [ "x64" ], @@ -2378,9 +2591,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.13.0.tgz", + "integrity": "sha512-qdtl9GDWDoDx6rhzF4RU5SJutV3lcLOiTiyqD6nBxYfEO6lAgLNukfrA+HJ2u2OiyWvV8JW5iEWD3LoONw7lYA==", "cpu": [ "x64" ], @@ -2392,9 +2605,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.13.0.tgz", + "integrity": "sha512-VrMUVwdrScTarsh4B6aUhTtziUavSlglo4Hi8XZ6CpAe4+j3nquY+XoP+tYkiSBDsXZ8386ne4pv4YYYBNi02w==", "cpu": [ "arm" ], @@ -2406,9 +2619,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.13.0.tgz", + "integrity": "sha512-zSbTVwaxiTC+zYJ9xVNTC6c+BYO/kOV/odcTI3bHuFFXa+XmjLoUkb/8UxsZeU5YF4K000m17wrXoTblNWuzOg==", "cpu": [ "arm" ], @@ -2420,9 +2633,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.13.0.tgz", + "integrity": "sha512-Z/Rax87moZHsuvJ8awdfP+zrMlsCt3d7mggUPIiPe32KpeagimyzJWnZSMQoV67an9uVQghyh92Pc0VprITnYA==", "cpu": [ "arm64" ], @@ -2434,9 +2647,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.13.0.tgz", + "integrity": "sha512-oUzXz8hNvFLNEAag8lBKJe1euiV2dmGHAR+eQ5fNpsZO0F1K7IMRb4J6pqYsd7uLMRQNPiu5A1WKUJHbFLWbbw==", "cpu": [ "arm64" ], @@ -2447,10 +2660,38 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.13.0.tgz", + "integrity": "sha512-BxhGYvZBg0YrvNOeSEGR3eNklwXYy0mUJk7oeGJvZoQMKM68zYvbbf52+fQI3Le4X0fW276FTKIoTwjpMob80A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.13.0.tgz", + "integrity": "sha512-nZcIuup9Ar3gAlqoZfX/KW6x83TMI11tLpCjldZG5M1KeY8qNUPNJJ71+sPvG8HMBpoh+ix1pAm5gkMFvcSAcQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.13.0.tgz", + "integrity": "sha512-AG6AgdDrkRNjAPwD/cftFJGgZUWCiJX+E8vpYCDEnwFHLTHZ7ppBiU6/vyPPwrfjcPUPQCDbbxE/eZUXsx49jQ==", "cpu": [ "ppc64" ], @@ -2462,9 +2703,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.13.0.tgz", + "integrity": "sha512-6vOqYGcAmzpRWpUURWutzvpf1qJ6eOu5cfYObdtPJnzZ0+Kw4cw+ZkZJqIYEh8fPuTBwSYDL/FwlUhma1G/JVQ==", "cpu": [ "riscv64" ], @@ -2476,9 +2717,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.13.0.tgz", + "integrity": "sha512-4HeIn0S2KVUowK0A+EweXrDvZ8CKHEfTtif0kx9vt3ORNsqcXNFFIFnHE0tizma3/Gt5OGrU2ovYNuj+D/bN4g==", "cpu": [ "riscv64" ], @@ -2490,9 +2731,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.13.0.tgz", + "integrity": "sha512-JfkvqY7NZbxyhexLE+g6Z8Hsx4utHOGaVazAVghPiBn8Upw2iERZPpKYnW1FBVU4WStv/BiIsoK/Muke0E7asA==", "cpu": [ "s390x" ], @@ -2504,9 +2745,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.13.0.tgz", + "integrity": "sha512-iwjLeVdZo/8v5OKAeRIC+4VQV045t1EPZr5B/AVLgUQ04IdtNthWzg6not2cNjZgH2gHpSf4fOXMmN83aLO1FA==", "cpu": [ "x64" ], @@ -2518,9 +2759,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.13.0.tgz", + "integrity": "sha512-TOh1fqOjyq1OPoht3BDLd7Y+HeMJRwd5g2VNmCQclknEbjp4py5GFRmT4L/rOybF5Af8q0LT9zHs2WGkwlqtrw==", "cpu": [ "x64" ], @@ -2531,27 +2772,24 @@ "linux" ] }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.13.0.tgz", + "integrity": "sha512-3T2MNtF2dd1yjaXCVqrsi+/3VZeksEzU1hygPzqqkBLVVjn+2iJzXEkxxCKIVFrWnpmEbazeHJp/ruYD0RUVWA==", "cpu": [ - "wasm32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } + "os": [ + "openharmony" + ] }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.13.0.tgz", + "integrity": "sha512-hKWfSLiN3L4cIxg0jrpyP/66AXLTcyD37jmSn//rGdOUYGlizYxTEFdJ+GZbp2NnaML3YS/A73nCysze8FaJdg==", "cpu": [ "arm64" ], @@ -2563,9 +2801,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.13.0.tgz", + "integrity": "sha512-N4gDKkDoiyIQEcn3vTqe2ksRxUAtBvQ8c5TiT+WonSa6FHgCAY5+RggnZ134hXJoRj57JEjv4/m+i/pAKHWrTQ==", "cpu": [ "ia32" ], @@ -2577,9 +2815,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.13.0.tgz", + "integrity": "sha512-DpLMlAqlrk4zBVK3DiDjwKD0oq7pZNy+8BiIBRuByqb6dm5jsdoLMhIiyh/xR7+WejaW/1Ui+XgbgMNa4+zWJg==", "cpu": [ "x64" ], @@ -2713,9 +2951,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2760,16 +2998,16 @@ "license": "MIT" }, "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.2.0", + "@jest/transform": "30.5.2", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -2782,9 +3020,9 @@ } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -2795,16 +3033,16 @@ "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "test-exclude": "^7.0.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { @@ -2842,20 +3080,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", + "babel-plugin-jest-hoist": "30.5.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, "node_modules/balanced-match": { @@ -2889,9 +3127,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.11.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2946,9 +3184,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.9", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.9.tgz", + "integrity": "sha512-FISIQxV5u91Y9gvuFq3q/tA/qTqLJn3AhRcEvyozyhJTh5OBFFtv827AdHU7Rbyct4r+Ba1/99oFa1Y3yuFsdw==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -2959,7 +3197,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.16.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -2985,16 +3223,16 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -3011,9 +3249,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz", + "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==", "dev": true, "funding": [ { @@ -3031,11 +3269,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.11.33", + "caniuse-lite": "^1.0.30001819", + "electron-to-chromium": "^1.5.464", + "node-releases": "^2.0.60", + "update-browserslist-db": "^1.3.4" }, "bin": { "browserslist": "cli.js" @@ -3157,9 +3395,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001776", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", - "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", + "version": "1.0.30001820", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", + "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", "dev": true, "funding": [ { @@ -3265,9 +3503,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", "dev": true, "license": "MIT" }, @@ -3624,9 +3862,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.476", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.476.tgz", + "integrity": "sha512-LnPnRZ0esKB9NjsSIaC+mffW+5dAtBui6kbK5f9Qykblj4Q33cn6yIhuCcTVjwpvL3YJPqqQ4IlsNyriGNpyFQ==", "dev": true, "license": "ISC" }, @@ -3696,6 +3934,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3851,9 +4096,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -4041,18 +4286,18 @@ } }, "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4272,9 +4517,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -4309,17 +4554,17 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -4527,9 +4772,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -4583,13 +4828,13 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", + "minimist": "^1.2.8", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" @@ -4643,9 +4888,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5028,16 +5273,16 @@ } }, "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", "import-local": "^3.2.0", - "jest-cli": "30.2.0" + "jest-cli": "30.5.2" }, "bin": { "jest": "bin/jest.js" @@ -5055,14 +5300,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.2.0", + "jest-util": "30.5.1", "p-limit": "^3.1.0" }, "engines": { @@ -5070,29 +5315,29 @@ } }, "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "p-limit": "^3.1.0", - "pretty-format": "30.2.0", + "pretty-format": "30.5.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -5102,21 +5347,21 @@ } }, "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "yargs": "^17.7.2" }, "bin": { @@ -5135,34 +5380,33 @@ } }, "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", + "@jest/types": "30.5.1", + "babel-jest": "30.5.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "parse-json": "^5.2.0", - "pretty-format": "30.2.0", + "pretty-format": "30.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -5172,95 +5416,58 @@ "peerDependencies": { "@types/node": "*", "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "ts-node": ">=9.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/jest-config/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { @@ -5271,110 +5478,110 @@ } }, "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", + "@jest/types": "30.5.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", + "jest-util": "30.5.1", + "picomatch": "^4.0.3", + "pretty-format": "30.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -5383,42 +5590,25 @@ } }, "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-util": "30.2.0" + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", "engines": { @@ -5426,111 +5616,100 @@ } }, "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "unrs-resolver": "^1.12.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", + "cjs-module-lexer": "^2.2.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -5538,56 +5717,19 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/jest-runtime/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5604,9 +5746,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", "dev": true, "license": "MIT", "dependencies": { @@ -5615,20 +5757,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.2.0", + "expect": "30.5.2", "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -5637,36 +5779,36 @@ } }, "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5686,19 +5828,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.2.0", + "jest-util": "30.5.1", "string-length": "^4.0.2" }, "engines": { @@ -5706,15 +5848,15 @@ } }, "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -5746,10 +5888,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5923,16 +6075,6 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5976,33 +6118,6 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -6059,13 +6174,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -6125,12 +6240,13 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.8.tgz", + "integrity": "sha512-dHbDC5V1g/EI8OLEtL4Iuwbu0fgzmyykxenn2KnlsX9XdhTXL3gWnfbbP+ab+oUcgV6CFpb8QGyQt/ZXA0UXig==", "dev": true, "license": "MIT", "bin": { + "napi-patch": "lib/patch.js", "napi-postinstall": "lib/cli.js" }, "engines": { @@ -6190,6 +6306,13 @@ "node": ">=10" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6198,11 +6321,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.63", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz", + "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -6438,33 +6564,36 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/picocolors": { @@ -6475,9 +6604,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -6604,15 +6733,16 @@ } }, "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6642,9 +6772,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -6652,6 +6782,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/pump": { @@ -6692,12 +6826,13 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -6754,13 +6889,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -6789,9 +6917,9 @@ } }, "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -7001,14 +7129,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -7020,13 +7148,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -7401,13 +7529,13 @@ } }, "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.9" + "@pkgr/core": "^0.3.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -7445,18 +7573,18 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/test-exclude/node_modules/balanced-match": { @@ -7467,38 +7595,86 @@ "license": "MIT" }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -7507,13 +7683,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7547,9 +7716,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -7727,14 +7896,6 @@ "node": ">=0.10.0" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -7841,44 +8002,45 @@ } }, "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.13.0.tgz", + "integrity": "sha512-mcPQo9Vm6kXd84d/WqMiYBi2CcA57QHBX/ev5PoUEb8b2oQzUvJ6xYsfseQblnCncgnVmCZGWiFwK7KkS5JZJA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "napi-postinstall": "^0.3.7" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "@unrs/resolver-binding-android-arm-eabi": "1.13.0", + "@unrs/resolver-binding-android-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-x64": "1.13.0", + "@unrs/resolver-binding-freebsd-x64": "1.13.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.13.0", + "@unrs/resolver-binding-linux-loong64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-loong64-musl": "1.13.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-musl": "1.13.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-musl": "1.13.0", + "@unrs/resolver-binding-openharmony-arm64": "1.13.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.13.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.13.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.13.0" } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -7931,19 +8093,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -7986,16 +8135,6 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8127,9 +8266,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/api/package.json b/api/package.json index 7f306a4..2061ec4 100644 --- a/api/package.json +++ b/api/package.json @@ -4,19 +4,16 @@ "description": "REST API for log aggregation system", "main": "dist/index.js", "scripts": { - "dev": "ts-node-dev --respawn src/index.ts", + "dev": "npm run build && node dist/index.js", "build": "tsc", "start": "node dist/index.js", "lint": "eslint src/**/*.ts", - "test": "jest", - "seed": "ts-node-dev --transpile-only scripts/seed.ts", - "seed:prod": "node dist/scripts/seed.js" + "test": "jest" }, "dependencies": { "better-sqlite3": "^11.0.0", "cors": "^2.8.5", - "express": "^4.21.0", - "uuid": "^10.0.0" + "express": "^4.21.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.11", @@ -26,7 +23,6 @@ "@types/nock": "^10.0.3", "@types/node": "^22.0.0", "@types/supertest": "^7.2.0", - "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "eslint": "^9.0.0", @@ -36,5 +32,8 @@ "ts-jest": "^29.4.6", "ts-node-dev": "^2.0.0", "typescript": "^5.5.0" + }, + "overrides": { + "qs": "^6.16.0" } } diff --git a/api/src/app.ts b/api/src/app.ts new file mode 100644 index 0000000..ff5aaf3 --- /dev/null +++ b/api/src/app.ts @@ -0,0 +1,75 @@ +import express from 'express'; +import { performance } from 'node:perf_hooks'; +import type Database from 'better-sqlite3'; +import { LogRepository } from './db/logRepository'; +import { AlertRepository } from './db/alertRepository'; +import { AlertService } from './services/alertService'; +import { createLogsRouter } from './routes/logs'; +import { createAlertsRouter } from './routes/alerts'; +import type { IngestionController } from './services/ingestionController'; +export function createApp(db: Database.Database, queueUrl: string, ingestion: IngestionController) { + const app = express(), + logs = new LogRepository(db), + alerts = new AlertRepository(db), + monitor = new AlertService(alerts, logs), + samples: number[] = []; + const start = performance.now(); + app.disable('x-powered-by'); + app.use(express.json({ limit: '8mb' })); + app.get('/health', (_req, res) => + res + .status(ingestion.state.isRunning ? 200 : 503) + .json({ status: ingestion.state.isRunning ? 'ok' : 'degraded', ingestion: ingestion.state }), + ); + app.get('/metrics', async (_req, res) => { + let queue: Record = { reachable: false }; + try { + const response = await fetch(queueUrl + '/api/queue/stats', { + signal: AbortSignal.timeout(1200), + }); + if (response.ok) queue = { ...((await response.json()) as object), reachable: true }; + } catch {} + const sorted = [...samples].sort((a, b) => a - b); + const percentile = (p: number) => + sorted.length ? sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * p) - 1)] : null; + res.json({ + indexedLogs: logs.count(), + uptimeSeconds: (performance.now() - start) / 1000, + ingestion: ingestion.state, + queue, + search: { samples: samples.length, p50Ms: percentile(0.5), p95Ms: percentile(0.95) }, + }); + }); + app.use( + '/api/logs', + (req, res, next) => { + const begin = performance.now(); + res.on('finish', () => { + if ( + req.method === 'GET' && + (req.path === '/' || req.path === '/page') && + res.statusCode === 200 + ) { + samples.push(performance.now() - begin); + if (samples.length > 256) samples.shift(); + } + }); + next(); + }, + createLogsRouter(logs), + ); + app.use('/api/alerts', createAlertsRouter(alerts, monitor)); + app.use( + ( + error: Error & { status?: number }, + _req: express.Request, + res: express.Response, + _next: express.NextFunction, + ) => { + res + .status(error.status === 400 ? 400 : error.status === 413 ? 413 : 500) + .json({ error: 'Request could not be processed' }); + }, + ); + return { app, monitor, logs, alerts }; +} diff --git a/api/src/db/alertRepository.ts b/api/src/db/alertRepository.ts index a3f055c..95c8168 100644 --- a/api/src/db/alertRepository.ts +++ b/api/src/db/alertRepository.ts @@ -40,7 +40,7 @@ export class AlertRepository { alert.message, alert.details ? this.safeStringify(alert.details) : null, alert.createdAt, - alert.acknowledged ? 1 : 0 + alert.acknowledged ? 1 : 0, ); } @@ -49,6 +49,18 @@ export class AlertRepository { const values: (string | number)[] = []; const countValues: (string | number)[] = []; + if (filter.q) { + conditions.push('instr(lower(message),lower(?))>0'); + values.push(filter.q); + countValues.push(filter.q); + } + if (filter.source) { + conditions.push( + "CASE WHEN json_valid(details) THEN COALESCE(json_extract(details,'$.source'),json_extract(details,'$.log_source')) END = ?", + ); + values.push(filter.source); + countValues.push(filter.source); + } if (filter.severity) { conditions.push('severity = ?'); values.push(filter.severity); @@ -62,14 +74,14 @@ export class AlertRepository { } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; - + const countQuery = `SELECT COUNT(*) as total FROM alerts ${whereClause}`; const countResult = this.db.prepare(countQuery).get(...countValues) as { total: number }; const total = countResult?.total || 0; const limit = Math.min(Math.max(filter.limit || 50, 1), 1000); const offset = Math.max(filter.offset || 0, 0); - + const query = ` SELECT id, log_id, severity, message, details, created_at, acknowledged FROM alerts @@ -77,9 +89,9 @@ export class AlertRepository { ORDER BY created_at DESC LIMIT ? OFFSET ? `; - + values.push(limit, offset); - + const rows = this.db.prepare(query).all(...values) as Array<{ id: string; log_id: string; @@ -90,31 +102,35 @@ export class AlertRepository { acknowledged: number; }>; - const alerts: Alert[] = rows.map(row => ({ + const alerts: Alert[] = rows.map((row) => ({ id: row.id, logId: row.log_id, severity: row.severity as Alert['severity'], message: row.message, details: this.safeParseJson(row.details), createdAt: row.created_at, - acknowledged: Boolean(row.acknowledged) + acknowledged: Boolean(row.acknowledged), })); return { alerts, total }; } findById(id: string): Alert | null { - const row = this.db.prepare( - 'SELECT id, log_id, severity, message, details, created_at, acknowledged FROM alerts WHERE id = ?' - ).get(id) as { - id: string; - log_id: string; - severity: string; - message: string; - details: string | null; - created_at: string; - acknowledged: number; - } | undefined; + const row = this.db + .prepare( + 'SELECT id, log_id, severity, message, details, created_at, acknowledged FROM alerts WHERE id = ?', + ) + .get(id) as + | { + id: string; + log_id: string; + severity: string; + message: string; + details: string | null; + created_at: string; + acknowledged: number; + } + | undefined; if (!row) return null; @@ -125,29 +141,27 @@ export class AlertRepository { message: row.message, details: this.safeParseJson(row.details), createdAt: row.created_at, - acknowledged: Boolean(row.acknowledged) + acknowledged: Boolean(row.acknowledged), }; } acknowledge(id: string): boolean { - const result = this.db.prepare( - 'UPDATE alerts SET acknowledged = 1 WHERE id = ?' - ).run(id); + const result = this.db.prepare('UPDATE alerts SET acknowledged = 1 WHERE id = ?').run(id); return result.changes > 0; } getStats(): AlertStats { - const totalResult = this.db.prepare( - 'SELECT COUNT(*) as count FROM alerts' - ).get() as { count: number }; - - const severityStats = this.db.prepare( - 'SELECT severity, COUNT(*) as count FROM alerts GROUP BY severity' - ).all() as Array<{ severity: string; count: number }>; - - const unacknowledgedResult = this.db.prepare( - 'SELECT COUNT(*) as count FROM alerts WHERE acknowledged = 0' - ).get() as { count: number }; + const totalResult = this.db.prepare('SELECT COUNT(*) as count FROM alerts').get() as { + count: number; + }; + + const severityStats = this.db + .prepare('SELECT severity, COUNT(*) as count FROM alerts GROUP BY severity') + .all() as Array<{ severity: string; count: number }>; + + const unacknowledgedResult = this.db + .prepare('SELECT COUNT(*) as count FROM alerts WHERE acknowledged = 0') + .get() as { count: number }; const bySeverity: Record = {}; for (const row of severityStats) { @@ -157,25 +171,32 @@ export class AlertRepository { return { total: totalResult.count, bySeverity, - unacknowledged: unacknowledgedResult.count + openBySeverity: Object.fromEntries( + ( + this.db + .prepare( + 'SELECT severity,COUNT(*) count FROM alerts WHERE acknowledged=0 GROUP BY severity', + ) + .all() as { severity: string; count: number }[] + ).map((row) => [row.severity, row.count]), + ), + unacknowledged: unacknowledgedResult.count, }; } deleteOldAlerts(days: number): number { const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); - - const result = this.db.prepare( - 'DELETE FROM alerts WHERE created_at < ? AND acknowledged = 1' - ).run(cutoff); - + + const result = this.db + .prepare('DELETE FROM alerts WHERE created_at < ? AND acknowledged = 1') + .run(cutoff); + return result.changes; } deleteAcknowledged(): number { - const result = this.db.prepare( - 'DELETE FROM alerts WHERE acknowledged = 1' - ).run(); - + const result = this.db.prepare('DELETE FROM alerts WHERE acknowledged = 1').run(); + return result.changes; } @@ -183,13 +204,15 @@ export class AlertRepository { // Escape LIKE wildcards and build pattern const escapedPattern = pattern.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_'); const likePattern = `%${escapedPattern}%`; - const row = this.db.prepare( - `SELECT id FROM alerts + const row = this.db + .prepare( + `SELECT id FROM alerts WHERE severity = ? AND acknowledged = 0 AND created_at >= ? AND message LIKE ? ESCAPE '\\' - LIMIT 1` - ).get(severity, since, likePattern) as { id: string } | undefined; - + LIMIT 1`, + ) + .get(severity, since, likePattern) as { id: string } | undefined; + return row ? this.findById(row.id) : null; } @@ -210,4 +233,4 @@ export class AlertRepository { return null; } } -} \ No newline at end of file +} diff --git a/api/src/db/database.ts b/api/src/db/database.ts new file mode 100644 index 0000000..e11a8b3 --- /dev/null +++ b/api/src/db/database.ts @@ -0,0 +1,13 @@ +import Database from 'better-sqlite3'; +import { mkdirSync } from 'node:fs'; +import path from 'node:path'; +export function openDatabase(file: string): Database.Database { + mkdirSync(path.dirname(file), { recursive: true }); + const db = new Database(file); + db.pragma('journal_mode=WAL'); + db.pragma('synchronous=FULL'); + db.pragma('busy_timeout=10000'); + db.pragma('cache_size=-32768'); + db.pragma('wal_autocheckpoint=4096'); + return db; +} diff --git a/api/src/db/logPageReader.ts b/api/src/db/logPageReader.ts new file mode 100644 index 0000000..6bc11f2 --- /dev/null +++ b/api/src/db/logPageReader.ts @@ -0,0 +1,103 @@ +import type Database from 'better-sqlite3'; +import { createHash } from 'node:crypto'; +import type { LogEntry, SearchParams } from '../types/log'; +export class InvalidCursor extends Error {} +type Position = { time: string; row: number; filter: string }; +export function readLogPage(db: Database.Database, params: SearchParams & { cursor?: string }) { + const filter = createHash('sha256') + .update( + JSON.stringify([ + params.q ?? '', + params.level ?? '', + params.source ?? '', + params.startDate ?? '', + params.endDate ?? '', + ]), + ) + .digest('hex') + .slice(0, 24); + const conditions: string[] = [], + values: (string | number)[] = []; + for (const [key, column] of [ + ['level', 'level'], + ['source', 'source'], + ['startDate', 'timestamp >='], + ['endDate', 'timestamp <='], + ] as const) { + const value = params[key]; + if (value) { + conditions.push(column.includes(' ') ? column + ' ?' : column + ' = ?'); + values.push(value); + } + } + if (params.cursor) { + let p: Position; + try { + if (params.cursor.length > 1024) throw new Error(); + p = JSON.parse(Buffer.from(params.cursor, 'base64url').toString()); + if ( + typeof p.time !== 'string' || + !Number.isFinite(Date.parse(p.time)) || + !Number.isSafeInteger(p.row) || + p.row < 1 || + p.filter !== filter + ) + throw new Error(); + } catch { + throw new InvalidCursor('Cursor does not match this search.'); + } + conditions.push('(timestamp < ? OR (timestamp = ? AND rowid < ?))'); + values.push(p.time, p.time, p.row); + } + const where = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '', + limit = Math.min(Math.max(params.limit || 50, 1), 1000); + const fields = 'rowid AS cursor_row,id,timestamp,level,source,message,raw'; + let rows: Record[]; + if (params.q) { + const phrase = '"' + params.q.replace(/"/g, '""') + '"'; + // Intersect one bounded FTS scan with the latest timestamp-ordered rows. + // A correlated MATCH per candidate becomes very expensive for sparse phrases. + // Fall back to the global index when the recent slice cannot fill a page. + rows = db + .prepare( + 'WITH recent AS MATERIALIZED (SELECT rowid AS cursor_row,timestamp' + + ' FROM logs ' + + where + + ' ORDER BY timestamp DESC,rowid DESC LIMIT 10000), hits AS (SELECT * FROM recent WHERE cursor_row IN (SELECT rowid FROM logs_fts WHERE logs_fts MATCH ? AND rowid >= (SELECT min(cursor_row) FROM recent) AND rowid <= (SELECT max(cursor_row) FROM recent)) ORDER BY timestamp DESC,cursor_row DESC LIMIT ?) SELECT logs.rowid AS cursor_row,logs.* FROM hits JOIN logs ON logs.rowid=hits.cursor_row ORDER BY logs.timestamp DESC,logs.rowid DESC', + ) + .all(...values, phrase, limit + 1) as Record[]; + if (rows.length <= limit) + rows = db + .prepare( + 'SELECT ' + + fields + + ' FROM logs ' + + where + + (where ? ' AND ' : ' WHERE ') + + 'rowid IN (SELECT rowid FROM logs_fts WHERE logs_fts MATCH ?) ORDER BY timestamp DESC,rowid DESC LIMIT ?', + ) + .all(...values, phrase, limit + 1) as Record[]; + } else + rows = db + .prepare( + 'SELECT ' + fields + ' FROM logs ' + where + ' ORDER BY timestamp DESC,rowid DESC LIMIT ?', + ) + .all(...values, limit + 1) as Record[]; + const hasMore = rows.length > limit, + page = rows.slice(0, limit), + last = page.at(-1); + const logs = page.map(({ cursor_row: _, ...entry }) => ({ + ...entry, + raw: entry.raw ?? undefined, + })) as unknown as LogEntry[]; + return { + logs, + hasMore, + nextCursor: + hasMore && last + ? Buffer.from( + JSON.stringify({ time: last.timestamp, row: last.cursor_row, filter }), + ).toString('base64url') + : null, + }; +} diff --git a/api/src/db/logRepository.ts b/api/src/db/logRepository.ts index f32924d..e621a25 100644 --- a/api/src/db/logRepository.ts +++ b/api/src/db/logRepository.ts @@ -1,8 +1,10 @@ import Database from 'better-sqlite3'; import { LogEntry, SearchParams, LogStats } from '../types/log'; +import { readLogPage } from './logPageReader'; export class LogRepository { private db: Database.Database; + private delivered = new Map(); constructor(db: Database.Database) { this.db = db; @@ -45,11 +47,48 @@ export class LogRepository { END; CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp); + DROP INDEX IF EXISTS idx_logs_time_row; + CREATE INDEX IF NOT EXISTS idx_logs_source_time ON logs(source,timestamp DESC); + CREATE INDEX IF NOT EXISTS idx_logs_level_time ON logs(level,timestamp DESC); CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level); CREATE INDEX IF NOT EXISTS idx_logs_source ON logs(source); `); } + searchPage(params: SearchParams & { cursor?: string }) { + return readLogPage(this.db, params); + } + + count(): number { + return (this.db.prepare('SELECT COUNT(*) n FROM logs').get() as { n: number }).n; + } + + insertDeliveredBatch(entries: LogEntry[]): number { + let added = 0; + for (let offset = 0; offset < entries.length; offset += 128) { + const chunk = entries.slice(offset, offset + 128); + let stmt = this.delivered.get(chunk.length); + if (!stmt) { + stmt = this.db.prepare( + 'INSERT OR IGNORE INTO logs(id,timestamp,level,source,message,raw) VALUES ' + + chunk.map(() => '(?,?,?,?,?,?)').join(','), + ); + this.delivered.set(chunk.length, stmt); + } + added += stmt.run( + ...chunk.flatMap((entry) => [ + entry.id, + entry.timestamp, + entry.level, + entry.source, + entry.message, + entry.raw ?? null, + ]), + ).changes; + } + return added; + } + insert(entry: LogEntry): void { const stmt = this.db.prepare(` INSERT INTO logs (id, timestamp, level, source, message, raw) @@ -61,7 +100,7 @@ export class LogRepository { entry.level, entry.source, entry.message, - entry.raw || null + entry.raw || null, ); } @@ -72,7 +111,7 @@ export class LogRepository { INSERT INTO logs (id, timestamp, level, source, message, raw) VALUES (?, ?, ?, ?, ?, ?) `); - + const transaction = this.db.transaction((items: LogEntry[]) => { for (const entry of items) { insertStmt.run( @@ -81,12 +120,12 @@ export class LogRepository { entry.level, entry.source, entry.message, - entry.raw || null + entry.raw || null, ); } return items.length; }); - + return transaction(entries); } @@ -129,20 +168,20 @@ export class LogRepository { } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; - + // Build count query using same conditions but without limit/offset const countQuery = ` SELECT COUNT(*) as total FROM logs ${whereClause} `; - + const countResult = this.db.prepare(countQuery).get(...countValues) as { total: number }; const total = countResult?.total || 0; const limit = Math.min(Math.max(params.limit || 50, 1), 1000); const offset = Math.max(params.offset || 0, 0); - + const query = ` SELECT id, timestamp, level, source, message, raw FROM logs @@ -150,9 +189,9 @@ export class LogRepository { ORDER BY timestamp DESC LIMIT ? OFFSET ? `; - + values.push(limit, offset); - + const stmt = this.db.prepare(query); const rows = stmt.all(...values) as Array<{ id: string; @@ -163,13 +202,13 @@ export class LogRepository { raw: string | null; }>; - const logs: LogEntry[] = rows.map(row => ({ + const logs: LogEntry[] = rows.map((row) => ({ id: row.id, timestamp: row.timestamp, level: row.level as LogEntry['level'], source: row.source, message: row.message, - raw: row.raw || undefined + raw: row.raw || undefined, })); return { logs, total }; @@ -179,18 +218,20 @@ export class LogRepository { // Clamp hours to a reasonable range (1 hour to 1 year) const clampedHours = Math.min(Math.max(hours, 1), 8760); const since = new Date(Date.now() - clampedHours * 60 * 60 * 1000).toISOString(); - - const totalResult = this.db.prepare( - 'SELECT COUNT(*) as count FROM logs WHERE timestamp >= ?' - ).get(since) as { count: number }; - - const levelStats = this.db.prepare( - 'SELECT level, COUNT(*) as count FROM logs WHERE timestamp >= ? GROUP BY level' - ).all(since) as Array<{ level: string; count: number }>; - - const sourceStats = this.db.prepare( - 'SELECT source, COUNT(*) as count FROM logs WHERE timestamp >= ? GROUP BY source ORDER BY count DESC LIMIT 100' - ).all(since) as Array<{ source: string; count: number }>; + + const totalResult = this.db + .prepare('SELECT COUNT(*) as count FROM logs WHERE timestamp >= ?') + .get(since) as { count: number }; + + const levelStats = this.db + .prepare('SELECT level, COUNT(*) as count FROM logs WHERE timestamp >= ? GROUP BY level') + .all(since) as Array<{ level: string; count: number }>; + + const sourceStats = this.db + .prepare( + 'SELECT source, COUNT(*) as count FROM logs WHERE timestamp >= ? GROUP BY source ORDER BY count DESC LIMIT 100', + ) + .all(since) as Array<{ source: string; count: number }>; const byLevel: Record = {}; const bySource: Record = {}; @@ -206,31 +247,31 @@ export class LogRepository { return { total: totalResult.count, byLevel, - bySource + bySource, }; } deleteOldLogs(days: number): number { const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); - - const result = this.db.prepare( - 'DELETE FROM logs WHERE timestamp < ?' - ).run(cutoff); - + + const result = this.db.prepare('DELETE FROM logs WHERE timestamp < ?').run(cutoff); + return result.changes; } getById(id: string): LogEntry | null { - const row = this.db.prepare( - 'SELECT id, timestamp, level, source, message, raw FROM logs WHERE id = ?' - ).get(id) as { - id: string; - timestamp: string; - level: string; - source: string; - message: string; - raw: string | null; - } | undefined; + const row = this.db + .prepare('SELECT id, timestamp, level, source, message, raw FROM logs WHERE id = ?') + .get(id) as + | { + id: string; + timestamp: string; + level: string; + source: string; + message: string; + raw: string | null; + } + | undefined; if (!row) return null; @@ -240,14 +281,16 @@ export class LogRepository { level: row.level as LogEntry['level'], source: row.source, message: row.message, - raw: row.raw || undefined + raw: row.raw || undefined, }; } getRecent(limit: number): LogEntry[] { - const rows = this.db.prepare( - 'SELECT id, timestamp, level, source, message, raw FROM logs ORDER BY timestamp DESC LIMIT ?' - ).all(limit) as Array<{ + const rows = this.db + .prepare( + 'SELECT id, timestamp, level, source, message, raw FROM logs ORDER BY timestamp DESC LIMIT ?', + ) + .all(limit) as Array<{ id: string; timestamp: string; level: string; @@ -256,21 +299,24 @@ export class LogRepository { raw: string | null; }>; - return rows.map(row => ({ + return rows.map((row) => ({ id: row.id, timestamp: row.timestamp, level: row.level as LogEntry['level'], source: row.source, message: row.message, - raw: row.raw || undefined + raw: row.raw || undefined, })); } - getErrorCountSince(since: string, level: string = 'error'): number { - const result = this.db.prepare( - 'SELECT COUNT(*) as count FROM logs WHERE level = ? AND timestamp >= ?' - ).get(level, since) as { count: number }; - + getErrorCountSince(since: string, level: string = 'error', until?: string): number { + const result = this.db + .prepare( + 'SELECT COUNT(*) as count FROM logs WHERE level = ? AND timestamp >= ?' + + (until ? ' AND timestamp <= ?' : ''), + ) + .get(...(until ? [level, since, until] : [level, since])) as { count: number }; + return result.count; } -} \ No newline at end of file +} diff --git a/api/src/index.ts b/api/src/index.ts index 7cc3a2d..61fa224 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,117 +1,26 @@ -import express, { Request, Response, NextFunction } from 'express'; -import cors from 'cors'; -import fs from 'fs'; -import path from 'path'; -import Database from 'better-sqlite3'; +import { openDatabase } from './db/database'; +import { IngestionController } from './services/ingestionController'; +import { createApp } from './app'; +const dbPath = process.env.DB_PATH || './data/logs.db', + queueUrl = process.env.QUEUE_URL || 'http://127.0.0.1:8081'; +const db = openDatabase(dbPath); +// Initialize the schema before starting a second SQLite connection. import { LogRepository } from './db/logRepository'; -import { AlertRepository } from './db/alertRepository'; -import { QueueService } from './services/queueService'; -import { QueueConsumer } from './services/queueConsumer'; -import { AlertService } from './services/alertService'; -import { createLogsRouter } from './routes/logs'; -import { createAlertsRouter } from './routes/alerts'; - -const app = express(); -const PORT = process.env.PORT || 3000; -const DB_PATH = process.env.DB_PATH || './data/logs.db'; -const QUEUE_URL = process.env.QUEUE_URL || 'http://localhost:8081'; -const QUEUE_POLL_INTERVAL = parseInt(process.env.QUEUE_POLL_INTERVAL_MS || '5000', 10) || 5000; - -// Ensure data directory exists -const dataDir = path.dirname(DB_PATH); -if (!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir, { recursive: true }); -} - -// Initialize database and repositories (single shared connection) -const db = new Database(DB_PATH); -const logRepo = new LogRepository(db); -const alertRepo = new AlertRepository(db); - -// Initialize services -const queueService = new QueueService(QUEUE_URL); -const queueConsumer = new QueueConsumer(logRepo, queueService, QUEUE_POLL_INTERVAL); -const alertService = new AlertService(alertRepo, logRepo); - -// Middleware -app.use(cors()); -app.use(express.json({ limit: '100kb' })); - -// Health check -app.get('/health', (_req: Request, res: Response) => { - const consumerStatus = queueConsumer.getStatus(); - const alertStatus = alertService.getStatus(); - - res.json({ - status: 'ok', - services: { - queueConsumer: consumerStatus.isRunning ? 'running' : 'stopped', - alertService: alertStatus.isRunning ? 'running' : 'stopped' - } - }); -}); - -// Mount routes -app.use('/api/logs', createLogsRouter(logRepo)); -app.use('/api/alerts', createAlertsRouter(alertRepo, alertService)); - -// Error handling middleware (must have 4 parameters) -app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { - console.error('Unhandled error:', err); - res.status(500).json({ error: 'Internal server error' }); +new LogRepository(db); +const ingestion = new IngestionController(dbPath, queueUrl); +const { app, monitor } = createApp(db, queueUrl, ingestion); +const server = app.listen(Number(process.env.PORT || 3000), process.env.HOST || '127.0.0.1', () => { + monitor.start(); + console.log('Search API ready'); }); - -// Start server -const server = app.listen(PORT, () => { - console.log(`API server running on port ${PORT}`); - console.log(`Database: ${DB_PATH}`); - console.log(`Queue URL: ${QUEUE_URL}`); - - // Start background services - queueConsumer.start(); - alertService.start(); -}); - -// Graceful shutdown -let shuttingDown = false; - -function gracefulShutdown(signal: string): void { - if (shuttingDown) { - console.log(`Already shutting down, ignoring ${signal}`); - return; - } - shuttingDown = true; - - console.log(`\nReceived ${signal}. Starting graceful shutdown...`); - - // Stop background services - queueConsumer.stop(); - alertService.stop(); - - // Close server first, then close database after in-flight requests finish - server.close(() => { - db.close(); - console.log('Server closed. Exiting.'); - process.exit(0); - }); - - // Force shutdown after 10 seconds - setTimeout(() => { - console.error('Forced shutdown after timeout'); - process.exit(1); - }, 10000); +let closing = false; +async function stop() { + if (closing) return; + closing = true; + monitor.stop(); + const closed = new Promise((resolve) => server.close(() => resolve())); + await Promise.all([closed, ingestion.stop()]); + db.close(); } - -process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); -process.on('SIGINT', () => gracefulShutdown('SIGINT')); - -// Handle uncaught exceptions -process.on('uncaughtException', (error) => { - console.error('Uncaught exception:', error); - gracefulShutdown('uncaughtException'); -}); - -process.on('unhandledRejection', (reason) => { - console.error('Unhandled rejection:', reason); - gracefulShutdown('unhandledRejection'); -}); \ No newline at end of file +process.on('SIGINT', () => void stop()); +process.on('SIGTERM', () => void stop()); diff --git a/api/src/routes/alerts.ts b/api/src/routes/alerts.ts index c7f6bb8..0aede40 100644 --- a/api/src/routes/alerts.ts +++ b/api/src/routes/alerts.ts @@ -4,25 +4,37 @@ import { AlertService } from '../services/alertService'; import { AlertFilter } from '../types/log'; import { parseQueryInt } from '../utils/queryHelpers'; -export function createAlertsRouter( - alertRepo: AlertRepository, - alertService: AlertService -): Router { +export function createAlertsRouter(alertRepo: AlertRepository, alertService: AlertService): Router { const router = Router(); // GET /api/alerts - List alerts router.get('/', (req: Request, res: Response) => { try { + for (const key of ['q', 'source', 'severity', 'acknowledged']) + if (req.query[key] !== undefined && typeof req.query[key] !== 'string') { + res.status(400).json({ error: 'Invalid alert filter' }); + return; + } + if ( + (typeof req.query.q === 'string' && req.query.q.length > 10000) || + (typeof req.query.source === 'string' && req.query.source.length > 500) || + (req.query.acknowledged !== undefined && + !['true', 'false'].includes(String(req.query.acknowledged))) + ) { + res.status(400).json({ error: 'Invalid alert filter' }); + return; + } const limit = parseQueryInt(req.query.limit as string, 50, 1, 1000); const offset = parseQueryInt(req.query.offset as string, 0, 0); const filter: AlertFilter = { + q: req.query.q as string | undefined, + source: req.query.source as string | undefined, severity: req.query.severity as string | undefined, - acknowledged: req.query.acknowledged !== undefined - ? req.query.acknowledged === 'true' - : undefined, + acknowledged: + req.query.acknowledged !== undefined ? req.query.acknowledged === 'true' : undefined, limit, - offset + offset, }; const result = alertRepo.findAll(filter); @@ -30,7 +42,7 @@ export function createAlertsRouter( alerts: result.alerts, total: result.total, limit, - offset + offset, }); } catch (error) { console.error('Error fetching alerts:', error); @@ -43,14 +55,14 @@ export function createAlertsRouter( try { const stats = alertRepo.getStats(); const serviceStatus = alertService.getStatus(); - + res.json({ ...stats, monitoring: { isRunning: serviceStatus.isRunning, lastCheck: serviceStatus.lastCheck, - config: serviceStatus.config - } + config: serviceStatus.config, + }, }); } catch (error) { console.error('Error fetching alert stats:', error); @@ -62,7 +74,7 @@ export function createAlertsRouter( router.get('/:id', (req: Request, res: Response) => { try { const alert = alertRepo.findById(req.params.id); - + if (!alert) { res.status(404).json({ error: 'Alert not found' }); return; @@ -79,7 +91,7 @@ export function createAlertsRouter( router.post('/:id/acknowledge', (req: Request, res: Response) => { try { const success = alertRepo.acknowledge(req.params.id); - + if (!success) { res.status(404).json({ error: 'Alert not found' }); return; @@ -96,12 +108,12 @@ export function createAlertsRouter( router.delete('/', (req: Request, res: Response) => { try { const days = parseInt(req.query.olderThanDays as string) || 30; - + if (days <= 0) { res.status(400).json({ error: 'olderThanDays must be a positive number' }); return; } - + const deleted = alertRepo.deleteOldAlerts(days); res.json({ deleted, olderThanDays: days }); } catch (error) { diff --git a/api/src/routes/logs.ts b/api/src/routes/logs.ts index 96bd11c..aef9957 100644 --- a/api/src/routes/logs.ts +++ b/api/src/routes/logs.ts @@ -1,9 +1,10 @@ import { Request, Response, Router } from 'express'; -import { v4 as uuidv4 } from 'uuid'; +import { randomUUID as uuidv4 } from 'node:crypto'; import { LogRepository } from '../db/logRepository'; import { LogEntry, SearchParams } from '../types/log'; import { parseQueryInt } from '../utils/queryHelpers'; import { normalizeLevel } from '../utils/logNormalizer'; +import { InvalidCursor } from '../db/logPageReader'; export function createLogsRouter(logRepo: LogRepository): Router { const router = Router(); @@ -32,7 +33,7 @@ export function createLogsRouter(logRepo: LogRepository): Router { level: normalizeLevel(level), source: validatedSource || 'api', message, - raw: typeof raw === 'string' ? raw : undefined + raw: typeof raw === 'string' ? raw : undefined, }; logRepo.insert(entry); @@ -45,6 +46,11 @@ export function createLogsRouter(logRepo: LogRepository): Router { // GET /api/logs - Search logs router.get('/', (req: Request, res: Response) => { + for (const key of ['q', 'level', 'source', 'startDate', 'endDate']) + if (req.query[key] !== undefined && typeof req.query[key] !== 'string') { + res.status(400).json({ error: 'Invalid filter' }); + return; + } try { const limit = parseQueryInt(req.query.limit as string, 50, 1, 1000); const offset = parseQueryInt(req.query.offset as string, 0, 0); @@ -56,7 +62,7 @@ export function createLogsRouter(logRepo: LogRepository): Router { startDate: req.query.startDate as string | undefined, endDate: req.query.endDate as string | undefined, limit, - offset + offset, }; const result = logRepo.search(params); @@ -64,7 +70,7 @@ export function createLogsRouter(logRepo: LogRepository): Router { logs: result.logs, total: result.total, limit, - offset + offset, }); } catch (error) { console.error('Error searching logs:', error); @@ -72,6 +78,47 @@ export function createLogsRouter(logRepo: LogRepository): Router { } }); + router.get('/page', (req: Request, res: Response) => { + for (const key of ['q', 'level', 'source', 'startDate', 'endDate', 'cursor']) + if (req.query[key] !== undefined && typeof req.query[key] !== 'string') { + res.status(400).json({ error: 'Invalid filter' }); + return; + } + const q = req.query.q as string | undefined, + source = req.query.source as string | undefined; + if ((q && q.length > 10000) || (source && source.length > 500)) { + res.status(400).json({ error: 'Filter too long' }); + return; + } + const startDate = req.query.startDate as string | undefined, + endDate = req.query.endDate as string | undefined; + if ( + (startDate && !Number.isFinite(Date.parse(startDate))) || + (endDate && !Number.isFinite(Date.parse(endDate))) || + (startDate && endDate && Date.parse(startDate) > Date.parse(endDate)) + ) { + res.status(400).json({ error: 'Invalid time range' }); + return; + } + try { + res.json( + logRepo.searchPage({ + q, + source, + level: req.query.level ? String(req.query.level).toLowerCase() : undefined, + startDate: startDate ? new Date(startDate).toISOString() : undefined, + endDate: endDate ? new Date(endDate).toISOString() : undefined, + limit: parseQueryInt(req.query.limit as string, 50, 1, 1000), + cursor: req.query.cursor as string | undefined, + }), + ); + } catch (error) { + res + .status(error instanceof InvalidCursor ? 400 : 500) + .json({ error: error instanceof InvalidCursor ? error.message : 'Search failed' }); + } + }); + // GET /api/logs/stats - Get statistics (must be before /:id) router.get('/stats', (req: Request, res: Response) => { try { @@ -88,7 +135,7 @@ export function createLogsRouter(logRepo: LogRepository): Router { router.get('/:id', (req: Request, res: Response) => { try { const log = logRepo.getById(req.params.id); - + if (!log) { res.status(404).json({ error: 'Log not found' }); return; @@ -124,9 +171,9 @@ export function createLogsRouter(logRepo: LogRepository): Router { function validateTimestamp(timestamp: string | undefined): string | null { if (!timestamp) return null; - + const date = new Date(timestamp); if (isNaN(date.getTime())) return null; - + return date.toISOString(); -} \ No newline at end of file +} diff --git a/api/src/services/alertService.ts b/api/src/services/alertService.ts index 4da404a..eba8345 100644 --- a/api/src/services/alertService.ts +++ b/api/src/services/alertService.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from 'uuid'; +import { randomUUID as uuidv4 } from 'node:crypto'; import { AlertRepository } from '../db/alertRepository'; import { LogRepository } from '../db/logRepository'; import { Alert } from '../types/log'; @@ -26,17 +26,17 @@ export class AlertService { constructor( alertRepo: AlertRepository, logRepo: LogRepository, - config: Partial = {} + config: Partial = {}, ) { this.alertRepo = alertRepo; this.logRepo = logRepo; this.config = { errorRate: { errorsPerMinute: 10, - windowMinutes: 5 + windowMinutes: 5, }, enabled: true, - ...config + ...config, }; this.lastCheck = new Date(); this.checkInterval = 60000; // Check every minute @@ -92,27 +92,45 @@ export class AlertService { return; } + const windowEnd = new Date().toISOString(); const windowStart = new Date( - Date.now() - this.config.errorRate.windowMinutes * 60 * 1000 + Date.parse(windowEnd) - this.config.errorRate.windowMinutes * 60 * 1000, ).toISOString(); - const errorCount = this.logRepo.getErrorCountSince(windowStart); + const errorCount = this.logRepo.getErrorCountSince(windowStart, 'error', windowEnd); const errorsPerMinute = errorCount / this.config.errorRate.windowMinutes; if (errorsPerMinute > this.config.errorRate.errorsPerMinute) { const since = new Date(Date.now() - 60 * 60 * 1000).toISOString(); - const existingAlert = this.alertRepo.findRecentUnacknowledged('high', 'error_rate', since); - + const existingAlert = this.alertRepo.findRecentUnacknowledged( + 'high', + 'High error rate detected:', + since, + ); + if (!existingAlert) { - this.createErrorRateAlert(errorCount, errorsPerMinute); + this.createErrorRateAlert(errorCount, errorsPerMinute, windowEnd); } } } - private createErrorRateAlert(errorCount: number, errorsPerMinute: number): void { + private createErrorRateAlert( + errorCount: number, + errorsPerMinute: number, + windowEnd: string, + ): void { + const anchor = this.logRepo.searchPage({ + level: 'error', + startDate: new Date( + Date.parse(windowEnd) - this.config.errorRate.windowMinutes * 60000, + ).toISOString(), + endDate: windowEnd, + limit: 1, + }).logs[0]; + if (!anchor) return; const alert: Alert = { id: uuidv4(), - logId: 'system', + logId: anchor.id, severity: 'high', message: `High error rate detected: ${errorsPerMinute.toFixed(2)} errors/minute (${errorCount} errors in ${this.config.errorRate.windowMinutes} minutes)`, details: { @@ -120,10 +138,11 @@ export class AlertService { errorsPerMinute, windowMinutes: this.config.errorRate.windowMinutes, threshold: this.config.errorRate.errorsPerMinute, - type: 'error_rate' + type: 'error_rate', + window_end: windowEnd, }, createdAt: new Date().toISOString(), - acknowledged: false + acknowledged: false, }; this.alertRepo.create(alert); @@ -134,7 +153,7 @@ export class AlertService { const fullAlert: Alert = { ...alert, id: uuidv4(), - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), }; this.alertRepo.create(fullAlert); @@ -158,7 +177,7 @@ export class AlertService { return { isRunning: this.isRunning, lastCheck: this.lastCheck, - config: this.getConfig() + config: this.getConfig(), }; } -} \ No newline at end of file +} diff --git a/api/src/services/ingestionController.ts b/api/src/services/ingestionController.ts new file mode 100644 index 0000000..c09052b --- /dev/null +++ b/api/src/services/ingestionController.ts @@ -0,0 +1,37 @@ +import { Worker } from 'node:worker_threads'; +import path from 'node:path'; +export class IngestionController { + state: { isRunning: boolean; lastError: string | null; duplicates: number; rejected: number } = { + isRunning: false, + lastError: null, + duplicates: 0, + rejected: 0, + }; + private worker: Worker; + private exited = false; + constructor(dbPath: string, queueUrl: string) { + this.worker = new Worker(path.join(__dirname, 'ingestionWorker.js'), { + workerData: { dbPath, queueUrl }, + }); + this.worker.on('message', (state) => { + this.state = state; + }); + this.worker.on('error', (error) => { + this.state = { ...this.state, isRunning: false, lastError: error.message }; + }); + this.worker.on('exit', () => { + this.exited = true; + this.state = { ...this.state, isRunning: false }; + }); + } + async stop() { + if (this.exited) return; + this.worker.postMessage('stop'); + const timer = setTimeout(() => void this.worker.terminate(), 12000); + await new Promise((resolve) => { + this.worker.once('exit', () => resolve()); + if (!this.state.isRunning) void this.worker.terminate(); + }); + clearTimeout(timer); + } +} diff --git a/api/src/services/ingestionWorker.ts b/api/src/services/ingestionWorker.ts new file mode 100644 index 0000000..2d9af0a --- /dev/null +++ b/api/src/services/ingestionWorker.ts @@ -0,0 +1,127 @@ +import { parentPort, workerData } from 'node:worker_threads'; +import { createHash } from 'node:crypto'; +import { setTimeout as delay } from 'node:timers/promises'; +import { openDatabase } from '../db/database'; +import { LogRepository } from '../db/logRepository'; +import { LogEntry } from '../types/log'; +import { normalizeLevel } from '../utils/logNormalizer'; +const db = openDatabase(workerData.dbPath), + repo = new LogRepository(db); +db.exec( + 'CREATE TABLE IF NOT EXISTS rejected_logs(id TEXT PRIMARY KEY,payload TEXT NOT NULL,reason TEXT NOT NULL,created_at TEXT NOT NULL)', +); +const rejected = db.prepare('INSERT OR IGNORE INTO rejected_logs VALUES(?,?,?,?)'); +let stopping = false, + duplicates = 0, + lastError: string | null = null; +parentPort!.on('message', (message) => { + if (message === 'stop') stopping = true; +}); +const status = () => + parentPort!.postMessage({ + isRunning: !stopping, + duplicates, + rejected: (db.prepare('SELECT COUNT(*) n FROM rejected_logs').get() as { n: number }).n, + lastError, + }); +async function request(route: string, body?: unknown) { + const res = await fetch(workerData.queueUrl + route, { + method: body ? 'POST' : 'GET', + headers: body ? { 'content-type': 'application/json' } : undefined, + body: body ? JSON.stringify(body) : undefined, + signal: AbortSignal.timeout(10000), + }); + if (!res.ok) throw new Error('Queue HTTP ' + res.status); + return res.json(); +} +function normalize(value: unknown, fallback: string): LogEntry { + if (!value || typeof value !== 'object' || Array.isArray(value)) + throw new Error('Expected a log object'); + const raw = value as Record; + if (typeof raw.message !== 'string' || raw.message.length > 10000) + throw new Error('Invalid message'); + if (raw.source != null && (typeof raw.source !== 'string' || raw.source.length > 500)) + throw new Error('Invalid source'); + if (raw.id != null && (typeof raw.id !== 'string' || !raw.id || raw.id.length > 256)) + throw new Error('Invalid event id'); + const date = + raw.timestamp == null + ? new Date() + : new Date(typeof raw.timestamp === 'number' ? raw.timestamp : String(raw.timestamp)); + if (!Number.isFinite(date.getTime())) throw new Error('Invalid timestamp'); + return { + id: typeof raw.id === 'string' ? raw.id : createHash('sha256').update(fallback).digest('hex'), + timestamp: date.toISOString(), + message: raw.message, + source: typeof raw.source === 'string' && raw.source ? raw.source : 'unknown', + level: normalizeLevel(raw.level as string), + raw: typeof raw.raw === 'string' ? raw.raw : JSON.stringify(raw), + }; +} +async function main() { + status(); + while (!stopping) { + try { + const page = (await request('/api/logs/pending')) as { + messages?: { id: string; lease: string; logs: unknown[] }[]; + }; + if (!Array.isArray(page.messages)) throw new Error('Invalid queue response'); + const entries: LogEntry[] = [], + poison: { id: string; payload: string; reason: string }[] = []; + for (const batch of page.messages) { + if ( + typeof batch.id !== 'string' || + typeof batch.lease !== 'string' || + !Array.isArray(batch.logs) || + batch.logs.length > 4096 + ) + throw new Error('Invalid leased batch'); + batch.logs.forEach((value, index) => { + try { + entries.push(normalize(value, batch.id + ':' + index)); + } catch (error) { + poison.push({ + id: batch.id + ':' + index, + payload: JSON.stringify(value), + reason: String(error), + }); + } + }); + } + if (page.messages.length) { + const result = db.transaction(() => { + const inserted = repo.insertDeliveredBatch(entries); + let quarantined = 0; + for (const row of poison) + quarantined += rejected.run( + row.id, + row.payload, + row.reason, + new Date().toISOString(), + ).changes; + return { inserted, quarantined }; + })(); + duplicates += entries.length - result.inserted; + // Every lease is acknowledged only after all rows are committed. + await request('/api/logs/ack', { + leases: page.messages.map(({ id, lease }) => ({ id, lease })), + }); + } + lastError = null; + status(); + if (page.messages.length === 0) await delay(20); + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + status(); + await delay(250); + } + } + db.close(); + parentPort!.close(); +} +void main().catch((error) => { + console.error(error); + process.exitCode = 1; + db.close(); + parentPort!.close(); +}); diff --git a/api/src/services/queueConsumer.ts b/api/src/services/queueConsumer.ts index cfdcd92..24a10be 100644 --- a/api/src/services/queueConsumer.ts +++ b/api/src/services/queueConsumer.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from 'uuid'; +import { randomUUID as uuidv4 } from 'node:crypto'; import { LogRepository } from '../db/logRepository'; import { QueueService } from './queueService'; import { LogEntry } from '../types/log'; @@ -24,11 +24,7 @@ export class QueueConsumer { private failedMessageIds: Map = new Map(); // id -> timestamp private readonly MAX_FAILED_IDS = 10000; - constructor( - logRepo: LogRepository, - queueService: QueueService, - pollInterval: number = 5000 - ) { + constructor(logRepo: LogRepository, queueService: QueueService, pollInterval: number = 5000) { this.logRepo = logRepo; this.queueService = queueService; this.pollInterval = pollInterval; @@ -72,7 +68,7 @@ export class QueueConsumer { if (messages.length > 0) { console.log(`[QueueConsumer] Fetched ${messages.length} messages from queue`); const entries = this.parseMessages(messages); - + if (entries.length > 0) { try { const inserted = this.logRepo.insertBatch(entries); @@ -82,13 +78,16 @@ export class QueueConsumer { throw insertError; // Re-throw to trigger retry logic } } - + this.consecutiveErrors = 0; } } catch (error) { this.consecutiveErrors++; - console.error(`[QueueConsumer] Error processing batch (attempt ${this.consecutiveErrors}):`, error); - + console.error( + `[QueueConsumer] Error processing batch (attempt ${this.consecutiveErrors}):`, + error, + ); + if (this.consecutiveErrors >= this.maxConsecutiveErrors) { console.error('[QueueConsumer] Too many consecutive errors, stopping consumer'); this.pollLock = false; @@ -153,7 +152,7 @@ export class QueueConsumer { level: normalizeLevel(raw.level), source: raw.source || 'unknown', message: raw.message || JSON.stringify(raw), - raw: JSON.stringify(raw) + raw: JSON.stringify(raw), }; } @@ -178,7 +177,7 @@ export class QueueConsumer { getStatus(): { isRunning: boolean; consecutiveErrors: number } { return { isRunning: this.isRunning, - consecutiveErrors: this.consecutiveErrors + consecutiveErrors: this.consecutiveErrors, }; } -} \ No newline at end of file +} diff --git a/api/src/services/queueService.ts b/api/src/services/queueService.ts index f5c4e77..eb439f6 100644 --- a/api/src/services/queueService.ts +++ b/api/src/services/queueService.ts @@ -11,7 +11,7 @@ export class QueueService { baseUrl: string, timeout: number = 5000, maxRetries: number = 3, - maxResponseSize: number = 10 * 1024 * 1024 // 10MB default + maxResponseSize: number = 10 * 1024 * 1024, // 10MB default ) { this.baseUrl = baseUrl.replace(/\/$/, ''); this.timeout = timeout; @@ -21,19 +21,19 @@ export class QueueService { async fetchBatch(): Promise { const url = `${this.baseUrl}/api/logs/pending`; - + for (let attempt = 1; attempt <= this.maxRetries; attempt++) { try { const response = await this.httpGet(url); const data = JSON.parse(response) as { messages?: Array<{ id: number; content: string }> }; - + if (!data.messages || !Array.isArray(data.messages)) { return []; } - return data.messages.map(msg => ({ + return data.messages.map((msg) => ({ id: msg.id, - content: msg.content + content: msg.content, })); } catch (error) { if (attempt === this.maxRetries) { @@ -45,20 +45,20 @@ export class QueueService { await this.delay(baseDelay + jitter); } } - + throw new Error('Max retries exceeded'); } async getStats(): Promise { const url = `${this.baseUrl}/api/queue/stats`; - + try { const response = await this.httpGet(url); const data = JSON.parse(response) as { pending?: number; total?: number }; - + return { pending: data.pending || 0, - total: data.total || 0 + total: data.total || 0, }; } catch (error) { return { pending: 0, total: 0 }; @@ -69,30 +69,38 @@ export class QueueService { return new Promise((resolve, reject) => { let settled = false; const safeResolve = (value: string): void => { - if (!settled) { settled = true; resolve(value); } + if (!settled) { + settled = true; + resolve(value); + } }; const safeReject = (error: Error): void => { - if (!settled) { settled = true; reject(error); } + if (!settled) { + settled = true; + reject(error); + } }; const request = http.get(url, { timeout: this.timeout }, (response) => { let data = ''; let receivedSize = 0; - + response.on('data', (chunk: Buffer) => { receivedSize += chunk.length; if (receivedSize > this.maxResponseSize) { request.destroy(); - safeReject(new Error(`Response exceeded maximum size of ${this.maxResponseSize} bytes`)); + safeReject( + new Error(`Response exceeded maximum size of ${this.maxResponseSize} bytes`), + ); return; } data += chunk; }); - + response.on('error', (error) => { safeReject(error); }); - + response.on('end', () => { if (response.statusCode && response.statusCode >= 200 && response.statusCode < 300) { safeResolve(data); @@ -101,11 +109,11 @@ export class QueueService { } }); }); - + request.on('error', (error) => { safeReject(error); }); - + request.on('timeout', () => { request.destroy(); safeReject(new Error('Request timeout')); @@ -114,6 +122,6 @@ export class QueueService { } private delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } -} \ No newline at end of file +} diff --git a/api/src/types/log.ts b/api/src/types/log.ts index c51b796..edf6cac 100644 --- a/api/src/types/log.ts +++ b/api/src/types/log.ts @@ -34,6 +34,8 @@ export interface Alert { } export interface AlertFilter { + q?: string; + source?: string; severity?: string; acknowledged?: boolean; limit?: number; @@ -44,6 +46,7 @@ export interface AlertStats { total: number; bySeverity: Record; unacknowledged: number; + openBySeverity: Record; } export interface QueueMessage { diff --git a/api/src/utils/logNormalizer.ts b/api/src/utils/logNormalizer.ts index fd5bab3..269332d 100644 --- a/api/src/utils/logNormalizer.ts +++ b/api/src/utils/logNormalizer.ts @@ -4,11 +4,11 @@ export function normalizeLevel(level: string | undefined): LogEntry['level'] { if (!level) return 'info'; const normalized = level.toLowerCase(); - + if (['debug', 'dbg'].includes(normalized)) return 'debug'; if (['info', 'information', 'log'].includes(normalized)) return 'info'; if (['warn', 'warning'].includes(normalized)) return 'warn'; if (['error', 'err', 'fatal', 'critical', 'severe'].includes(normalized)) return 'error'; - + return 'info'; } diff --git a/api/src/utils/queryHelpers.ts b/api/src/utils/queryHelpers.ts index 78b08d7..32861d9 100644 --- a/api/src/utils/queryHelpers.ts +++ b/api/src/utils/queryHelpers.ts @@ -1,9 +1,14 @@ -export function parseQueryInt(value: string | undefined, defaultValue: number, min: number, max?: number): number { +export function parseQueryInt( + value: string | undefined, + defaultValue: number, + min: number, + max?: number, +): number { if (!value) return defaultValue; - + const parsed = parseInt(value, 10); if (isNaN(parsed)) return defaultValue; - + if (max !== undefined) { return Math.max(min, Math.min(parsed, max)); } diff --git a/api/tests/helpers/testDb.ts b/api/tests/helpers/testDb.ts index db31782..0c141d7 100644 --- a/api/tests/helpers/testDb.ts +++ b/api/tests/helpers/testDb.ts @@ -1,7 +1,7 @@ import Database from 'better-sqlite3'; import fs from 'fs'; import path from 'path'; -import { v4 as uuidv4 } from 'uuid'; +import { randomUUID as uuidv4 } from 'node:crypto'; import { LogEntry, Alert } from '../../src/types/log'; const TEST_DB_DIR = path.join(__dirname, '..', '.test-data'); diff --git a/api/tests/unit/db/logPage.test.ts b/api/tests/unit/db/logPage.test.ts new file mode 100644 index 0000000..4ff13ce --- /dev/null +++ b/api/tests/unit/db/logPage.test.ts @@ -0,0 +1,63 @@ +import { createTestDb, createSampleLog } from '../../helpers/testDb'; +import { LogRepository } from '../../../src/db/logRepository'; +import { InvalidCursor } from '../../../src/db/logPageReader'; +describe('cursor pages and idempotent delivery', () => { + let resource: ReturnType, repo: LogRepository; + beforeEach(() => { + resource = createTestDb(); + repo = new LogRepository(resource.db); + }); + afterEach(() => resource.cleanup()); + it('does not lose timestamp ties or repeat events when new logs arrive between pages', () => { + const timestamp = '2026-11-04T10:00:00.000Z'; + repo.insertBatch( + Array.from({ length: 60 }, (_, i) => createSampleLog({ id: String(i), timestamp })), + ); + const first = repo.searchPage({ limit: 25 }); + repo.insert(createSampleLog({ id: 'new', timestamp: '2026-11-04T11:00:00Z' })); + const second = repo.searchPage({ limit: 25, cursor: first.nextCursor! }), + third = repo.searchPage({ limit: 25, cursor: second.nextCursor! }); + const ids = [...first.logs, ...second.logs, ...third.logs].map((log) => log.id); + expect(ids).toHaveLength(60); + expect(new Set(ids).size).toBe(60); + expect(ids).not.toContain('new'); + expect(third.hasMore).toBe(false); + }); + it('binds cursors to the actual filters and rejects malformed positions', () => { + repo.insertBatch(Array.from({ length: 3 }, () => createSampleLog({ source: 'auth' }))); + const page = repo.searchPage({ source: 'auth', limit: 1 }); + expect(() => repo.searchPage({ source: 'payments', cursor: page.nextCursor! })).toThrow( + InvalidCursor, + ); + expect(() => repo.searchPage({ cursor: 'not-json' })).toThrow(InvalidCursor); + }); + it('uses the global FTS fallback for rare, older phrases and retains literal phrase semantics', () => { + repo.insert( + createSampleLog({ + id: 'rare', + message: 'connection timeout', + timestamp: '2020-01-01T00:00:00Z', + }), + ); + repo.insertBatch( + Array.from({ length: 10200 }, (_, i) => + createSampleLog({ id: 'normal-' + i, message: 'request completed' }), + ), + ); + expect(repo.searchPage({ q: 'connection timeout' }).logs.map((log) => log.id)).toEqual([ + 'rare', + ]); + expect(repo.searchPage({ q: '" OR "' }).logs).toEqual([]); + }); + it('commits a replayed delivery only once without updating the original row or FTS content', () => { + const log = createSampleLog({ id: 'same', message: 'original timeout' }); + resource.db.transaction(() => repo.insertDeliveredBatch([log, log]))(); + resource.db.transaction(() => + repo.insertDeliveredBatch([{ ...log, message: 'modified replay' }]), + )(); + expect(repo.count()).toBe(1); + expect(repo.getById('same')?.message).toBe('original timeout'); + expect(repo.searchPage({ q: 'original timeout' }).logs).toHaveLength(1); + expect(repo.searchPage({ q: 'modified replay' }).logs).toHaveLength(0); + }); +}); diff --git a/api/tests/unit/services/alertIntegrity.test.ts b/api/tests/unit/services/alertIntegrity.test.ts new file mode 100644 index 0000000..818b1fe --- /dev/null +++ b/api/tests/unit/services/alertIntegrity.test.ts @@ -0,0 +1,29 @@ +import { createTestDb, createSampleLog } from '../../helpers/testDb'; +import { LogRepository } from '../../../src/db/logRepository'; +import { AlertRepository } from '../../../src/db/alertRepository'; +import { AlertService } from '../../../src/services/alertService'; +it('persists a globally scoped threshold alert with a valid log reference and deduplicates real messages', () => { + const resource = createTestDb(); + resource.db.pragma('foreign_keys=ON'); + const logs = new LogRepository(resource.db), + alerts = new AlertRepository(resource.db), + monitor = new AlertService(alerts, logs); + try { + logs.insertBatch(Array.from({ length: 60 }, () => createSampleLog({ level: 'error' }))); + logs.insert(createSampleLog({ level: 'error', timestamp: '2099-01-01T00:00:00Z' })); + monitor.start(); + monitor.stop(); + const page = alerts.findAll({}); + expect(page.total).toBe(1); + expect(logs.getById(page.alerts[0].logId)?.timestamp).not.toContain('2099'); + expect(page.alerts[0].details?.errorCount).toBe(60); + expect(page.alerts[0].details?.source).toBeUndefined(); + monitor.start(); + monitor.stop(); + expect(alerts.findAll({}).total).toBe(1); + expect(resource.db.pragma('foreign_key_check')).toEqual([]); + } finally { + monitor.stop(); + resource.cleanup(); + } +}); diff --git a/api/tests/unit/services/alertService.test.ts b/api/tests/unit/services/alertService.test.ts index d21ac23..f6adec2 100644 --- a/api/tests/unit/services/alertService.test.ts +++ b/api/tests/unit/services/alertService.test.ts @@ -15,8 +15,9 @@ const mockLogRepo = { insert: jest.fn(), insertBatch: jest.fn(), search: jest.fn(), + searchPage: jest.fn().mockReturnValue({logs: [{id: 'retained-evidence'}]}), getById: jest.fn(), - getRecent: jest.fn(), + getRecent: jest.fn().mockReturnValue([{ id: 'retained-evidence' }]), getStats: jest.fn(), deleteOldLogs: jest.fn(), getErrorCountSince: jest.fn().mockReturnValue(0) diff --git a/api/tsconfig.json b/api/tsconfig.json index f44baa9..d3a1624 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -12,8 +12,14 @@ "resolveJsonModule": true, "declaration": true, "declarationMap": true, - "sourceMap": true + "sourceMap": true, + "isolatedModules": true }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] } diff --git a/benchmarks/before-normalization.json b/benchmarks/before-normalization.json new file mode 100644 index 0000000..546e6b9 --- /dev/null +++ b/benchmarks/before-normalization.json @@ -0,0 +1,204 @@ +{ + "recordedAt": "2026-11-04T20:08:03.110Z", + "sourceCommit": "2967d6794b52f3bb213575768cac56bc6f84049c", + "environment": { + "os": "linux", + "arch": "x64", + "node": "v22.23.4", + "logicalCpus": 5 + }, + "durability": { + "queue": "WAL / synchronous FULL", + "index": "WAL / synchronous FULL", + "acknowledgment": "After durable index commit", + "pipeline": "File → C++20 agent → C++20 queue → indexing worker → SQLite FTS5", + "uiAndDetectorIncluded": false + }, + "bulk": { + "logs": 100000, + "secondsIncludingStartup": 5.072276714, + "logsPerSecond": 19715.012732643278 + }, + "searches": [ + { + "label": "literal phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 5.662088999999469, + "p95Ms": 7.285961000001407, + "samples": 30 + }, + { + "label": "common phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 10.428068999999596, + "p95Ms": 18.45621299999948, + "samples": 30 + }, + { + "label": "source and level", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 4.132738000000245, + "p95Ms": 10.29707899999994, + "samples": 30 + } + ], + "paced": { + "offeredLogsPerSecond": 25000, + "generationSeconds": 30.293910371, + "indexedLogs": 757107, + "completionSeconds": 33.752669897, + "achievedLogsPerSecond": 22431.025525103516, + "observations": [ + { + "elapsedSeconds": 1.0062202620000007, + "indexedLogs": 117096, + "pending": 4096 + }, + { + "elapsedSeconds": 2.030189282000001, + "indexedLogs": 143720, + "pending": 2048 + }, + { + "elapsedSeconds": 3.038072269, + "indexedLogs": 170344, + "pending": 4096 + }, + { + "elapsedSeconds": 4.093426108, + "indexedLogs": 188776, + "pending": 4096 + }, + { + "elapsedSeconds": 5.1145548199999995, + "indexedLogs": 196968, + "pending": 10240 + }, + { + "elapsedSeconds": 6.123655025, + "indexedLogs": 227688, + "pending": 0 + }, + { + "elapsedSeconds": 7.132132926000002, + "indexedLogs": 250216, + "pending": 2048 + }, + { + "elapsedSeconds": 8.144653877000001, + "indexedLogs": 270696, + "pending": 6144 + }, + { + "elapsedSeconds": 9.153065696000002, + "indexedLogs": 293224, + "pending": 4096 + }, + { + "elapsedSeconds": 10.162081140000002, + "indexedLogs": 317800, + "pending": 2048 + }, + { + "elapsedSeconds": 11.196635149000002, + "indexedLogs": 334184, + "pending": 12288 + }, + { + "elapsedSeconds": 12.339213306000001, + "indexedLogs": 358760, + "pending": 10240 + }, + { + "elapsedSeconds": 13.351240923, + "indexedLogs": 387432, + "pending": 4096 + }, + { + "elapsedSeconds": 14.371884712, + "indexedLogs": 409960, + "pending": 6144 + }, + { + "elapsedSeconds": 15.394790219999999, + "indexedLogs": 432488, + "pending": 14336 + }, + { + "elapsedSeconds": 16.416766071999998, + "indexedLogs": 463208, + "pending": 8192 + }, + { + "elapsedSeconds": 17.439989813, + "indexedLogs": 489832, + "pending": 6144 + }, + { + "elapsedSeconds": 18.453844236000002, + "indexedLogs": 516456, + "pending": 2048 + }, + { + "elapsedSeconds": 19.470290584999997, + "indexedLogs": 536936, + "pending": 6144 + }, + { + "elapsedSeconds": 20.481464364, + "indexedLogs": 559464, + "pending": 6144 + }, + { + "elapsedSeconds": 21.527676582, + "indexedLogs": 577896, + "pending": 10240 + }, + { + "elapsedSeconds": 22.566759427, + "indexedLogs": 606568, + "pending": 4096 + }, + { + "elapsedSeconds": 23.580766411999996, + "indexedLogs": 633192, + "pending": 2048 + }, + { + "elapsedSeconds": 24.601656901000002, + "indexedLogs": 653672, + "pending": 6144 + }, + { + "elapsedSeconds": 25.629232811, + "indexedLogs": 680296, + "pending": 2048 + }, + { + "elapsedSeconds": 26.645080833, + "indexedLogs": 704872, + "pending": 2048 + }, + { + "elapsedSeconds": 27.672019614999996, + "indexedLogs": 729448, + "pending": 2048 + }, + { + "elapsedSeconds": 29.204645691, + "indexedLogs": 749928, + "pending": 4096 + }, + { + "elapsedSeconds": 30.292925046999997, + "indexedLogs": 772456, + "pending": 0 + } + ], + "drained": true + }, + "dataDirectory": "/workspace/Log-Aggregation-System/.runtime/bench-cGlA9W" +} diff --git a/benchmarks/latest.json b/benchmarks/latest.json new file mode 100644 index 0000000..54488f3 --- /dev/null +++ b/benchmarks/latest.json @@ -0,0 +1,174 @@ +{ + "recordedAt": "2026-11-04T20:53:51.200Z", + "sourceCommit": "4409a1b674a5658a1e45bcc4e0782eb88e319feb", + "environment": { + "os": "linux", + "arch": "x64", + "node": "v22.23.4", + "logicalCpus": 5 + }, + "durability": { + "queue": "WAL / synchronous FULL", + "index": "WAL / synchronous FULL", + "acknowledgment": "After durable index commit", + "pipeline": "File → C++20 agent → C++20 queue → indexing worker → SQLite FTS5", + "uiAndDetectorIncluded": false + }, + "bulk": { + "logs": 100000, + "secondsIncludingStartup": 5.8206139839999995, + "logsPerSecond": 17180.31813737951 + }, + "searches": [ + { + "label": "literal phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 6.561682999999903, + "p95Ms": 18.204287000000477, + "samples": 30 + }, + { + "label": "common phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 8.381303000000116, + "p95Ms": 12.149884000000384, + "samples": 30 + }, + { + "label": "source and level", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 3.181116000001566, + "p95Ms": 10.301226999999926, + "samples": 30 + } + ], + "paced": { + "offeredLogsPerSecond": 25000, + "generationSeconds": 33.890452072, + "indexedLogs": 609191, + "completionSeconds": 50.887192677, + "achievedLogsPerSecond": 11971.401210256627, + "observations": [ + { + "elapsedSeconds": 1.0127118200000005, + "indexedLogs": 117007, + "pending": 2048 + }, + { + "elapsedSeconds": 2.0271968099999995, + "indexedLogs": 141583, + "pending": 2048 + }, + { + "elapsedSeconds": 3.0371749230000007, + "indexedLogs": 166159, + "pending": 4096 + }, + { + "elapsedSeconds": 4.049653727999999, + "indexedLogs": 186639, + "pending": 6144 + }, + { + "elapsedSeconds": 5.068857447999999, + "indexedLogs": 213263, + "pending": 2048 + }, + { + "elapsedSeconds": 6.079629166999999, + "indexedLogs": 229647, + "pending": 8192 + }, + { + "elapsedSeconds": 7.090849967999999, + "indexedLogs": 250127, + "pending": 8192 + }, + { + "elapsedSeconds": 8.115580439, + "indexedLogs": 274703, + "pending": 6144 + }, + { + "elapsedSeconds": 9.161876719, + "indexedLogs": 295183, + "pending": 4096 + }, + { + "elapsedSeconds": 10.246604374999999, + "indexedLogs": 299279, + "pending": 16384 + }, + { + "elapsedSeconds": 11.260124928000002, + "indexedLogs": 313615, + "pending": 20480 + }, + { + "elapsedSeconds": 12.278511155, + "indexedLogs": 338191, + "pending": 18432 + }, + { + "elapsedSeconds": 13.307941877999998, + "indexedLogs": 362767, + "pending": 18432 + }, + { + "elapsedSeconds": 14.326951863999998, + "indexedLogs": 387343, + "pending": 14336 + }, + { + "elapsedSeconds": 15.35110513, + "indexedLogs": 411919, + "pending": 18432 + }, + { + "elapsedSeconds": 16.362766301, + "indexedLogs": 434447, + "pending": 8192 + }, + { + "elapsedSeconds": 17.387248008, + "indexedLogs": 461071, + "pending": 2048 + }, + { + "elapsedSeconds": 18.417125076, + "indexedLogs": 481551, + "pending": 2048 + }, + { + "elapsedSeconds": 19.448029199, + "indexedLogs": 493839, + "pending": 10240 + }, + { + "elapsedSeconds": 20.466287289, + "indexedLogs": 522511, + "pending": 2048 + }, + { + "elapsedSeconds": 21.497125583000003, + "indexedLogs": 530703, + "pending": 12288 + }, + { + "elapsedSeconds": 25.588940094, + "indexedLogs": 538895, + "pending": 10240 + }, + { + "elapsedSeconds": 33.880612576, + "indexedLogs": 545039, + "pending": 14336 + } + ], + "drained": true + }, + "dataDirectory": "/workspace/Log-Aggregation-System/.runtime/bench-i09Wzr" +} diff --git a/benchmarks/normalization-initial.json b/benchmarks/normalization-initial.json new file mode 100644 index 0000000..6491326 --- /dev/null +++ b/benchmarks/normalization-initial.json @@ -0,0 +1,204 @@ +{ + "recordedAt": "2026-11-04T20:51:06.181Z", + "sourceCommit": "0eedf921d403dc859ad8649113678ea51dc6823b", + "environment": { + "os": "linux", + "arch": "x64", + "node": "v22.23.4", + "logicalCpus": 5 + }, + "durability": { + "queue": "WAL / synchronous FULL", + "index": "WAL / synchronous FULL", + "acknowledgment": "After durable index commit", + "pipeline": "File → C++20 agent → C++20 queue → indexing worker → SQLite FTS5", + "uiAndDetectorIncluded": false + }, + "bulk": { + "logs": 100000, + "secondsIncludingStartup": 5.946882258, + "logsPerSecond": 16815.53386490471 + }, + "searches": [ + { + "label": "literal phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 6.634808999999223, + "p95Ms": 9.262254000001121, + "samples": 30 + }, + { + "label": "common phrase", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 8.876390999999785, + "p95Ms": 12.498170999999274, + "samples": 30 + }, + { + "label": "source and level", + "endpoint": "/api/logs/page", + "corpusLogs": 100000, + "p50Ms": 3.6829629999992903, + "p95Ms": 5.74949800000104, + "samples": 30 + } + ], + "paced": { + "offeredLogsPerSecond": 25000, + "generationSeconds": 30.097925891, + "indexedLogs": 751978, + "completionSeconds": 115.94137510399999, + "achievedLogsPerSecond": 6485.846828411963, + "observations": [ + { + "elapsedSeconds": 1.0236593759999997, + "indexedLogs": 117172, + "pending": 2048 + }, + { + "elapsedSeconds": 2.0332026989999994, + "indexedLogs": 137652, + "pending": 2048 + }, + { + "elapsedSeconds": 3.0447523889999992, + "indexedLogs": 160180, + "pending": 2048 + }, + { + "elapsedSeconds": 4.058047286999999, + "indexedLogs": 180660, + "pending": 4096 + }, + { + "elapsedSeconds": 5.070731192000001, + "indexedLogs": 203188, + "pending": 4096 + }, + { + "elapsedSeconds": 6.083164914999999, + "indexedLogs": 225716, + "pending": 4096 + }, + { + "elapsedSeconds": 7.0933643360000005, + "indexedLogs": 250292, + "pending": 2048 + }, + { + "elapsedSeconds": 8.105150692999997, + "indexedLogs": 274868, + "pending": 4096 + }, + { + "elapsedSeconds": 9.140638743999999, + "indexedLogs": 297396, + "pending": 6144 + }, + { + "elapsedSeconds": 10.186799517999997, + "indexedLogs": 326068, + "pending": 2048 + }, + { + "elapsedSeconds": 11.202848563999998, + "indexedLogs": 350644, + "pending": 4096 + }, + { + "elapsedSeconds": 12.220129006, + "indexedLogs": 379316, + "pending": 4096 + }, + { + "elapsedSeconds": 13.235876481000002, + "indexedLogs": 401844, + "pending": 4096 + }, + { + "elapsedSeconds": 14.298437561, + "indexedLogs": 410036, + "pending": 4096 + }, + { + "elapsedSeconds": 15.310226714999999, + "indexedLogs": 430516, + "pending": 6144 + }, + { + "elapsedSeconds": 16.32948475, + "indexedLogs": 455092, + "pending": 6144 + }, + { + "elapsedSeconds": 17.345240621, + "indexedLogs": 479668, + "pending": 4096 + }, + { + "elapsedSeconds": 18.383794706, + "indexedLogs": 500148, + "pending": 2048 + }, + { + "elapsedSeconds": 19.406116206, + "indexedLogs": 518580, + "pending": 2048 + }, + { + "elapsedSeconds": 20.429622065999997, + "indexedLogs": 537012, + "pending": 6144 + }, + { + "elapsedSeconds": 21.459781588, + "indexedLogs": 557492, + "pending": 6144 + }, + { + "elapsedSeconds": 22.479128213000003, + "indexedLogs": 563636, + "pending": 10240 + }, + { + "elapsedSeconds": 23.607467637000003, + "indexedLogs": 571828, + "pending": 12288 + }, + { + "elapsedSeconds": 24.633516020000002, + "indexedLogs": 580020, + "pending": 14336 + }, + { + "elapsedSeconds": 25.673865443000004, + "indexedLogs": 596404, + "pending": 12288 + }, + { + "elapsedSeconds": 26.726014088000003, + "indexedLogs": 612788, + "pending": 6144 + }, + { + "elapsedSeconds": 27.785835507000005, + "indexedLogs": 629172, + "pending": 4096 + }, + { + "elapsedSeconds": 28.900338417, + "indexedLogs": 633268, + "pending": 6144 + }, + { + "elapsedSeconds": 30.097574473, + "indexedLogs": 637364, + "pending": 6144 + } + ], + "drained": true + }, + "dataDirectory": "/workspace/Log-Aggregation-System/.runtime/bench-gsnUNg" +} diff --git a/docker/Dockerfile.agent b/docker/Dockerfile.agent index 74ff1f8..90314fe 100644 --- a/docker/Dockerfile.agent +++ b/docker/Dockerfile.agent @@ -1,21 +1,19 @@ -FROM gcc:14 as builder - -WORKDIR /app - -RUN apt-get update && apt-get install -y cmake - -COPY CMakeLists.txt . -COPY include/ include/ -COPY src/ src/ - -RUN mkdir build && cd build && cmake .. && cmake --build . - +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends build-essential cmake git ca-certificates && rm -rf /var/lib/apt/lists/* +WORKDIR /src +COPY CMakeLists.txt ./ +COPY native native +COPY agent agent +COPY queue queue +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export SSL_CERT_FILE=/run/secrets/ca_bundle GIT_SSL_CAINFO=/run/secrets/ca_bundle; ca_args=-DCMAKE_TLS_CAINFO=/run/secrets/ca_bundle; else ca_args=; fi; \ + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release $ca_args && cmake --build build --parallel 2 FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* && mkdir -p /data /events && chown 1000:1000 /data /events +COPY --from=build /src/build/bin/log_agent /src/build/bin/log_queue /usr/local/bin/ +USER 1000:1000 +CMD ["log_queue","--host","0.0.0.0","--db","/data/queue.db"] -WORKDIR /app - -RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /app/build/log_agent /app/log_agent - -CMD ["./log_agent"] +ENTRYPOINT ["log_agent"] +CMD ["--file","/events/events.ndjson","--checkpoint","/data/agent.json"] diff --git a/docker/Dockerfile.anomaly b/docker/Dockerfile.anomaly index b73967f..5ad6c20 100644 --- a/docker/Dockerfile.anomaly +++ b/docker/Dockerfile.anomaly @@ -1,16 +1,11 @@ -FROM python:3.11-slim - +# syntax=docker/dockerfile:1 +FROM python:3.12-slim-bookworm WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc \ - && rm -rf /var/lib/apt/lists/* - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY src/ src/ - -EXPOSE 5000 - -CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "5000"] +COPY anomaly/requirements.txt ./ +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export PIP_CERT=/run/secrets/ca_bundle; fi; pip install --no-cache-dir -r requirements.txt +COPY --chown=1000:1000 anomaly/src src +RUN mkdir -p /data && chown 1000:1000 /data +ENV DB_PATH=/data/logs.db MODEL_PATH=/data/model.pkl OPENBLAS_NUM_THREADS=2 OMP_NUM_THREADS=2 +USER 1000:1000 +CMD ["python","-m","uvicorn","src.main:app","--host","0.0.0.0","--port","5000"] diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api index f3f16a2..09b37e7 100644 --- a/docker/Dockerfile.api +++ b/docker/Dockerfile.api @@ -1,26 +1,20 @@ -FROM node:20-alpine as builder - +# syntax=docker/dockerfile:1 +FROM node:22-bookworm-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ && rm -rf /var/lib/apt/lists/* WORKDIR /app - -COPY package*.json ./ -RUN npm ci - -COPY tsconfig.json ./ -COPY scripts/ scripts/ -COPY src/ src/ - -RUN npm run build - -FROM node:20-alpine - +COPY api/package*.json ./ +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export NODE_EXTRA_CA_CERTS=/run/secrets/ca_bundle; fi; npm ci +COPY api/src src +COPY api/tsconfig.json ./ +RUN npm run build && npm prune --omit=dev +FROM node:22-bookworm-slim WORKDIR /app - -COPY package*.json ./ -RUN npm ci --omit=dev - -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/scripts ./scripts - -EXPOSE 3000 - -CMD ["npm", "start"] +COPY --from=build --chown=node:node /app/node_modules node_modules +COPY --from=build --chown=node:node /app/dist dist +COPY --from=build --chown=node:node /app/package.json ./ +COPY --chown=node:node scripts/simulator.mjs ./ +RUN mkdir -p /data /events && chown node:node /data /events +ENV NODE_ENV=production HOST=0.0.0.0 DB_PATH=/data/logs.db +USER node +CMD ["node","dist/index.js"] diff --git a/docker/Dockerfile.frontend b/docker/Dockerfile.frontend index 1187b78..621b791 100644 --- a/docker/Dockerfile.frontend +++ b/docker/Dockerfile.frontend @@ -1,29 +1,19 @@ -FROM node:20-alpine as builder - +# syntax=docker/dockerfile:1 +FROM node:22-bookworm-slim AS build WORKDIR /app - -COPY package*.json ./ -RUN npm ci - -COPY tsconfig.json ./ -COPY next.config.ts ./ -COPY tailwind.config.js ./ -COPY next-env.d.ts ./ -COPY src/ src/ - +COPY frontend/package*.json ./ +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export NODE_EXTRA_CA_CERTS=/run/secrets/ca_bundle; fi; npm ci +COPY frontend . +ARG API_INTERNAL_URL=http://api:3000 +ARG ANOMALY_INTERNAL_URL=http://anomaly:5000 +ENV API_INTERNAL_URL=$API_INTERNAL_URL ANOMALY_INTERNAL_URL=$ANOMALY_INTERNAL_URL NEXT_TELEMETRY_DISABLED=1 RUN npm run build - -FROM node:20-alpine - +FROM node:22-bookworm-slim WORKDIR /app - -COPY package*.json ./ -RUN npm ci --omit=dev - -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/next.config.ts ./next.config.ts - -EXPOSE 3000 - -CMD ["npm", "start"] +COPY --from=build --chown=node:node /app/.next/standalone ./ +COPY --from=build --chown=node:node /app/.next/static .next/static +ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3001 NEXT_TELEMETRY_DISABLED=1 +USER node +EXPOSE 3001 +CMD ["node","server.js"] diff --git a/docker/Dockerfile.native b/docker/Dockerfile.native new file mode 100644 index 0000000..8eb92d5 --- /dev/null +++ b/docker/Dockerfile.native @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends build-essential cmake git ca-certificates && rm -rf /var/lib/apt/lists/* +WORKDIR /src +COPY CMakeLists.txt ./ +COPY native native +COPY agent agent +COPY queue queue +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export SSL_CERT_FILE=/run/secrets/ca_bundle GIT_SSL_CAINFO=/run/secrets/ca_bundle; ca_args=-DCMAKE_TLS_CAINFO=/run/secrets/ca_bundle; else ca_args=; fi; \ + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release $ca_args && cmake --build build --parallel 2 +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* && mkdir -p /data /events && chown 1000:1000 /data /events +COPY --from=build /src/build/bin/log_agent /src/build/bin/log_queue /usr/local/bin/ +USER 1000:1000 +CMD ["log_queue","--host","0.0.0.0","--db","/data/queue.db"] diff --git a/docker/Dockerfile.queue b/docker/Dockerfile.queue index 6c21ddf..aa0194c 100644 --- a/docker/Dockerfile.queue +++ b/docker/Dockerfile.queue @@ -1,23 +1,19 @@ -FROM gcc:14 as builder - -WORKDIR /app - -RUN apt-get update && apt-get install -y cmake - -COPY CMakeLists.txt . -COPY include/ include/ -COPY src/ src/ - -RUN mkdir build && cd build && cmake .. && cmake --build . - +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS build +RUN apt-get update && apt-get install -y --no-install-recommends build-essential cmake git ca-certificates && rm -rf /var/lib/apt/lists/* +WORKDIR /src +COPY CMakeLists.txt ./ +COPY native native +COPY agent agent +COPY queue queue +RUN --mount=type=secret,id=ca_bundle \ + if [ -s /run/secrets/ca_bundle ]; then export SSL_CERT_FILE=/run/secrets/ca_bundle GIT_SSL_CAINFO=/run/secrets/ca_bundle; ca_args=-DCMAKE_TLS_CAINFO=/run/secrets/ca_bundle; else ca_args=; fi; \ + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release $ca_args && cmake --build build --parallel 2 FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* && mkdir -p /data /events && chown 1000:1000 /data /events +COPY --from=build /src/build/bin/log_agent /src/build/bin/log_queue /usr/local/bin/ +USER 1000:1000 +CMD ["log_queue","--host","0.0.0.0","--db","/data/queue.db"] -WORKDIR /app - -RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /app/build/log_queue /app/log_queue - -EXPOSE 8081 - -CMD ["./log_queue"] +ENTRYPOINT ["log_queue"] +CMD ["--host","0.0.0.0","--db","/data/queue.db"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 017aa32..40d7857 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,79 +1,75 @@ services: - agent: - build: - context: ../agent - dockerfile: ../docker/Dockerfile.agent - volumes: - - ../data/logs:/var/log/app - depends_on: - - queue - networks: - - log-network - queue: build: - context: ../queue - dockerfile: ../docker/Dockerfile.queue - ports: - - "8081:8081" - volumes: - - ../data/queue:/data - networks: - - log-network - + context: .. + dockerfile: docker/Dockerfile.native + volumes: [queue-data:/data] + healthcheck: + test: [CMD, curl, -fsS, http://127.0.0.1:8081/health] + interval: 2s + timeout: 2s + retries: 30 api: build: - context: ../api - dockerfile: ../docker/Dockerfile.api - ports: - - "3000:3000" - volumes: - - ../data/db:/app/data + context: .. + dockerfile: docker/Dockerfile.api + environment: + QUEUE_URL: http://queue:8081 + DB_PATH: /data/logs.db + HOST: 0.0.0.0 + volumes: [log-data:/data] depends_on: - - queue - networks: - - log-network + queue: {condition: service_healthy} + healthcheck: + test: [CMD, node, -e, "fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] + interval: 2s + timeout: 2s + retries: 30 + simulator: + build: + context: .. + dockerfile: docker/Dockerfile.api environment: - - NODE_ENV=production - - QUEUE_URL=http://queue:8081 - - DB_PATH=/app/data/logs.db - - anomaly: + SIM_RATE: ${SIM_RATE:-5000} + command: [node, --input-type=module, -e, "import {generate} from './simulator.mjs'; await generate('/events/events.ndjson',{rate:Number(process.env.SIM_RATE)});"] + volumes: [events:/events] + agent: build: - context: ../anomaly - dockerfile: ../docker/Dockerfile.anomaly - ports: - - "5000:5000" - volumes: - - ../data/db:/app/data + context: .. + dockerfile: docker/Dockerfile.native + command: [log_agent, --file, /events/events.ndjson, --checkpoint, /data/agent.json, --queue-host, queue, --queue-port, '8081'] + volumes: [events:/events, agent-state:/data] depends_on: - - api - networks: - - log-network + queue: {condition: service_healthy} + anomaly: + build: + context: .. + dockerfile: docker/Dockerfile.anomaly environment: - - DB_PATH=/app/data/logs.db - - MODEL_PATH=/app/data/anomaly_model.pkl - - POLL_INTERVAL_SECONDS=30 - - LOG_LEVEL=INFO - + POLL_INTERVAL_SECONDS: '2' + BATCH_SIZE: '5000' + volumes: [log-data:/data] + depends_on: + api: {condition: service_healthy} + healthcheck: + test: [CMD, python, -c, "import urllib.request; urllib.request.urlopen('http://127.0.0.1:5000/health', timeout=1)"] + interval: 2s + timeout: 2s + retries: 30 frontend: build: - context: ../frontend - dockerfile: ../docker/Dockerfile.frontend - ports: - - "3001:3000" + context: .. + dockerfile: docker/Dockerfile.frontend + ports: ['127.0.0.1:3001:3001'] depends_on: - - api - networks: - - log-network - environment: - - NEXT_PUBLIC_API_URL=http://localhost:3000 - -networks: - log-network: - driver: bridge - + api: {condition: service_healthy} + healthcheck: + test: [CMD, node, -e, "fetch('http://127.0.0.1:3001/').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] + interval: 2s + timeout: 2s + retries: 30 volumes: - logs: - queue: - db: + queue-data: + log-data: + events: + agent-state: diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json deleted file mode 100644 index bffb357..0000000 --- a/frontend/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..13f6fe9 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,8 @@ +import { FlatCompat } from '@eslint/eslintrc'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +const compat = new FlatCompat({ baseDirectory: path.dirname(fileURLToPath(import.meta.url)) }); +export default [ + ...compat.extends('next/core-web-vitals', 'next/typescript'), + { ignores: ['.next/**', 'node_modules/**'] }, +]; diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 94647ad..55699cc 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,7 +1,15 @@ import type { NextConfig } from 'next'; - -const nextConfig: NextConfig = { +const api = process.env.API_INTERNAL_URL || 'http://127.0.0.1:3000', + detector = process.env.ANOMALY_INTERNAL_URL || 'http://127.0.0.1:5000'; +const config: NextConfig = { output: 'standalone', + devIndicators: false, + async rewrites() { + return [ + { source: '/api/:path*', destination: api + '/api/:path*' }, + { source: '/pipeline-metrics', destination: api + '/metrics' }, + { source: '/detector-status', destination: detector + '/stats' }, + ]; + }, }; - -export default nextConfig; +export default config; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 99c782f..9c32813 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,19 +8,187 @@ "name": "log-aggregation-frontend", "version": "1.0.0", "dependencies": { - "d3": "^7.9.0", "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "@types/d3": "^7.4.3", + "@testing-library/react": "^16.3.3", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "eslint": "^9.0.0", "eslint-config-next": "^15.0.0", - "typescript": "^5.5.0" + "jsdom": "^26.1.0", + "prettier": "^3.9.10", + "typescript": "^5.5.0", + "vitest": "^5.1.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.13.tgz", + "integrity": "sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/@emnapi/core": { @@ -36,9 +204,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "license": "MIT", "optional": true, "dependencies": { @@ -201,29 +369,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -263,9 +445,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -275,19 +457,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -297,19 +479,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -323,9 +524,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -339,9 +540,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -355,9 +556,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -371,9 +572,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -387,9 +588,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -403,9 +604,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -419,9 +620,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -435,9 +636,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -451,9 +652,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -467,9 +668,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -479,19 +680,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -501,19 +702,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -523,19 +724,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -545,19 +746,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -567,19 +768,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -589,19 +790,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -611,19 +812,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -633,38 +834,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -674,16 +891,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -693,16 +910,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -712,12 +929,40 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -732,15 +977,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", - "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.29.tgz", + "integrity": "sha512-9SrTiTsgORa+KfhlXXAGC1eSs847E+d2MQTPUX+n7R5XA8g/B6ZitfL6GoYHNMLwG3Hzs1wjDH0/ME5WrGMqBQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.12.tgz", - "integrity": "sha512-+ZRSDFTv4aC96aMb5E41rMjysx8ApkryevnvEYZvPZO52KvkqP5rNExLUXJFr9P4s0f3oqNQR6vopCZsPWKDcQ==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.29.tgz", + "integrity": "sha512-d3dH0WVbXr+W/3f/TMU6fl7vHJBuok7ugMCDPfRnHhCLl8ppAtFbfXgVHpnLrUirHN4vE5GkTMnId3Fmxv9UCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -748,9 +993,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", - "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.29.tgz", + "integrity": "sha512-vozRDuM23T0faRR0O3oN1eRqjEqUue4Eljhkb9f7urQgzmyzve7j3atzTv3Z1AnjQ9IeVY0X3aS1/an56TOiTg==", "cpu": [ "arm64" ], @@ -764,9 +1009,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", - "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.29.tgz", + "integrity": "sha512-FzrcEzMZVcp/SqFXCW5SXyPil10bB9l5wpTBapEs9ZMqke3W+hyC5mNAdAa1divn+4SZ7LD5BtK67XvK7SucWQ==", "cpu": [ "x64" ], @@ -780,9 +1025,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", - "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.29.tgz", + "integrity": "sha512-FWbWE5P313Yy5062vgY9UspNz/t6Ve/s35X9v6YU7ilsbhqKGabPBPIWcMEPhyomeRdwfv19NAITkU2FFIhGwA==", "cpu": [ "arm64" ], @@ -796,9 +1041,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", - "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.29.tgz", + "integrity": "sha512-xjTHrVisb9GeDSNFOwJI+4UJrKdwlpjz95q8sEvQh46yUp3+JikNcf26pode/HlHktSdJwcHx3iVMCjfPcnU+A==", "cpu": [ "arm64" ], @@ -812,9 +1057,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", - "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.29.tgz", + "integrity": "sha512-5WqW7kHscT02wyF8TlbYv9+zx3KhnQeZwk+FkTCr1r7TuvMrqqOZ3RDkdDKl3O0M0Er3VuQEzoEIrj8VX5fjaw==", "cpu": [ "x64" ], @@ -828,9 +1073,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", - "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.29.tgz", + "integrity": "sha512-I0QJ1Be9vtdq5FABhNcSbkELllA91WnhG+m4BjJHCn1bXAIL8AuDABIKIJ7Bz84T/sIkZP6TvOEWJcKckrX3yQ==", "cpu": [ "x64" ], @@ -844,9 +1089,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", - "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.29.tgz", + "integrity": "sha512-q4fQFWtR7nH7ZDhU8RIvwNW0S/nHImaZkoOSw7rOXFgoP51HTesm8/8P/w3ACjc9GxqytXj0LZZesykK3arFBw==", "cpu": [ "arm64" ], @@ -860,9 +1105,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", - "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.29.tgz", + "integrity": "sha512-putWQ+CTEJunAPIPDPfb06Kc4emhYpdM5FoCDHSkC/ym7Dy2xOBMPgBjBbRkw2Hp5/550ACYxCmUtV4k4ui3Iw==", "cpu": [ "x64" ], @@ -923,335 +1168,419 @@ "node": ">=12.4.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", - "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", "dev": true, - "license": "MIT" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/geojson": "*" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-color": "*" + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/d3-time": "*" + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, "license": "MIT" }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", "dependencies": { - "@types/d3-path": "*" + "tslib": "^2.8.0" } }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "node_modules/@testing-library/dom": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", + "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "node_modules/@testing-library/react": { + "version": "16.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.3.tgz", + "integrity": "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/d3-selection": "*" + "tslib": "^2.4.0" } }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", "dev": true, "license": "MIT" }, @@ -1339,16 +1668,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz", + "integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", "debug": "^4.4.3" }, "engines": { @@ -1360,18 +1689,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz", + "integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.72.1", + "@typescript-eslint/types": "^8.72.1", "debug": "^4.4.3" }, "engines": { @@ -1382,18 +1711,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz", + "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1403,10 +1732,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz", + "integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==", "dev": true, "license": "MIT", "engines": { @@ -1417,64 +1746,60 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz", + "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz", + "integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.72.1", + "@typescript-eslint/tsconfig-utils": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz", + "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "@typescript-eslint/types": "8.72.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1482,12 +1807,9 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "node_modules/@typescript-eslint/parser/node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", @@ -1497,27 +1819,40 @@ "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -1526,15 +1861,178 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils": { + "node_modules/@typescript-eslint/project-service": { "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, @@ -1850,6 +2348,44 @@ "win32" ] }, + "node_modules/@vitest/mocker": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.1.0.tgz", + "integrity": "sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^1.4.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9 || ^3.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.1.0.tgz", + "integrity": "sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -1873,6 +2409,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -1890,6 +2436,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2083,6 +2640,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2172,563 +2739,177 @@ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001776", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", - "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, + "node_modules/caniuse-lite": { + "version": "1.0.30001776", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", + "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-time": "1 - 3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=12" + "node": ">=7.0.0" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" + "node": ">= 8" } }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -2736,6 +2917,20 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -2808,6 +3003,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2851,21 +3053,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" } }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -2883,6 +3087,14 @@ "node": ">=0.10.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2905,6 +3117,19 @@ "dev": true, "license": "MIT" }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -3022,6 +3247,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3156,13 +3388,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.12.tgz", - "integrity": "sha512-ktW3XLfd+ztEltY5scJNjxjHwtKWk6vU2iwzZqSN09UsbBmMeE/cVlJ1yESg6Yx5LW7p/Z8WzUAgYXGLEmGIpg==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.29.tgz", + "integrity": "sha512-44lyVuWyyuYgLeuJvuHSMcvHsfC/JLae6C2xCdi6neWAxk94vKm5qGBFmTrGQavQ2JIL2Jrll16incpNlKOJbQ==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.12", + "@next/eslint-plugin-next": "15.5.29", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -3516,6 +3748,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3526,6 +3768,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3578,9 +3830,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "dev": true, "license": "ISC", "dependencies": { @@ -3645,9 +3897,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -3667,6 +3919,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3938,10 +4206,52 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -4002,15 +4312,6 @@ "node": ">= 0.4" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4268,6 +4569,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4453,10 +4761,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4465,6 +4783,46 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4559,6 +4917,279 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4595,6 +5226,34 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4660,9 +5319,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "funding": [ { "type": "github", @@ -4701,12 +5360,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", - "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", + "version": "15.5.29", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.29.tgz", + "integrity": "sha512-93AMszTCdi9Az7fD5VzcWy46SHCdQF45+i53K0LpDc4dDXCwB9P1SxP1wBYZKW6dF3HJnz41jyEni1vmgCluiw==", "license": "MIT", "dependencies": { - "@next/env": "15.5.12", + "@next/env": "15.5.29", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -4719,15 +5378,15 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.12", - "@next/swc-darwin-x64": "15.5.12", - "@next/swc-linux-arm64-gnu": "15.5.12", - "@next/swc-linux-arm64-musl": "15.5.12", - "@next/swc-linux-x64-gnu": "15.5.12", - "@next/swc-linux-x64-musl": "15.5.12", - "@next/swc-win32-arm64-msvc": "15.5.12", - "@next/swc-win32-x64-msvc": "15.5.12", - "sharp": "^0.34.3" + "@next/swc-darwin-arm64": "15.5.29", + "@next/swc-darwin-x64": "15.5.29", + "@next/swc-linux-arm64-gnu": "15.5.29", + "@next/swc-linux-arm64-musl": "15.5.29", + "@next/swc-linux-x64-gnu": "15.5.29", + "@next/swc-linux-x64-musl": "15.5.29", + "@next/swc-win32-arm64-msvc": "15.5.29", + "@next/swc-win32-x64-msvc": "15.5.29", + "sharp": "^0.34.3 || ^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -4781,6 +5440,13 @@ "semver": "bin/semver.js" } }, + "node_modules/nwsapi": { + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz", + "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4904,6 +5570,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4985,6 +5665,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5019,9 +5712,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -5042,9 +5735,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "funding": [ { "type": "opencollective", @@ -5061,9 +5754,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5076,9 +5769,63 @@ "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5246,11 +5993,47 @@ "node": ">=0.10.0" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" + "node_modules/rolldown": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -5276,12 +6059,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -5341,8 +6118,22 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5350,9 +6141,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", + "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", "devOptional": true, "license": "ISC", "bin": { @@ -5412,48 +6203,53 @@ } }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shebang-command": { @@ -5556,9 +6352,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5571,6 +6367,13 @@ "dev": true, "license": "MIT" }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5770,15 +6573,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.3.tgz", + "integrity": "sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -5806,9 +6636,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -5818,6 +6648,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5831,10 +6681,36 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -6039,6 +6915,256 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", + "esbuild": "^0.27.0 || ^0.28.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.1.0.tgz", + "integrity": "sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.1.0", + "chai": "^6.3.0", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.4.3", + "obug": "^3.0.0", + "picomatch": "^4.0.7", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.1.0", + "@vitest/browser-preview": "5.1.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.1.0", + "@vitest/coverage-v8": "5.1.0", + "@vitest/ui": "5.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6144,6 +7270,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6154,6 +7293,45 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.22.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz", + "integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d46b767..f532faa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,21 +6,27 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "eslint src", + "test": "vitest run" }, "dependencies": { - "d3": "^7.9.0", "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "@types/d3": "^7.4.3", + "@testing-library/react": "^16.3.3", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "eslint": "^9.0.0", "eslint-config-next": "^15.0.0", - "typescript": "^5.5.0" + "jsdom": "^26.1.0", + "prettier": "^3.9.10", + "typescript": "^5.5.0", + "vitest": "^5.1.0" + }, + "overrides": { + "postcss": "^8.5.32" } } diff --git a/frontend/src/app/alerts/page.tsx b/frontend/src/app/alerts/page.tsx new file mode 100644 index 0000000..de17521 --- /dev/null +++ b/frontend/src/app/alerts/page.tsx @@ -0,0 +1,2 @@ +import AlertsPage from '../../components/AlertsPage'; +export default AlertsPage; diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index de11f62..a6947fc 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,527 +1,1920 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - :root { - --dd-bg: #0a0f1a; - --dd-bg-2: #101726; - --dd-panel: rgba(18, 25, 41, 0.92); - --dd-panel-soft: rgba(25, 34, 56, 0.85); - --dd-border: rgba(148, 163, 184, 0.14); - --dd-text: #ebf2ff; - --dd-muted: #94a8ca; - --dd-cyan: #66e3ff; - --dd-indigo: #7895ff; - --dd-amber: #f7bf5f; - --dd-rose: #ff6e86; - --dd-shadow: 0 30px 80px rgba(0, 0, 0, 0.34); -} - + color-scheme: dark; + --bg: #0b100f; + --panel: #101716; + --line: #293630; + --line-soft: #1d2924; + --text: #dce4e0; + --muted: #8b9d93; + --lime: #b9ed7c; + --amber: #e8bd66; + --red: #ff796b; + --blue: #8cb5ec; + --mono: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', monospace; +} * { box-sizing: border-box; } - html, body { + margin: 0; min-height: 100%; + background: var(--bg); } - body { - margin: 0; - color: var(--dd-text); - background: - radial-gradient(circle at 15% 10%, rgba(120, 149, 255, 0.18), transparent 18%), - radial-gradient(circle at 85% 12%, rgba(102, 227, 255, 0.1), transparent 20%), - linear-gradient(180deg, #080d16 0%, #0a101a 40%, #0b111d 100%); - font-family: "Segoe UI", "Inter", sans-serif; -} - + color: var(--text); + font: + 13px/1.5 'Segoe UI', + Inter, + Arial, + sans-serif; + background: radial-gradient(ellipse at 12% 0%, #12201940, transparent 50%), var(--bg); +} +button, +input, +select { + font: inherit; + color: inherit; +} +button, +a { + touch-action: manipulation; +} +button { + cursor: pointer; + border: 0; + background: transparent; +} +button:disabled { + cursor: default; + opacity: 0.4; +} a { color: inherit; text-decoration: none; } - -.app-shell { - min-height: 100vh; - padding: 24px; +button:focus-visible, +a:focus-visible, +select:focus-visible, +input:focus-visible, +summary:focus-visible, +[tabindex]:focus-visible { + outline: 2px solid var(--lime); + outline-offset: 3px; +} +h1, +h2, +h3, +p { + margin: 0; } - -.dd-shell { - max-width: 1480px; - margin: 0 auto; - display: grid; - grid-template-columns: 250px 1fr; - gap: 22px; -} - -.dd-nav, -.dd-topbar, -.dd-stat-card, -.dd-panel { - border: 1px solid var(--dd-border); - background: linear-gradient(180deg, rgba(17, 23, 37, 0.96), rgba(12, 17, 28, 0.94)); - box-shadow: var(--dd-shadow); -} - -.dd-nav { - border-radius: 28px; - padding: 22px 18px; +h2 { + font-size: 14px; + font-weight: 600; +} +h3 { + font-size: 13px; + font-weight: 500; +} +svg { + flex-shrink: 0; +} +strong { + font-weight: 600; +} +select { + background: #111917; + border: 1px solid var(--line); + border-radius: 5px; + padding: 8px 28px 8px 12px; + min-height: 36px; + max-width: 230px; +} +input { + background: transparent; + border: 1px solid var(--line); + border-radius: 5px; + padding: 8px 10px; + min-width: 0; +} +input::placeholder { + color: #82968b; +} +table { + width: 100%; + border-collapse: collapse; + text-align: left; +} +th { + color: #9facaa; + font: 10px var(--mono); + text-transform: uppercase; + letter-spacing: 0.6px; + white-space: nowrap; +} +td, +th { + padding: 9px 16px; + border-bottom: 1px solid var(--line-soft); +} +td { + font-size: 12px; +} +thead { + background: #111a17; +} +tbody tr:last-child td { + border-bottom: 0; +} +.muted { + color: var(--muted); +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} +.sr-only:focus { + position: static; + width: auto; + height: auto; + clip-path: none; +} +.skip-link { + position: fixed; + top: -80px; + left: 16px; + background: var(--lime); + color: #14200e; + padding: 12px; + z-index: 100; +} +.skip-link:focus { + top: 10px; +} +.workspace { display: grid; - grid-template-rows: auto auto 1fr; - gap: 24px; + grid-template-columns: 208px minmax(0, 1fr); + min-height: 100svh; +} +.sidebar { position: sticky; - top: 24px; - height: fit-content; + top: 0; + height: 100svh; + display: flex; + flex-direction: column; + border-right: 1px solid var(--line); + padding: 58px 12px 16px; + background: #0c1210bb; } - -.dd-brand { +.primary-nav { + display: grid; + gap: 4px; +} +.nav-item { + height: 43px; display: flex; align-items: center; - gap: 12px; + gap: 13px; + border-radius: 5px; + padding: 0 15px; + color: #b9c7bf; + position: relative; +} +.nav-item:hover, +.source-link:hover { + background: #14201b; +} +.nav-item.active { + background: #18231f; + color: var(--lime); +} +.nav-item.active:before { + content: ''; + position: absolute; + left: 0; + top: 5px; + bottom: 5px; + width: 3px; + background: var(--lime); + border-radius: 4px; +} +.nav-count { + margin-left: auto; + background: var(--amber); + color: #211a0d; + border-radius: 20px; + padding: 0 6px; + min-width: 20px; + text-align: center; + font: 11px/20px var(--mono); +} +.sources-heading { + margin: 28px 6px 12px; + padding-top: 20px; + border-top: 1px solid var(--line); + text-transform: uppercase; + letter-spacing: 1.3px; + color: #a7b9ad; + font-size: 10px; + display: flex; + justify-content: space-between; +} +.sources-heading span { + color: var(--muted); + letter-spacing: 0; } - -.dd-brand-mark { - width: 44px; - height: 44px; - border-radius: 14px; +.source-nav { + display: flex; + flex-direction: column; + gap: 3px; + overflow: auto; + min-height: 0; +} +.source-link { display: flex; align-items: center; - justify-content: center; - background: linear-gradient(135deg, var(--dd-indigo), var(--dd-cyan)); - color: #07101d; - font-weight: 800; -} - -.dd-brand strong, -.dd-nav-card strong, -.dd-stat-card strong, -.dd-node strong, -.dd-chart-meta strong, -.dd-hotspot-row strong, -.dd-topbar h1 { - display: block; + gap: 12px; + min-height: 37px; + padding: 7px 14px; + border-radius: 4px; } - -.dd-brand span, -.dd-topbar p, -.dd-nav-card small, -.dd-panel-header p, -.dd-stat-card small, -.dd-node small, -.dd-chart-meta span, -.dd-hotspot-row small, -.dd-alert-topline small, -.dd-empty, -.dd-stream-time, -.dd-kicker { - color: var(--dd-muted); -} - -.dd-nav-list { - display: grid; - gap: 8px; +.source-link span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } - -.dd-nav-list a { - padding: 12px 14px; - border-radius: 14px; - color: #d7e4ff; - background: transparent; - transition: background-color 0.2s ease, transform 0.2s ease; -} - -.dd-nav-list a:hover, -.dd-nav-list .is-active { - background: rgba(120, 149, 255, 0.14); - transform: translateX(2px); -} - -.dd-nav-card { - align-self: flex-end; - border: 1px solid rgba(120, 149, 255, 0.18); - border-radius: 18px; - padding: 16px; - background: linear-gradient(180deg, rgba(23, 33, 56, 0.95), rgba(14, 19, 33, 0.9)); -} - -.dd-nav-card span, -.dd-stat-card span, -.dd-badge, -.dd-pill, -.dd-stream-header-row, -.dd-stream-level, -.dd-severity { - text-transform: uppercase; - letter-spacing: 0.08em; - font-size: 11px; - font-weight: 700; +.source-link svg { + color: #a3b5aa; } - -.dd-main { - display: grid; - gap: 18px; +.environment { + margin-top: auto; + display: flex; + align-items: center; + gap: 9px; + padding: 12px 10px 2px; + color: #9aafa1; + font-size: 10px; +} +.environment > .status-dot { + margin-left: auto; } - -.dd-topbar { - border-radius: 28px; - padding: 26px 28px; +.main-workspace { + min-width: 0; +} +.topbar { + position: sticky; + top: 0; + z-index: 20; + min-height: 66px; + padding: 10px 22px; + border-bottom: 1px solid var(--line); + background: #0c1411ed; + backdrop-filter: blur(12px); display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 20px; } - -.dd-kicker { - display: inline-block; - margin-bottom: 10px; - text-transform: uppercase; - letter-spacing: 0.14em; - font-size: 12px; -} - -.dd-topbar h1 { - margin: 0 0 8px; - font-size: clamp(2rem, 4vw, 3.1rem); - line-height: 1; +.topbar-metrics { + display: flex; + align-items: center; + gap: 28px; } - -.dd-topbar-actions { +.inline-metric { display: flex; + align-items: center; gap: 12px; - flex-wrap: wrap; } - -.dd-pill { - border-radius: 999px; - padding: 10px 14px; - border: 1px solid rgba(148, 163, 184, 0.16); - background: rgba(15, 23, 42, 0.75); -} - -.dd-pill.is-accent, -.dd-badge { - background: linear-gradient(135deg, rgba(120, 149, 255, 0.22), rgba(102, 227, 255, 0.18)); - color: #dff7ff; -} - -.dd-summary-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; +.inline-metric + .inline-metric { + padding-left: 28px; + border-left: 1px solid var(--line); } - -.dd-stat-card { - border-radius: 20px; - padding: 18px 20px; - display: grid; +.inline-metric strong { + font-size: 19px; + display: block; + line-height: 1.25; +} +.inline-metric span { + font-size: 10px; + color: var(--muted); + display: block; + margin-top: 3px; +} +.topbar-controls { + display: flex; + align-items: center; gap: 10px; } - -.dd-stat-card strong { - font-size: 1.8rem; +.mode-pill { + display: flex; + gap: 7px; + align-items: center; + color: var(--lime); + font-size: 11px; + padding: 6px 9px; + border: 1px solid #344a2c; + border-radius: 4px; + white-space: nowrap; +} +.page-content { + padding: 18px 22px 22px; } - -.dd-grid { - display: grid; - grid-template-columns: 1.35fr 0.95fr 0.95fr; - gap: 18px; +.status-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + background: #6c7f70; + flex: none; +} +.status-dot.positive { + background: var(--lime); +} +.status-dot.warning { + background: var(--amber); +} +.status-dot.danger { + background: var(--red); +} +.button { + display: inline-flex; + justify-content: center; + align-items: center; + gap: 8px; + min-height: 36px; + padding: 8px 12px; + border: 1px solid var(--line); + border-radius: 5px; + background: #111917; + font-size: 12px; + white-space: nowrap; +} +.button:hover:not(:disabled) { + background: #1a281d; + border-color: #4c644e; +} +.button.primary { + border-color: #718e45; + background: #24341c; + color: var(--lime); +} +.icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 26px; + min-height: 28px; + color: #a2b3aa; + border-radius: 4px; +} +.icon-button:hover { + background: #233027; +} +.previous svg { + transform: rotate(180deg); } - -.dd-grid-bottom { - grid-template-columns: 0.9fr 1.4fr; +.icon-actions { + display: flex; + gap: 2px; } - -.dd-panel { - border-radius: 24px; - padding: 20px; +.text-button { + color: #afca99; + font-size: 11px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0; } - -.dd-panel-large { - grid-column: span 2; +.text-button:hover { + color: var(--lime); } - -.dd-panel-wide { +.panel { + border: 1px solid var(--line); + background: linear-gradient(130deg, #121b18b3, #0e1513b3); + border-radius: 5px; min-width: 0; } - -.dd-panel-header { +.panel-header { display: flex; - align-items: flex-start; justify-content: space-between; - gap: 16px; - margin-bottom: 18px; + align-items: center; + gap: 12px; + padding: 12px 15px; + border-bottom: 1px solid var(--line-soft); } - -.dd-panel-header-stack { - flex-direction: column; +.panel-header h2, +.panel-header h3 { + font-size: 13px; } - -.dd-panel-header h2 { - margin: 0 0 6px; - font-size: 1.1rem; +.panel-header > .muted { + font-size: 10px; } - -.dd-topology { - display: grid; - grid-template-columns: repeat(7, minmax(0, 1fr)); - gap: 10px; +.table-scroll { + overflow: auto; + min-width: 0; +} +.quiet-state { + padding: 15px; + color: var(--muted); + font-size: 12px; +} +.empty-state { + display: flex; + flex-direction: column; align-items: center; - margin-bottom: 20px; + justify-content: center; + gap: 10px; + padding: 48px 20px; + text-align: center; + color: var(--muted); + min-height: 200px; } - -.dd-node { - border: 1px solid rgba(148, 163, 184, 0.14); - border-radius: 18px; - padding: 14px; - background: rgba(16, 23, 40, 0.75); - min-height: 96px; +.empty-state strong { + color: var(--text); + font-size: 14px; } - -.dd-node.is-primary { - background: linear-gradient(180deg, rgba(41, 65, 118, 0.45), rgba(17, 26, 44, 0.85)); - border-color: rgba(120, 149, 255, 0.32); +.empty-state span { + max-width: 340px; + font-size: 12px; } - -.dd-link { - height: 2px; - background: linear-gradient(90deg, rgba(120, 149, 255, 0.15), rgba(102, 227, 255, 0.85)); +.empty-state svg { + color: #8ca477; } - -.dd-mini-charts { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); +.inline-error, +.connection-banner { + display: flex; + align-items: center; + justify-content: space-between; gap: 14px; + border: 1px solid #674537; + background: #2c211c; + color: #f1bba6; + border-radius: 4px; + padding: 10px 13px; + font-size: 12px; + margin: 10px 0; } - -.dd-chart-card, -.dd-query-bar, -.dd-alert-card, -.dd-stream-row, -.dd-hotspot-row { - border: 1px solid rgba(148, 163, 184, 0.12); - background: rgba(15, 23, 42, 0.68); -} - -.dd-chart-card { - border-radius: 18px; - padding: 14px; +.connection-banner { + margin: 0; + border-radius: 0; } - -.dd-chart-meta { +.results-footer { display: flex; - align-items: flex-end; + align-items: center; justify-content: space-between; - margin-bottom: 10px; + gap: 14px; + margin-top: 12px; + color: #a2b5a8; + font-size: 11px; } - -.dd-chart-card svg { - width: 100%; - height: 64px; +.results-footer small { + display: flex; + align-items: center; + gap: 7px; + font-size: 10px; + margin-top: 4px; } - -.dd-line { - fill: none; - stroke-width: 3; - stroke-linecap: round; - stroke-linejoin: round; +.footer-actions { + display: flex; + align-items: center; + gap: 10px; } - -.dd-line-indigo { - stroke: var(--dd-indigo); +.footer-actions .button, +.footer-actions select { + font-size: 11px; + min-height: 32px; + padding-top: 6px; + padding-bottom: 6px; } - -.dd-line-cyan { - stroke: var(--dd-cyan); +.load-older { + min-width: 180px; } - -.dd-bars { - height: 260px; - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 14px; - align-items: flex-end; +.segmented { + display: flex; + align-items: center; + border: 1px solid var(--line); + border-radius: 5px; + background: #101713; + padding: 2px; +} +.segmented button { + padding: 7px 12px; + color: #a5b5a8; + font-size: 11px; + white-space: nowrap; + border: 1px solid transparent; + border-radius: 3px; + min-height: 30px; } - -.dd-bar-column { - display: grid; - gap: 8px; - justify-items: center; +.segmented button.selected { + border-color: #718e45; + background: #24341c; + color: var(--lime); +} +.segmented button:hover:not(.selected) { + color: var(--text); + background: #19261d; } - -.dd-bar-track { +.search-input { + display: flex; + align-items: center; + gap: 10px; + background: #121c18; + border: 1px solid #3a4b40; + border-radius: 5px; + min-width: 120px; + flex: 1; + padding: 0 12px; +} +.search-input > svg { + color: #a6b8aa; +} +.search-input input { + border: 0; width: 100%; - height: 190px; - border-radius: 18px; - background: linear-gradient(180deg, rgba(148, 163, 184, 0.06), rgba(148, 163, 184, 0.14)); + padding: 9px 0; + outline: none; + background: transparent; + font-size: 13px; + min-height: 36px; +} +.search-input:focus-within { + border-color: var(--lime); + box-shadow: 0 0 0 1px #b9ed7c33; +} +.section-tools { display: flex; - align-items: flex-end; - padding: 8px; + justify-content: space-between; + margin: -4px 0 5px; +} +.pipeline-section { + position: relative; + padding-top: 9px; + margin-bottom: 8px; +} +.pipeline-legend { + position: absolute; + right: 2px; + top: 0; + font: 10px var(--mono); + color: #a7b7a9; } - -.dd-bar-fill { +.pipeline-scroll { + overflow-x: auto; +} +.pipeline { width: 100%; - border-radius: 12px; + min-width: 850px; + max-height: 190px; + display: block; } - -.dd-hotspots, -.dd-alert-list { - display: grid; - gap: 12px; +.pipeline rect { + fill: #111b16; + stroke: #4c5c50; +} +.pipeline .inner-frame { + fill: none; + stroke: #2b3d30; +} +.flow-route { + fill: none; + stroke: #56665b; + stroke-width: 1.5; +} +.pipeline text { + font-family: var(--mono); + fill: var(--text); +} +.pipeline .flow-label { + font-size: 12px; + letter-spacing: 1px; + fill: #adbdaf; +} +.pipeline .flow-value { + font-size: 21px; + font-family: 'Segoe UI', sans-serif; +} +.pipeline .flow-caption { + font-size: 11px; + fill: #99ad9f; +} +.chart { + padding: 15px 16px 11px; + min-width: 0; +} +.chart-heading { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; + margin-bottom: 6px; +} +.chart h2 { + font-size: 12px; + font-weight: 500; +} +.chart h2 > span { + font-size: 10px; + color: var(--muted); + margin-left: 6px; } - -.dd-hotspot-row { - border-radius: 16px; - padding: 14px 16px; +.series-controls { display: flex; + gap: 10px; + flex-wrap: wrap; + margin-top: 8px; +} +.series-controls button { + display: inline-flex; align-items: center; - gap: 14px; + gap: 5px; + font: 9px var(--mono); + padding: 0; + color: #adbfb0; +} +.series-controls button[aria-pressed='false'] { + opacity: 0.4; +} +.series-controls i, +.chart-tooltip i { + width: 14px; + height: 2px; + display: inline-block; +} +.chart-readout { + text-align: right; + flex: none; +} +.chart-readout strong { + display: block; + font-size: 23px; + line-height: 1.1; + font-variant-numeric: tabular-nums; } - -.dd-hotspot-rank { - width: 34px; - height: 34px; - border-radius: 12px; - background: rgba(120, 149, 255, 0.14); - color: #dce7ff; +.chart-readout > span { + font-size: 9px; + color: var(--muted); +} +.chart-plot { + position: relative; + min-width: 0; + touch-action: pan-y; + overflow: hidden; +} +.chart-plot svg { + display: block; + max-width: 100%; +} +.chart-plot text { + font: 9px var(--mono); + fill: #80988a; +} +.grid-line { + stroke: #283c30; + stroke-dasharray: 2 5; +} +.chart-line { + stroke-linejoin: round; + stroke-linecap: round; +} +.chart-tooltip { + position: absolute; + top: 10px; + right: 12px; + min-width: 170px; + padding: 10px 12px; + background: #0c1512f5; + border: 1px solid #4f6451; + border-radius: 4px; + pointer-events: none; + box-shadow: 0 6px 20px #0006; +} +.chart-tooltip strong { + display: block; + font: 10px var(--mono); + margin-bottom: 8px; +} +.chart-tooltip > span { display: flex; align-items: center; - justify-content: center; - font-weight: 700; + gap: 6px; + font-size: 10px; + margin: 4px 0; } - -.dd-alert-card { - border-radius: 16px; - padding: 14px; +.chart-tooltip b { + margin-left: auto; + font: 11px var(--mono); } - -.dd-alert-topline { +.chart-footer { display: flex; justify-content: space-between; gap: 12px; - margin-bottom: 10px; -} - -.dd-alert-card p { - margin: 0; - line-height: 1.45; + font-size: 9px; + color: #8ea497; + padding: 5px 0 0; } - -.dd-severity { - display: inline-flex; +.chart-wait { + display: flex; align-items: center; justify-content: center; - border-radius: 999px; - padding: 6px 10px; + color: var(--muted); + font-size: 11px; } - -.dd-query-bar { - width: 100%; - border-radius: 18px; - padding: 12px; - display: grid; - grid-template-columns: 1.8fr 0.8fr auto; +.compact-chart .chart-heading { + margin-bottom: 0; +} +.compact-chart .chart-readout strong { + font-size: 20px; +} +.search-toolbar { + display: flex; gap: 12px; + align-items: stretch; + margin: 14px 0 12px; } - -.dd-query-bar input, -.dd-query-bar select, -.dd-query-bar button { - border-radius: 14px; - border: 1px solid rgba(148, 163, 184, 0.15); - padding: 12px 14px; - background: rgba(7, 12, 22, 0.9); - color: var(--dd-text); - font: inherit; +.search-toolbar > select { + min-width: 130px; } - -.dd-query-bar input::placeholder { - color: #7f93b5; +.time-filters { + display: flex; + flex-wrap: wrap; + gap: 12px; + padding: 12px; + margin-bottom: 10px; + align-items: center; + font-size: 11px; } - -.dd-query-bar button { - background: linear-gradient(135deg, var(--dd-indigo), var(--dd-cyan)); - color: #05101d; - font-weight: 800; - cursor: pointer; +.time-filters label { + display: flex; + align-items: center; + gap: 8px; } - -.dd-stream-table { - display: grid; - gap: 10px; +.search-context { + font: 10px var(--mono); + color: #b6c9aa; + margin-bottom: 12px; } - -.dd-stream-header-row, -.dd-stream-row { +.explorer-layout { display: grid; - grid-template-columns: 148px 88px 108px 1fr; + grid-template-columns: minmax(0, 1fr); gap: 14px; - align-items: center; + align-items: start; } - -.dd-stream-header-row { - padding: 0 6px 4px; - color: var(--dd-muted); +.explorer-layout.with-inspector { + grid-template-columns: minmax(0, 1fr) 370px; } - -.dd-stream-row { - border-radius: 16px; - padding: 14px 16px; +.log-table { + table-layout: fixed; + min-width: 600px; } - -.dd-stream-level, -.dd-stream-source, -.dd-stream-message { - min-width: 0; +.log-table th:first-child { + width: 138px; } - -.dd-stream-message { - font-family: "Consolas", "Courier New", monospace; - color: #dfe8fc; -} - -.dd-empty { - padding: 18px 0; -} - -@media (max-width: 1200px) { - .dd-shell, - .dd-grid, - .dd-grid-bottom, - .dd-summary-grid, - .dd-topology, - .dd-mini-charts { - grid-template-columns: 1fr; - } - - .dd-nav { - position: static; - } - - .dd-panel-large { - grid-column: span 1; - } - - .dd-link { - height: 28px; - width: 2px; - justify-self: center; - background: linear-gradient(180deg, rgba(120, 149, 255, 0.15), rgba(102, 227, 255, 0.85)); - } +.log-table th:nth-child(2) { + width: 80px; } - -@media (max-width: 760px) { - .app-shell { - padding: 14px; +.log-table th:nth-child(3) { + width: 110px; +} +.log-table thead { + position: sticky; + top: 0; + z-index: 1; +} +.log-table td { + font: 11px/1.5 var(--mono); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 9px 14px; +} +.log-table tr { + cursor: pointer; +} +.log-table tr:hover { + background: #1a281f; +} +.log-table tr.selected { + background: #25351e77; + box-shadow: inset 2px 0 var(--lime); +} +.log-table tr.selected:has(.level.error) { + background: #45241d66; + box-shadow: inset 2px 0 var(--red); +} +.log-table-panel > .table-scroll { + min-height: 350px; + max-height: 560px; +} +.log-select, +.related-log-select { + display: block; + padding: 0; + font: inherit; + text-align: left; + white-space: nowrap; +} +.level { + font: inherit; + font-weight: 600; +} +.level.info { + color: var(--blue); +} +.level.warn { + color: var(--amber); +} +.level.error { + color: var(--red); +} +.level.debug { + color: #9ca8be; +} +.inspector { + position: sticky; + top: 82px; + min-height: 440px; + max-height: calc(100svh - 105px); + display: flex; + flex-direction: column; + scroll-margin-top: 82px; + overflow: hidden; +} +.event-summary { + padding: 14px 15px; +} +.event-stamp { + display: flex; + gap: 10px; + flex-wrap: wrap; + font: 10px var(--mono); + margin-bottom: 14px; +} +.event-stamp time { + color: #a6b9ad; +} +.field-list { + display: grid; + grid-template-columns: 70px minmax(0, 1fr); + gap: 9px 10px; + font-size: 11px; + margin: 0; +} +.field-list dt { + color: #9fb1a5; +} +.field-list dd { + margin: 0; + overflow-wrap: anywhere; +} +.field-pair { + display: contents; +} +.event-message { + font: 10px/1.6 var(--mono); + background: #0a100d80; + padding: 8px; + border: 1px solid var(--line); + border-radius: 4px; +} +.inspector-tabs { + display: flex; + align-items: center; + border-bottom: 1px solid var(--line); + padding: 0 12px; +} +.inspector-tabs > button { + font-size: 11px; + padding: 9px 7px; + border-bottom: 2px solid transparent; +} +.inspector-tabs > [aria-selected='true'] { + border-bottom-color: var(--lime); +} +.copy-button { + margin-left: auto; +} +.inspector-content { + overflow: auto; + min-height: 140px; + flex: 1; +} +.json-view { + font: 10px/1.7 var(--mono); + margin: 0; + padding: 14px; + color: #c5d2c9; + white-space: pre-wrap; + overflow-wrap: anywhere; + tab-size: 2; +} +.json-key { + color: #9dbce3; +} +.json-string { + color: #dba987; +} +.json-number { + color: #b8d88e; +} +.parsed-fields { + font-size: 10px; + margin: 0; + padding: 12px; +} +.parsed-fields > div { + display: grid; + grid-template-columns: 95px minmax(0, 1fr); + gap: 12px; + padding: 8px 0; + border-bottom: 1px solid var(--line-soft); +} +.parsed-fields dt { + color: #9bb5a2; + overflow-wrap: anywhere; +} +.parsed-fields dd { + margin: 0; + overflow-wrap: anywhere; +} +.copy-status { + padding: 8px 12px; + font-size: 10px; + color: var(--lime); + border-top: 1px solid var(--line); +} +.alert-toolbar { + display: flex; + gap: 12px; + margin: 2px 0 14px; + align-items: stretch; + flex-wrap: wrap; +} +.alert-toolbar .search-input { + min-width: 210px; +} +.alert-toolbar select { + min-width: 130px; + flex: 0 1 150px; +} +.alerts-layout { + display: grid; + grid-template-columns: 1fr; + gap: 14px; + min-height: 500px; +} +.alerts-layout.has-selection { + grid-template-columns: minmax(240px, 0.82fr) minmax(340px, 1fr); + align-items: stretch; +} +.alert-queue { + display: flex; + flex-direction: column; + min-height: 0; + max-height: max(520px, calc(100svh - 184px)); + overflow: hidden; +} +.alert-queue > .panel-header, +.alert-detail > .panel-header, +.related-events > .panel-header { + min-height: 43px; + flex: none; +} +.alert-list { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: #3a4b40 transparent; +} +.alert-card { + display: grid; + grid-template-columns: 9px minmax(0, 1fr) 13px; + align-items: start; + gap: 10px; + text-align: left; + padding: 13px 12px; + color: var(--text); + width: 100%; + flex: none; + min-height: 90px; + position: relative; + border-bottom: 1px solid var(--line-soft); + transition: background 0.12s; +} +.alert-card:last-child { + border-bottom: 0; +} +.alert-card:hover { + background: #19251d; +} +.alert-card.selected { + background: linear-gradient(110deg, #23341eaa, #17241c66); + box-shadow: inset 2px 0 var(--lime); +} +.alert-card > svg { + align-self: center; + color: #9aae9f; +} +.alert-card-top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.alert-card-top time { + color: #a6baac; + font: 10px var(--mono); + white-space: nowrap; +} +.alert-card h2 { + font-size: 13px; + font-weight: 500; + line-height: 1.45; + margin: 6px 0 4px; + overflow-wrap: anywhere; +} +.alert-card-meta { + display: flex; + flex-wrap: wrap; + gap: 3px 8px; + color: #a2bba8; + font-size: 11px; + overflow-wrap: anywhere; +} +.alert-card-meta > span + span:before { + content: '·'; + margin-right: 8px; + color: #657b6c; +} +.severity { + text-transform: uppercase; + font: 10px var(--mono); + letter-spacing: 0.5px; + padding: 3px 7px; + display: inline-block; + color: #bdd7b8; + border: 1px solid #4c6943; + border-radius: 3px; + background: #22351c; +} +.severity.high, +.severity.critical { + color: var(--red); + border-color: #a85343; + background: #3b201a; +} +.severity.medium { + color: var(--amber); + border-color: #80642e; + background: #322714; +} +.severity.low { + color: #a6d0b9; + border-color: #3d6b56; + background: #1d2f25; +} +.severity-dot { + display: block; + width: 9px; + height: 9px; + margin-top: 6px; + border-radius: 50%; + background: #a6d0b9; +} +.severity-dot.high, +.severity-dot.critical { + background: var(--red); +} +.severity-dot.medium { + background: var(--amber); +} +.ack-mark { + display: flex; + align-items: center; + gap: 4px; + color: #a5c28e; + font-size: 10px; + margin-top: 5px; +} +.alert-detail { + padding: 0; + min-height: 0; + scroll-margin-top: 82px; +} +.alert-detail-body { + padding: 18px 16px; +} +.alert-detail-header { + margin-bottom: 20px; +} +.alert-detail-status { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.alert-detail-header h2 { + font-size: 20px; + font-weight: 600; + line-height: 1.35; + margin: 12px 0 5px; + overflow-wrap: anywhere; +} +.alert-reason { + font-size: 12px; + line-height: 1.6; + color: #bdcbbf; +} +.alert-meta { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 8px 12px; + color: #a2bba8; + font-size: 10px; + margin-top: 16px; + overflow-wrap: anywhere; +} +.alert-meta > span:first-child { + display: flex; + gap: 7px; + align-items: center; + min-width: 0; +} +.alert-meta time { + margin-left: 4px; + color: #bfccc0; + font-family: var(--mono); +} +.acknowledged { + display: inline-flex; + align-items: center; + gap: 5px; + color: var(--lime); + font-size: 11px; +} +.observed-metric { + border-top: 1px solid var(--line); + padding: 20px 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; +} +.observed-metric > span { + font-size: 12px; + color: #bdcbbf; +} +.observed-metric strong { + font-size: clamp(36px, 3.7vw, 58px); + line-height: 1.15; + font-weight: 600; + font-variant-numeric: tabular-nums; + overflow-wrap: anywhere; + max-width: 100%; +} +.evidence-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin: 0; + padding: 0; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} +.evidence-grid > div { + padding: 13px 8px; + text-align: center; + min-width: 0; + border-right: 1px solid var(--line); +} +.evidence-grid > div:nth-child(3n), +.evidence-grid > div:last-child { + border-right: 0; +} +.evidence-grid dt, +.evidence-grid > div > span { + color: #9fb5a3; + font-size: 10px; +} +.evidence-grid dd { + font-size: 18px; + font-weight: 600; + margin: 5px 0 0; + font-variant-numeric: tabular-nums; + overflow-wrap: anywhere; +} +.threshold-chart { + padding: 18px 0 16px; + margin: 0; + border-bottom: 1px solid var(--line); +} +.threshold-chart figcaption { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-bottom: 20px; +} +.threshold-chart h3 { + font-size: 12px; + font-weight: 500; +} +.threshold-chart figcaption > span { + font: 10px var(--mono); + white-space: nowrap; +} +.comparison-exceeded { + color: #f5a894; +} +.comparison-row { + display: grid; + grid-template-columns: 60px minmax(20px, 1fr) minmax(35px, auto); + align-items: center; + gap: 10px; + margin: 15px 0; + font-size: 11px; +} +.comparison-row > span { + color: #acbfb0; +} +.comparison-row strong { + font: 11px var(--mono); + text-align: right; + color: #efb09e; +} +.comparison-track { + height: 10px; + background: #27352a; + border-radius: 2px; + overflow: hidden; +} +.comparison-track i { + display: block; + height: 100%; + background: #e58e78; + border-radius: 2px; +} +.threshold-row .comparison-track i { + background: #a0b2a2; +} +.threshold-row strong { + color: #c0d1c3; +} +.threshold-chart > p { + margin: 20px 0 0; + color: #92a797; + font-size: 10px; +} +.evidence-context { + color: #9fb69f; + font: 10px var(--mono); + margin: 15px 0; +} +.alert-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin: 17px 0; +} +.alert-actions .button { + font-size: 11px; + padding-left: 10px; + padding-right: 10px; +} +.detection-details { + border: 1px solid var(--line); + border-radius: 4px; + margin-top: 12px; +} +.detection-details summary, +.related-raw summary { + cursor: pointer; + padding: 11px 13px; + color: #b3c8b4; + font-size: 11px; +} +.original-detection { + font-size: 11px; + line-height: 1.6; + padding: 12px; + border-top: 1px solid var(--line-soft); + color: #a8bcad; + overflow-wrap: anywhere; +} +.detection-details .json-view, +.related-raw .json-view { + max-height: 240px; + overflow: auto; + border-top: 1px solid var(--line); + font-size: 10px; +} +.related-events { + grid-column: 1/-1; + display: flex; + flex-direction: column; + min-height: 0; + height: 620px; + overflow: hidden; +} +.related-log-scroll { + flex: 1; + min-height: 180px; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: #3a4b40 transparent; +} +.related-log-table { + table-layout: fixed; + min-width: 440px; + font-family: var(--mono); +} +.related-log-table thead { + position: sticky; + top: 0; + z-index: 1; + background: #111a17; +} +.related-log-table th { + padding: 11px 10px; + font-size: 9px; +} +.related-log-table th:first-child { + width: 107px; +} +.related-log-table th:nth-child(2) { + width: 78px; +} +.related-log-table th:nth-child(3) { + width: 62px; +} +.related-log-table td { + padding: 11px 10px; + font-size: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.related-log-table tbody tr { + cursor: pointer; +} +.related-log-table tbody tr:hover { + background: #1a271e; +} +.related-log-table tr.selected { + background: #23341d88; + box-shadow: inset 2px 0 var(--lime); +} +.related-log-select:hover { + color: var(--lime); +} +.related-error { + padding: 16px; + color: #e4ad9c; + font-size: 12px; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; +} +.related-log-inspector { + padding: 16px; + border-top: 1px solid var(--line); + flex: none; + max-height: 48%; + overflow: auto; +} +.related-inspector-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 16px; +} +.related-inspector-heading h3 { + font-size: 12px; +} +.related-inspector-heading a { + display: inline-flex; + align-items: center; + font-size: 10px; + gap: 3px; +} +.related-log-fields { + display: grid; + grid-template-columns: 58px minmax(0, 1fr); + gap: 9px 12px; + font: 11px/1.6 var(--mono); + margin: 0; +} +.related-log-fields dt { + color: #9eb3a5; +} +.related-log-fields dd { + margin: 0; + overflow-wrap: anywhere; + white-space: pre-wrap; +} +.related-raw { + margin-top: 12px; + border: 1px solid var(--line); + border-radius: 4px; +} +.related-raw summary { + padding: 8px 10px; +} +.overview-page { + display: grid; + gap: 14px; +} +.metric-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 14px; +} +.metric-card { + display: flex; + align-items: center; + gap: 15px; + padding: 18px; + min-height: 108px; +} +.metric-card > svg { + color: #b5c6b9; +} +.metric-card strong { + font-size: 24px; + display: block; + line-height: 1.2; +} +.metric-card span { + display: block; + font-size: 12px; + margin-top: 6px; +} +.metric-card small { + display: block; + color: var(--muted); + font-size: 9px; + margin-top: 3px; +} +.two-column { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} +.health-table { + min-width: 420px; +} +.health-table td { + font-size: 11px; +} +.health-table td:first-child { + white-space: nowrap; +} +.health-table td:first-child svg { + vertical-align: middle; + margin-right: 9px; +} +.health-table .status-dot { + margin-right: 7px; +} +.health-table td:last-child { + color: var(--muted); + font-size: 10px; +} +.source-shares { + display: grid; + gap: 14px; + padding: 18px 16px; +} +.source-shares > a { + display: grid; + grid-template-columns: 80px minmax(30px, 1fr) 45px 45px; + align-items: center; + gap: 10px; + font-size: 11px; +} +.source-shares > a > span:first-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.source-shares strong { + text-align: right; + font: 11px var(--mono); +} +.source-shares > a > span:nth-last-child(2) { + text-align: right; + font: 10px var(--mono); + color: var(--muted); +} +.share-track { + height: 5px; + border-radius: 3px; + background: #243329; + overflow: hidden; +} +.share-track i { + display: block; + height: 100%; + background: #9fc96f; + border-radius: 3px; +} +.activity-row { + padding: 16px; + display: flex; + align-items: center; + gap: 14px; + font-size: 12px; +} +.activity-row > span:nth-child(2) { + flex: 1; +} +.activity-row time { + font: 10px var(--mono); + color: var(--muted); + white-space: nowrap; +} +@media (min-width: 1280px) { + .alerts-layout.has-selection { + grid-template-columns: minmax(240px, 0.82fr) minmax(340px, 1fr) minmax(360px, 1.4fr); + height: max(660px, calc(100svh - 184px)); + } + .alerts-layout.has-selection .alert-queue { + max-height: none; + } + .alerts-layout.has-selection .alert-detail { + height: 100%; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #3a4b40 transparent; + } + .alerts-layout.has-selection .related-events { + grid-column: auto; + height: 100%; + } +} +@media (min-width: 1800px) { + .page-content { + padding-left: 30px; + padding-right: 30px; + } + .topbar { + padding-left: 30px; + padding-right: 30px; + } + .log-table td { + font-size: 12px; + } +} +@media (max-width: 1180px) { + .workspace { + grid-template-columns: 174px minmax(0, 1fr); + } + .page-content { + padding: 16px; + } + .topbar { + padding: 10px 16px; + } + .topbar-metrics { + gap: 16px; + } + .inline-metric + .inline-metric { + padding-left: 16px; + } + .explorer-layout.with-inspector { + grid-template-columns: minmax(0, 1fr) 310px; + } + .search-toolbar { + flex-wrap: wrap; + gap: 8px; + } + .search-toolbar .search-input { + flex-basis: 40%; + } + .metric-card { + padding: 14px; + gap: 10px; + } + .metric-card strong { + font-size: 20px; + } + .alert-toolbar .segmented { + order: 3; + } + .alert-toolbar .search-input { + flex-basis: 40%; + } +} +@media (max-width: 1000px) { + .explorer-layout.with-inspector { + grid-template-columns: minmax(0, 1fr); + } + .inspector { + position: static; + max-height: 800px; + } + .metric-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .two-column { + grid-template-columns: minmax(0, 1fr); + } + .topbar-controls { + gap: 7px; + } + .inline-metric strong { + font-size: 17px; + } +} +@media (max-width: 950px) { + .alerts-layout.has-selection { + grid-template-columns: minmax(0, 1fr); + } + .alert-queue { + max-height: none; + } + .alert-list { + max-height: 270px; + } + .alert-card { + padding: 13px 16px; + } + .alert-card h2 { + font-size: 14px; + } + .alert-detail { + min-height: 0; + position: static; + } + .pipeline-legend { + font-size: 9px; + } +} +@media (max-width: 700px) { + .workspace { + display: block; + } + .sidebar { + position: sticky; + top: 0; + z-index: 30; + width: 100%; + height: 56px; + padding: 6px 10px; + border-right: 0; + border-bottom: 1px solid var(--line); + background: #0d1510; + flex-direction: row; + justify-content: space-between; + } + .primary-nav { + display: flex; + gap: 5px; + } + .nav-item { + height: 42px; + padding: 0 10px; + font-size: 12px; + gap: 7px; + } + .nav-item svg { + width: 17px; + } + .nav-item.active:before { + top: auto; + bottom: -7px; + height: 2px; + width: auto; + right: 7px; + left: 7px; + } + .nav-count { + font-size: 9px; + line-height: 17px; + min-width: 17px; + padding: 0 4px; + } + .sources-heading, + .source-nav { + display: none; + } + .environment { + padding: 0; + margin: 0; + gap: 5px; + font-size: 10px; + } + .environment > span { + display: none; + } + .topbar { + top: 56px; + min-height: 62px; + gap: 10px; + padding: 10px 12px; + } + .topbar-metrics .inline-metric > svg { + display: none; + } + .inline-metric { + gap: 0; + } + .inline-metric strong { + font-size: 17px; + } + .inline-metric span { + font-size: 9px; + } + .inline-metric + .inline-metric { + padding-left: 12px; + } + .topbar-controls { + gap: 6px; } - - .dd-topbar, - .dd-query-bar, - .dd-stream-header-row, - .dd-stream-row { - grid-template-columns: 1fr; + .topbar-controls .button { + font-size: 11px; + padding: 6px 9px; + gap: 5px; + min-height: 33px; } - - .dd-topbar { - flex-direction: column; + .topbar-controls select { + font-size: 11px; + padding: 6px 18px 6px 9px; + max-width: 63px; + min-height: 33px; + } + .mode-pill { + font-size: 10px; + padding: 5px 7px; + } + .page-content { + padding: 14px 12px; + } + .pipeline-legend { + top: 2px; + } + .pipeline-section { + padding-top: 16px; + } + .chart { + padding: 13px 12px 10px; + } + .chart-readout strong { + font-size: 20px; + } + .chart-footer { + font-size: 9px; + } + .chart-footer > span:last-child { + display: none; + } + .search-toolbar .search-input { + flex-basis: 100%; + } + .search-toolbar .segmented { + flex: 1; + } + .segmented button { + flex: 1; + } + .search-toolbar > select { + max-width: 150px; + min-width: 105px; + font-size: 11px; + } + .log-table-panel > .table-scroll { + min-height: 330px; + max-height: 600px; + } + .results-footer { + align-items: flex-start; + flex-wrap: wrap; + gap: 12px; + } + .footer-actions { + gap: 8px; + flex-wrap: wrap; + } + .load-older { + min-width: 140px; + } + .inspector { + scroll-margin-top: 132px; + } + .metric-grid { + gap: 10px; + } + .metric-card { + min-height: 85px; + padding: 13px; + } + .metric-card strong { + font-size: 20px; + } + .metric-card span { + font-size: 10px; + } + .overview-page { + gap: 12px; + } + .alert-toolbar { + gap: 8px; + margin-bottom: 14px; + } + .alert-toolbar .search-input { + flex-basis: 100%; + } + .alert-toolbar > select { + flex: 1; + max-width: none; + min-width: 100px; + font-size: 11px; + } + .alert-toolbar .segmented { + width: 100%; + order: 0; + } + .alert-toolbar .segmented button { + padding: 7px 8px; + } + .alert-detail-body { + padding: 18px 15px; + } + .observed-metric strong { + font-size: 44px; + } + .alert-detail-header h2 { + font-size: 17px; + } + .alert-detail { + scroll-margin-top: 132px; + } + .evidence-grid dd { + font-size: 16px; + } + .activity-row { + font-size: 11px; + gap: 10px; + flex-wrap: wrap; + } + .activity-row time { + font-size: 9px; + } + .time-filters input { + max-width: 180px; + } + .source-shares > a { + grid-template-columns: 68px minmax(20px, 1fr) 40px 40px; + gap: 8px; + } +} +@media (max-width: 440px) { + .topbar-metrics .inline-metric:nth-child(2) { + display: none; + } + .environment { + display: none; + } + .nav-item { + padding: 0 9px; + } + .mode-pill { + font-size: 9px; + } + .topbar-controls { + gap: 5px; + } + .metric-card > svg { + display: none; + } + .metric-card strong { + font-size: 21px; + } + .segmented button { + font-size: 10px; + } +} +@media (prefers-reduced-motion: reduce) { + *, + *:before, + *:after { + scroll-behavior: auto !important; + transition: none !important; + animation: none !important; } } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 3bb3bbc..da0d812 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,19 +1,22 @@ import type { Metadata } from 'next'; import './globals.css'; - +import { DashboardProvider } from '../components/DashboardProvider'; +import Shell from '../components/Shell'; export const metadata: Metadata = { - title: 'Log Aggregation System', - description: 'Distributed log aggregation dashboard', + title: 'Explore logs', + description: 'Search logs, monitor ingestion, and investigate detections.', + icons: { + icon: 'data:image/svg+xml,' + encodeURIComponent(''), + }, }; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function Layout({ children }: { children: React.ReactNode }) { return ( - - {children} + + + + {children} + + ); } diff --git a/frontend/src/app/overview/page.tsx b/frontend/src/app/overview/page.tsx new file mode 100644 index 0000000..47d71bf --- /dev/null +++ b/frontend/src/app/overview/page.tsx @@ -0,0 +1,2 @@ +import OverviewPage from '../../components/OverviewPage'; +export default OverviewPage; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 9133904..b024e2e 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,9 +1,9 @@ -import LogDashboard from '@/components/LogDashboard'; - -export default function Home() { +import { Suspense } from 'react'; +import ExplorePage from '../components/ExplorePage'; +export default function Page() { return ( -
- -
+ Loading workspace…}> + + ); } diff --git a/frontend/src/components/AlertDetail.tsx b/frontend/src/components/AlertDetail.tsx new file mode 100644 index 0000000..f02ecaa --- /dev/null +++ b/frontend/src/components/AlertDetail.tsx @@ -0,0 +1,172 @@ +'use client'; +import Link from 'next/link'; +import type { AlertItem } from '../lib/model'; +import { + alertSource, + alertTitle, + clock, + count, + numeric, + relatedParams, + serverDate, +} from '../lib/events'; +import Icon from './Icon'; +import JsonView from './JsonView'; +export default function AlertDetail({ + alert, + saving, + acknowledged, + onAcknowledge, +}: { + alert: AlertItem; + saving: boolean; + acknowledged: boolean; + onAcknowledge: () => void; +}) { + const d = alert.details ?? {}, + current = numeric(d, 'errorsPerMinute', 'errors_per_minute'), + threshold = numeric(d, 'threshold'), + window = numeric(d, 'windowMinutes', 'window_minutes'), + errors = numeric(d, 'errorCount', 'error_count'), + score = numeric(d, 'anomaly_score'), + title = alertTitle(alert), + link = relatedParams(alert); + if (alert.logId && alert.logId !== 'system') link.set('logId', alert.logId); + const reason = + current != null && threshold != null + ? 'The observed error rate exceeded ' + + threshold + + ' errors per minute' + + (window != null ? ' over a ' + window + '-minute window.' : '.') + : alert.message; + const facts: [string, string, string?][] = []; + if (threshold != null) + facts.push(['Threshold', String(threshold), current != null ? 'errors/minute' : undefined]); + if (window != null) facts.push(['Detection window', window + ' min']); + if (errors != null) + facts.push([ + 'Error events', + count(errors), + window != null ? 'in ' + window + ' minutes' : undefined, + ]); + if (numeric(d, 'current_count') != null) + facts.push(['Current count', count(numeric(d, 'current_count'))]); + if (numeric(d, 'mean_count') != null) + facts.push(['Historical mean', count(numeric(d, 'mean_count'))]); + if (numeric(d, 'z_score') != null) facts.push(['Z-score', numeric(d, 'z_score')!.toFixed(2)]); + return ( + + ); +} diff --git a/frontend/src/components/AlertRelatedLogs.tsx b/frontend/src/components/AlertRelatedLogs.tsx new file mode 100644 index 0000000..c845171 --- /dev/null +++ b/frontend/src/components/AlertRelatedLogs.tsx @@ -0,0 +1,131 @@ +'use client'; +import Link from 'next/link'; +import { useState } from 'react'; +import { usePolling } from '../hooks/usePolling'; +import type { AlertItem, LogPage } from '../lib/model'; +import { clock, count, eventFields, rawEvent, relatedParams } from '../lib/events'; +import Icon from './Icon'; +import JsonView from './JsonView'; +export default function AlertRelatedLogs({ alert }: { alert: AlertItem }) { + const params = relatedParams(alert); + params.set('limit', '25'); + const resource = usePolling('/api/logs/page?' + params, 0, true), + [selectedId, setSelectedId] = useState(null), + logs = resource.data?.logs ?? [], + selected = logs.find((log) => log.id === selectedId) ?? logs[0], + fields = selected ? eventFields(selected) : {}, + latency = fields.latency_ms ?? fields.latency, + link = relatedParams(alert); + if (selected) link.set('logId', selected.id); + return ( +
+
+

Related logs

+ + {resource.data ? count(logs.length) + ' shown · ' : ''}Newest first + +
+
+ + + + + + + + + + + {logs.map((log) => ( + setSelectedId(log.id)} + > + + + + + + ))} + +
Time ↓SourceLevelMessage
+ + {log.source} + {log.level.toUpperCase()} + {log.message}
+ {!resource.data && resource.loading && ( +

+ Loading related events… +

+ )} + {resource.data && !logs.length && ( +

No retained events match this detection window.

+ )} + {resource.error && ( +
+

Related events are unavailable.

+ +
+ )} +
+ {selected && ( +
+
+

Selected log

+ + Open in Explore + +
+
+
Time
+
{clock(selected.timestamp, true)}
+
Source
+
{selected.source}
+
Level
+
+ {selected.level.toUpperCase()} +
+
Message
+
{selected.message}
+ {latency != null && ( + <> +
Latency
+
{typeof latency === 'number' ? latency + ' ms' : String(latency)}
+ + )} +
+
+ Raw event + +
+
+ )} +
+ ); +} diff --git a/frontend/src/components/AlertsPage.tsx b/frontend/src/components/AlertsPage.tsx new file mode 100644 index 0000000..b492d8f --- /dev/null +++ b/frontend/src/components/AlertsPage.tsx @@ -0,0 +1,302 @@ +'use client'; +import { useEffect, useState } from 'react'; +import { usePolling } from '../hooks/usePolling'; +import { useDashboard } from './DashboardProvider'; +import { requestJson } from '../lib/api'; +import type { AlertItem } from '../lib/model'; +import { alertSource, alertTitle, alertValue, clock, count, serverDate } from '../lib/events'; +import AlertDetail from './AlertDetail'; +import AlertRelatedLogs from './AlertRelatedLogs'; +import Icon from './Icon'; +export default function AlertsPage() { + const { paused, stats, alertStats, refreshAlerts } = useDashboard(); + const [tab, setTab] = useState('open'), + [source, setSource] = useState(''), + [severity, setSeverity] = useState(''), + [draft, setDraft] = useState(''), + [q, setQ] = useState(''), + [offset, setOffset] = useState(0), + [selectedId, setSelectedId] = useState(null), + [saving, setSaving] = useState(null), + [confirmed, setConfirmed] = useState(null), + [error, setError] = useState(null); + const params = new URLSearchParams({ limit: '25', offset: String(offset) }); + if (tab !== 'all') params.set('acknowledged', String(tab === 'acknowledged')); + if (q) params.set('q', q); + if (source) params.set('source', source); + if (severity) params.set('severity', severity); + const resource = usePolling<{ alerts: AlertItem[]; total: number }>( + '/api/alerts?' + params, + 5000, + paused, + ), + alerts = resource.data?.alerts ?? [], + total = resource.data?.total ?? 0, + selected = alerts.find((alert) => alert.id === selectedId) ?? alerts[0], + activeId = selected?.id; + useEffect(() => { + if (activeId && activeId !== selectedId) setSelectedId(activeId); + }, [activeId, selectedId]); + useEffect(() => { + if (resource.data && offset > 0 && offset >= resource.data.total) { + setOffset(Math.max(0, Math.floor((resource.data.total - 1) / 25) * 25)); + setSelectedId(null); + } + }, [resource.data, offset]); + const reset = () => { + setOffset(0); + setSelectedId(null); + setError(null); + }; + async function acknowledge() { + if (!selected || saving) return; + const target = selected; + setSaving(target.id); + setError(null); + try { + await requestJson( + '/api/alerts/' + encodeURIComponent(target.id) + '/acknowledge', + undefined, + { method: 'POST' }, + ); + setConfirmed(target.id); + resource.refresh(); + refreshAlerts(); + } catch (cause) { + setError( + 'Detection at ' + + clock(target.createdAt) + + ': ' + + (cause instanceof Error ? cause.message : 'Acknowledgment failed.'), + ); + } finally { + setSaving(null); + } + } + return ( +
+

Alerts

+
{ + event.preventDefault(); + setQ(draft); + reset(); + }} + > +
+ + setDraft(event.target.value)} + placeholder="Search alerts…" + aria-label="Search alerts" + /> + {draft && ( + + )} +
+
+ {[ + ['open', 'Open' + (alertStats ? ' (' + count(alertStats.unacknowledged) + ')' : '')], + [ + 'acknowledged', + 'Acknowledged' + + (alertStats + ? ' (' + count(alertStats.total - alertStats.unacknowledged) + ')' + : ''), + ], + ['all', 'All'], + ].map(([value, label]) => ( + + ))} +
+ + + +
+ {(error || resource.error) && ( +
+ {error ?? resource.error} + +
+ )} +
+
+
+

Alerts {resource.data && '(' + count(total) + ')'}

+ Newest first +
+
+ {alerts.map((alert) => ( +