diff --git a/services/fermentaladin-service/.dockerignore b/services/fermentaladin-service/.dockerignore new file mode 100644 index 0000000..bae0634 --- /dev/null +++ b/services/fermentaladin-service/.dockerignore @@ -0,0 +1,10 @@ +test/ +http-tests/ +*.md +.gitignore +.demo.env +Makefile +__pycache__/ +.ruff_cache/ +.git/ +.venv/ diff --git a/services/fermentaladin-service/Dockerfile b/services/fermentaladin-service/Dockerfile index 7f25cee..e938161 100644 --- a/services/fermentaladin-service/Dockerfile +++ b/services/fermentaladin-service/Dockerfile @@ -1,26 +1,24 @@ -FROM python:3.12-slim-trixie - -# The installer requires curl (and certificates) to download the release archive -RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ - && rm -rf /var/lib/apt/lists/* +# ---- build stage ---- +FROM python:3.12-slim-trixie AS builder -# Download the latest installer -ADD https://astral.sh/uv/install.sh /uv-installer.sh +COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /bin/ -# Run the installer then remove it -RUN sh /uv-installer.sh && rm /uv-installer.sh +COPY . /app +WORKDIR /app -# Ensure the installed binary is on the `PATH` -ENV PATH="/root/.local/bin/:$PATH" +# Sync into the project virtual environment; exclude dev dependencies +RUN uv sync --locked --no-dev -# Copy the project into the image -COPY . /app +# ---- runtime stage ---- +FROM python:3.12-slim-trixie -# Sync the project into a new environment, asserting the lockfile is up to date -# --no-dev: exclude dev dependencies (pytest, ruff) from the production image +# Copy only the application code and the ready-made virtual environment +COPY --from=builder /app /app WORKDIR /app -RUN uv sync --locked --no-dev + +# Place the virtual-env Python first on PATH so we don't need `uv run` +ENV PATH="/app/.venv/bin:$PATH" EXPOSE 8000 -CMD ["uv", "run", "uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src"] +CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src"] diff --git a/services/fermentaladin-service/README.md b/services/fermentaladin-service/README.md index 08d9e01..a4df847 100644 --- a/services/fermentaladin-service/README.md +++ b/services/fermentaladin-service/README.md @@ -52,6 +52,24 @@ uv run src/main.py -i excel -p ./test/Bioreactor_forPy.xlsx -o df -f ausgabe.jso This reads the model parameters from file ./test/Bioreactor_forPy.xlsx and creates a file ausgabe.json in a Dataframe format +### Usage inside Docker + +The Docker image uses a multi-stage build and does not include `uv`. Instead, the virtual environment is already activated via `PATH`, so you run `python` directly instead of `uv run`: + +```sh +# local +uv run src/main.py -i excel -p input.xlsx -o df -f ausgabe.json + +# inside Docker +python src/main.py -i excel -p input.xlsx -o df -f ausgabe.json +``` + +Example with `docker run`: + +```sh +docker run --rm fermentaladin-service python src/main.py -i file -p ./test/test_data/user_input.json -o df -f ausgabe.json +``` + ## Architecture ### Input diff --git a/services/graph-rewriting-service/.env.example b/services/graph-rewriting-service/.env.example index 0794eb6..e5278cd 100644 --- a/services/graph-rewriting-service/.env.example +++ b/services/graph-rewriting-service/.env.example @@ -1,7 +1,12 @@ -# App Environment. Can be one of: development, testing, production. Defaults to "development". -APP_ENV=development +# Application environment. Can be one of: development, production. Defaults to "production". +APP_ENV=production -# Connection data for neo4j docker container -NEO4J_URI=bolt://localhost:7687 +# Database backend: "memory" (default, no external DB needed) or "neo4j" +DB_BACKEND=memory + +# Neo4j connection configuration (only needed when DB_BACKEND=neo4j) +# In docker-compose, the host is 'neo4j' (the service name). +# When running locally, typically 'localhost'. +NEO4J_URI=bolt://neo4j:7687 NEO4J_USERNAME=neo4j -NEO4J_PASSWORD= \ No newline at end of file +NEO4J_PASSWORD=your-secure-password-here \ No newline at end of file diff --git a/services/graph-rewriting-service/Dockerfile b/services/graph-rewriting-service/Dockerfile index 16c5b33..a710c37 100644 --- a/services/graph-rewriting-service/Dockerfile +++ b/services/graph-rewriting-service/Dockerfile @@ -1,5 +1,5 @@ # ── Stage 1: builder ───────────────────────────────────────────────────────── -# Compiles TypeScript to JavaScript and installs production-only dependencies. +# Bundles TypeScript to JavaScript via esbuild. FROM node:22-slim AS builder WORKDIR /build @@ -8,52 +8,47 @@ WORKDIR /build COPY package.json package-lock.json ./ RUN npm ci -# Copy source and compile +# Copy source and build COPY tsconfig.json ./ +COPY scripts/ ./scripts/ COPY src/ ./src/ RUN npm run build -# Prune to production deps only -RUN npm ci --omit=dev - # ── Stage 2: runtime ────────────────────────────────────────────────────────── -# Uses the official Neo4j image as base so Neo4j is available out of the box. -# Node 22 is installed via NodeSource. -FROM neo4j:5.25.1 - -# Install Node.js 22 (NodeSource) and supervisord -RUN apt-get update && \ - apt-get install -y --no-install-recommends curl ca-certificates supervisor && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y --no-install-recommends nodejs && \ - apt-get clean && rm -rf /var/lib/apt/lists/* - -# Copy compiled app and production node_modules from builder +# @grafeo-db/js ships a native Rust addon compiled against glibc (gnu). +# Using node:22-slim (Debian/glibc) keeps the same libc as the builder so the +# native binding works. node:22-alpine (musl) would require the -musl variant. +FROM node:22-slim + WORKDIR /app -COPY --from=builder /build/dist ./dist -COPY --from=builder /build/node_modules ./node_modules -# Copy process manager config and entrypoint -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -COPY scripts/entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +# Copy bundled app output from builder +COPY --from=builder /build/dist ./dist -# Create log directory for supervisord -RUN mkdir -p /var/log/supervisor +# @grafeo-db/js ships a native Rust addon (.node file) that cannot be bundled +# by esbuild and is therefore externalized. Copy the packages so both the +# in-memory and neo4j backends are available at runtime. +COPY --from=builder /build/node_modules/@grafeo-db ./node_modules/@grafeo-db # ── Environment defaults ─────────────────────────────────────────────────────── -# NEO4J_AUTH is constructed in entrypoint.sh from USERNAME + PASSWORD. -# NEO4J_PASSWORD has a default, but should be supplied at runtime. +# DB_BACKEND defaults to "memory" (no external DB needed). +# Set DB_BACKEND=neo4j to use Neo4j and supply all three NEO4J_* vars; +# NEO4J_PASSWORD has no default and must be provided explicitly. ENV APP_ENV=production \ - NEO4J_URI=bolt://localhost:7687 \ - NEO4J_USERNAME=neo4j \ - NEO4J_PASSWORD=graph_rewriting + NODE_ENV=production \ + NEO4J_URI=bolt://neo4j:7687 \ + NEO4J_USERNAME=neo4j - -# ── Ports ────────────────────────────────────────────────────────────────────── +# ── Port ──────────────────────────────────────────────────────────────────────── # 8080 – Fastify HTTP API -# 7474 – Neo4j Browser UI -# 7687 – Neo4j Bolt -EXPOSE 8080 7474 7687 +EXPOSE 8080 + +# Health check (optional) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:8080/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" + +# CLI is also available inside the container: +# docker run --rm -e NEO4J_URI=... graph-rewriting-service node dist/cli.js transform /data/request.json -ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] +# Start API server by default +CMD ["node", "dist/index.js"] diff --git a/services/graph-rewriting-service/Makefile b/services/graph-rewriting-service/Makefile index dfecce8..e3f95e0 100644 --- a/services/graph-rewriting-service/Makefile +++ b/services/graph-rewriting-service/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test lint start clean compile-schemas generate-openapi docker-build +.PHONY: build test lint start clean compile-schemas generate-openapi docker-build smoke-test prep: npm i @@ -28,3 +28,6 @@ generate-openapi: docker-build: docker build -t graph-rewriting-service . + +smoke-test: + ./scripts/smoke-test.sh diff --git a/services/graph-rewriting-service/README.md b/services/graph-rewriting-service/README.md index dcf4d2d..ece0eb8 100644 --- a/services/graph-rewriting-service/README.md +++ b/services/graph-rewriting-service/README.md @@ -2,8 +2,8 @@ ## Prerequisites -This repository contains all files necessary to run the application in a local development environment. -It does depend on Docker and Node.js being available on your system. +This repository contains all files necessary to run the application in local development and production environments. +It requires Docker and Node.js 22+ on your system. When cloning this repository and opening it through VSCode, it will ask you to install all recommended VSCode extensions. @@ -15,12 +15,86 @@ In order to install and run this application in your local development environme Next an .env file should be created by copying the .env.example and setting the appropriate values. -## Running the application +### Development Environment -You may start the project by running the tasks `docker compose up` and `npm run dev` in your terminal. +The application supports two database backends: + +#### In-Memory (default, no Neo4j needed) + +Simply start the application — no external database required: + +```bash +npm run dev +``` + +#### With Neo4j + +To run with Neo4j as the database backend: + +1. Start Neo4j container: + ```bash + docker compose -f docker-compose.dev.yml up + ``` + +2. In another terminal, start the application with neo4j backend: + ```bash + DB_BACKEND=neo4j npm run dev + ``` If using VSCode you can instead run the VSCode-Task `Start dev environment` as a shortcut. +### Production Environment (Docker + Compose) + +To run the application in production using Docker containers: + +```bash +# Set required environment variables +export NEO4J_PASSWORD=your-secure-password + +# Start both Neo4j and the application service +docker compose -f docker-compose.prod.yml up -d +``` + +The application will be available at `http://localhost:8080`. +Neo4j Browser is accessible at `http://localhost:7474`. + +**Note:** As of this version, the Docker image is split into two services: +- `graph-rewriting-service`: Node.js API server (port 8080) +- `neo4j`: Graph database (ports 7687 for Bolt, 7474 for Browser) + +This separation reduces image size and enables independent scaling. + +## Architecture + +### Build Pipeline + +- `npm run build` now bundles the service with esbuild. +- `npm run typecheck` runs TypeScript checks without emitting files. +- The build emits bundled artifacts to `dist/index.js` and `dist/cli.js`. +- Static assets from `src/static` are copied to `dist/static` during the build. + +### Docker Images + +- **graph-rewriting-service**: Lightweight Node.js container based on `node:22-alpine` + - Runs the Fastify HTTP API + - Connects to Neo4j via environment variables + - Contains bundled runtime artifacts only (no `node_modules` in the final image) + - No embedded database or process manager + +- **neo4j** (from official image): Graph database service + - Provides pattern matching and graph transformation operations + - Can be deployed separately or in the same compose stack + +### Environment Variables + +| Variable | Default | Required | Description | +|----------|---------|----------|-------------| +| `APP_ENV` | `production` | No | Application environment: `development`, `production` | +| `DB_BACKEND` | `memory` | No | Database backend: `memory` (in-memory, no external DB) or `neo4j` | +| `NEO4J_URI` | `bolt://neo4j:7687` | No | Neo4j Bolt connection URI (only when `DB_BACKEND=neo4j`) | +| `NEO4J_USERNAME` | `neo4j` | No | Neo4j username (only when `DB_BACKEND=neo4j`) | +| `NEO4J_PASSWORD` | (none) | When neo4j | Neo4j password (must be set when using neo4j backend) | + ## Documentation For documentation of the rewrite and instantiation rules, please refer to the [Wiki](https://github.com/sonjaka/graph-rewriting-as-a-service/wiki). @@ -52,6 +126,48 @@ A very simple TicTacToe game againt a computer player powered by graph transform This testcase consists of a very basic web app built on the Vue.js Framework. You can install the project by first running `npm install`, then `npm run dev`. +## Testing + +### Unit Tests + +```bash +npm test +``` + +### Smoke Tests + +End-to-end smoke tests using `curl` against a running service. + +#### Memory backend only (no external dependencies) + +```bash +make smoke-test +``` + +#### Memory + Neo4j backend + +Start a Neo4j container first, then run the smoke test with Neo4j credentials: + +```bash +# Start Neo4j +NEO4J_USERNAME=neo4j NEO4J_PASSWORD= \ + docker compose -f docker-compose.dev.yml up -d neo4j + +# Run smoke tests for both backends +NEO4J_URI=bolt://localhost:7687 \ +NEO4J_USERNAME=neo4j \ +NEO4J_PASSWORD= \ + make smoke-test +``` + +When `NEO4J_URI` is not set, the Neo4j portion of the smoke test is skipped automatically. + +#### Teardown + +```bash +docker compose -f docker-compose.dev.yml down +``` + ## SwaggerUI / OpenAPI When the server is running, you can access the SwaggerUI / OpenAPI documentation via the following url: diff --git a/services/graph-rewriting-service/docker-compose.prod.yml b/services/graph-rewriting-service/docker-compose.prod.yml index 6f2ea69..52bbb0d 100644 --- a/services/graph-rewriting-service/docker-compose.prod.yml +++ b/services/graph-rewriting-service/docker-compose.prod.yml @@ -1,15 +1,56 @@ name: graph-rewriting-as-a-service-prod services: + neo4j: + image: neo4j:5.25.1 + container_name: graph-rewriting-neo4j + environment: + - NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:?NEO4J_PASSWORD must be set} + # Optional: to use APOC and Graph Data Science plugins, uncomment: + # - NEO4J_PLUGINS=["apoc", "graph-data-science"] + ports: + - '7474:7474' # Neo4j Browser + - '7687:7687' # Neo4j Bolt + volumes: + - neo4j_data:/data + - neo4j_logs:/logs + healthcheck: + test: ['CMD', 'cypher-shell', '-u', '${NEO4J_USERNAME:-neo4j}', '-p', '${NEO4J_PASSWORD}', 'RETURN 1'] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + graph-rewriting-service: build: . image: graph-rewriting-service:latest + container_name: graph-rewriting-service ports: - '8080:8080' - - '7474:7474' - - '7687:7687' environment: - APP_ENV=production + - DB_BACKEND=neo4j + - NEO4J_URI=bolt://neo4j:7687 - NEO4J_USERNAME=${NEO4J_USERNAME:-neo4j} - NEO4J_PASSWORD=${NEO4J_PASSWORD:?NEO4J_PASSWORD must be set} + depends_on: + neo4j: + condition: service_healthy + healthcheck: + test: + [ + 'CMD', + 'node', + '-e', + "require('http').get('http://localhost:8080/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1))", + ] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s restart: unless-stopped + +volumes: + neo4j_data: + neo4j_logs: diff --git a/services/graph-rewriting-service/package-lock.json b/services/graph-rewriting-service/package-lock.json index 078ba0e..2b5f44d 100644 --- a/services/graph-rewriting-service/package-lock.json +++ b/services/graph-rewriting-service/package-lock.json @@ -14,6 +14,7 @@ "@fastify/static": "^9.0.0", "@fastify/swagger": "^9.4.2", "@fastify/swagger-ui": "^5.2.1", + "@grafeo-db/js": "^0.5.40", "fastify": "^5.2.1", "fastify-plugin": "^5.0.1", "graphology": "^0.26.0", @@ -26,6 +27,9 @@ "randexp": "^0.5.3", "uuid": "^11.0.5" }, + "bin": { + "graph-rewriting-service": "dist/cli.js" + }, "devDependencies": { "@eslint/js": "^9.19.0", "@testcontainers/neo4j": "^10.17.1", @@ -33,6 +37,7 @@ "@types/json-logic-js": "^2.0.8", "@types/node": "^22.10.7", "@vitest/coverage-v8": "^3.0.2", + "esbuild": "^0.25.12", "eslint": "^9.19.0", "json-schema-to-typescript": "^15.0.4", "prettier": "^3.4.2", @@ -142,9 +147,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -159,9 +164,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -176,9 +181,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -193,9 +198,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -210,9 +215,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -227,9 +232,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -244,9 +249,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -261,9 +266,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -278,9 +283,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -295,9 +300,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -312,9 +317,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -329,9 +334,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -346,9 +351,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -363,9 +368,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -380,9 +385,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -397,9 +402,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -414,9 +419,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -431,9 +436,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "cpu": [ "arm64" ], @@ -448,9 +453,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -465,9 +470,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "cpu": [ "arm64" ], @@ -482,9 +487,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -498,10 +503,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -516,9 +538,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -533,9 +555,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -550,9 +572,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -1070,6 +1092,128 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@grafeo-db/js": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js/-/js-0.5.40.tgz", + "integrity": "sha512-T0hUKNahAoSSmUGeKHR55c5dKHzOqqXb3hbOA//KUm26VlymvM5wtsPcUG+O5FFnzSUcaptMZy+S2eAipJ9IFg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@grafeo-db/js-darwin-arm64": "0.5.40", + "@grafeo-db/js-darwin-x64": "0.5.40", + "@grafeo-db/js-linux-arm64-gnu": "0.5.40", + "@grafeo-db/js-linux-arm64-musl": "0.5.40", + "@grafeo-db/js-linux-x64-gnu": "0.5.40", + "@grafeo-db/js-win32-x64-msvc": "0.5.40" + } + }, + "node_modules/@grafeo-db/js-darwin-arm64": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-darwin-arm64/-/js-darwin-arm64-0.5.40.tgz", + "integrity": "sha512-dUu2iG+S8MYrElR3TP8rdjd5CzLXlqpoaz1Qq2Pr+MuuyeTBJTRFdX0Xd943CMSHA75RifwfxH9IcetvWNWjnA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@grafeo-db/js-darwin-x64": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-darwin-x64/-/js-darwin-x64-0.5.40.tgz", + "integrity": "sha512-spO5pkKCtxRD5mPCCHnHIJq7/VcCOyy/OxvWFtssWL04LklhUhM9sOhX0HILdMkWweCdm0RB9nObRdsDVgOSmg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@grafeo-db/js-linux-arm64-gnu": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-linux-arm64-gnu/-/js-linux-arm64-gnu-0.5.40.tgz", + "integrity": "sha512-KZ0O3S3dM3aNwztHycSQ9PW+ykx8r6zft4Rk59k8OSyRVmKyI8zmJMENsPuWM/XDTz4jRADOX8QPERq2FFIsvA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@grafeo-db/js-linux-arm64-musl": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-linux-arm64-musl/-/js-linux-arm64-musl-0.5.40.tgz", + "integrity": "sha512-OuiJxKJ8FV4qY+wRZxevx2YmHv5L85z70Ye4U5Aq1BFL8pe+Mev6sLvVfo9SFL4oRCloMviAxsE9TT5u5w8o1A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@grafeo-db/js-linux-x64-gnu": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-linux-x64-gnu/-/js-linux-x64-gnu-0.5.40.tgz", + "integrity": "sha512-5HYDpe1KQFG6lKhlvwiJiRpntmkf6Tl1NTd7nBf0esNzWgki4CLikBQnnRbgGp1tbhl0C33rN1tKHK736RqgHQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@grafeo-db/js-win32-x64-msvc": { + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/@grafeo-db/js-win32-x64-msvc/-/js-win32-x64-msvc-0.5.40.tgz", + "integrity": "sha512-zFGDZ7leZpatFy6hAePt6tnb5+ngsKd1UvkTuCacDrjFPDiJAbD1K+BYHQ9HBVWksqIDZGd7zIIlMSNgz06Cyw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1723,7 +1867,6 @@ "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", @@ -2065,7 +2208,6 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3026,9 +3168,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3039,31 +3181,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escape-html": { @@ -3091,7 +3234,6 @@ "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3750,8 +3892,7 @@ "version": "0.24.8", "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz", "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -4042,7 +4183,6 @@ "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 10.16.0" } @@ -4671,7 +4811,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5818,7 +5957,6 @@ "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -5859,7 +5997,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5947,7 +6084,6 @@ "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", @@ -6043,7 +6179,6 @@ "integrity": "sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "3.0.7", "@vitest/mocker": "3.0.7", diff --git a/services/graph-rewriting-service/package.json b/services/graph-rewriting-service/package.json index 8b4b1af..08f8d8f 100644 --- a/services/graph-rewriting-service/package.json +++ b/services/graph-rewriting-service/package.json @@ -4,11 +4,16 @@ "author": "Sonja Hartl", "description": "", "main": "index.js", + "bin": { + "graph-rewriting-service": "dist/cli.js" + }, "scripts": { - "build": "tsc -p tsconfig.json", + "build": "node scripts/build.mjs", + "typecheck": "tsc --noEmit -p tsconfig.json", "dev": "NODE_ENV=development tsx watch --clear-screen=false src/index.ts", "dev:debug": "NODE_ENV=development tsx watch --inspect-brk --clear-screen=false src/index.ts", "start": "node dist/index.js", + "cli": "tsx src/cli.ts", "test": "vitest --coverage", "generate:openapi": "NODE_ENV=test tsx scripts/generate-openapi.ts", "prettier": "npx prettier ./src --check", @@ -28,6 +33,7 @@ "@types/json-logic-js": "^2.0.8", "@types/node": "^22.10.7", "@vitest/coverage-v8": "^3.0.2", + "esbuild": "^0.25.12", "eslint": "^9.19.0", "json-schema-to-typescript": "^15.0.4", "prettier": "^3.4.2", @@ -43,6 +49,7 @@ "@fastify/static": "^9.0.0", "@fastify/swagger": "^9.4.2", "@fastify/swagger-ui": "^5.2.1", + "@grafeo-db/js": "^0.5.40", "fastify": "^5.2.1", "fastify-plugin": "^5.0.1", "graphology": "^0.26.0", @@ -55,4 +62,4 @@ "randexp": "^0.5.3", "uuid": "^11.0.5" } -} \ No newline at end of file +} diff --git a/services/graph-rewriting-service/scripts/build.mjs b/services/graph-rewriting-service/scripts/build.mjs new file mode 100644 index 0000000..f5ec752 --- /dev/null +++ b/services/graph-rewriting-service/scripts/build.mjs @@ -0,0 +1,47 @@ +import { build } from 'esbuild'; +import { chmod, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const distDir = resolve('dist'); +const staticSourceDir = resolve('src/static'); +const staticTargetDir = resolve(distDir, 'static'); +const swaggerUiStaticDir = resolve('node_modules/@fastify/swagger-ui/static'); + +const sharedOptions = { + bundle: true, + format: 'cjs', + minify: true, + platform: 'node', + target: 'node22', + // @grafeo-db/js ships a native Rust addon (.node file) that esbuild cannot bundle + external: ['@grafeo-db/js', '@grafeo-db/js-linux-x64-gnu', '@grafeo-db/js-linux-arm64-gnu'], +}; + +await rm(distDir, { force: true, recursive: true }); +await mkdir(distDir, { recursive: true }); + +await Promise.all([ + build({ + ...sharedOptions, + entryPoints: ['src/index.ts'], + outfile: 'dist/index.js', + }), + build({ + ...sharedOptions, + entryPoints: ['src/cli.ts'], + outfile: 'dist/cli.js', + }), +]); + +const cliEntry = resolve(distDir, 'cli.js'); +const cliContent = await readFile(cliEntry, 'utf8'); +if (!cliContent.startsWith('#!/usr/bin/env node')) { + await writeFile(cliEntry, `#!/usr/bin/env node\n${cliContent}`, 'utf8'); +} + +await mkdir(staticTargetDir, { recursive: true }); +// Copy src/static (project's own static files) +await cp(staticSourceDir, staticTargetDir, { recursive: true }); +// Copy @fastify/swagger-ui static files for Swagger UI +await cp(swaggerUiStaticDir, staticTargetDir, { recursive: true }); +await chmod(cliEntry, 0o755); diff --git a/services/graph-rewriting-service/scripts/entrypoint.sh b/services/graph-rewriting-service/scripts/entrypoint.sh deleted file mode 100644 index 8ca78fd..0000000 --- a/services/graph-rewriting-service/scripts/entrypoint.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -set -e - -# Build NEO4J_AUTH from individual credential env vars so callers only need to -# supply NEO4J_USERNAME and NEO4J_PASSWORD. -export NEO4J_AUTH="${NEO4J_USERNAME}/${NEO4J_PASSWORD}" - -echo "Starting Neo4j..." -neo4j start - -# Poll the Bolt port until Neo4j is ready to accept connections. -# /dev/tcp is a bash built-in — requires bash, not dash. -echo "Waiting for Neo4j Bolt port (7687)..." -until (echo > /dev/tcp/localhost/7687) 2>/dev/null; do - sleep 1 -done -echo "Neo4j is ready." - -# Pipe Neo4j logs to stdout so they appear in 'docker logs'. -tail -f /var/lib/neo4j/logs/neo4j.log & - -# Start supervisord as PID 1. It manages the Fastify app process. -# Neo4j runs as its own self-managed Java process in the background. -exec supervisord -n -c /etc/supervisor/conf.d/supervisord.conf diff --git a/services/graph-rewriting-service/scripts/smoke-test.sh b/services/graph-rewriting-service/scripts/smoke-test.sh new file mode 100755 index 0000000..9b604c4 --- /dev/null +++ b/services/graph-rewriting-service/scripts/smoke-test.sh @@ -0,0 +1,482 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- +# smoke-test.sh — End-to-end smoke tests for graph-rewriting-service +# +# Usage: +# ./scripts/smoke-test.sh [BASE_URL] +# +# Environment variables: +# BASE_URL Service base URL (default: http://localhost:8080) +# +# This script tests two scenarios: +# 1. In-memory backend (DB_BACKEND=memory) — no Neo4j required +# 2. Neo4j backend (DB_BACKEND=neo4j) — requires Neo4j via docker-compose +# +# Exit code: 0 = all tests passed, 1 = one or more failures +# --------------------------------------------------------------------------- +set -euo pipefail + +BASE_URL="${1:-${BASE_URL:-http://127.0.0.1:8080}}" + +PASS=0 +FAIL=0 + +# ---- helpers --------------------------------------------------------------- + +green() { printf '\033[0;32m%s\033[0m\n' "$*"; } +red() { printf '\033[0;31m%s\033[0m\n' "$*"; } + +assert() { + local label="$1" + local expected_status="$2" + local actual_status="$3" + local body="$4" + + if [[ "$actual_status" == "$expected_status" ]]; then + green " PASS [$actual_status] $label" + (( PASS++ )) || true + else + red " FAIL [$actual_status != $expected_status] $label" + red " body: $body" + (( FAIL++ )) || true + fi +} + +# Usage: get LABEL EXPECTED_STATUS URL +get() { + local label="$1" expected="$2" url="$3" + local response + response=$(curl -s -w '\n%{http_code}' "$url") + local http_body http_status + http_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -n 1) + assert "$label" "$expected" "$http_status" "$http_body" +} + +# Usage: post LABEL EXPECTED_STATUS URL JSON_BODY +post() { + local label="$1" expected="$2" url="$3" body="$4" + local response + response=$(curl -s -w '\n%{http_code}' -X POST "$url" \ + -H 'Content-Type: application/json' \ + -d "$body") + local http_body http_status + http_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -n 1) + assert "$label" "$expected" "$http_status" "$http_body" +} + +# Usage: post_body_contains LABEL EXPECTED_STATUS URL JSON_BODY NEEDLE +post_body_contains() { + local label="$1" expected="$2" url="$3" body="$4" needle="$5" + local response + response=$(curl -s -w '\n%{http_code}' -X POST "$url" \ + -H 'Content-Type: application/json' \ + -d "$body") + local http_body http_status + http_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -n 1) + assert "$label (HTTP $expected)" "$expected" "$http_status" "$http_body" + if [[ "$http_status" == "$expected" ]]; then + if echo "$http_body" | grep -q "$needle"; then + green " PASS body contains \"$needle\"" + (( PASS++ )) || true + else + red " FAIL body does not contain \"$needle\"" + red " body: $http_body" + (( FAIL++ )) || true + fi + fi +} + +wait_for_service() { + local url="$1" + echo -n " Waiting for service at ${url}..." + for _ in $(seq 1 15); do + if curl -sf "${url}/health" >/dev/null 2>&1; then + echo " OK" + return 0 + fi + echo -n "." + sleep 1 + done + echo " TIMEOUT" + return 1 +} + +# ---- shared test payloads -------------------------------------------------- + +TRANSFORM_PAYLOAD='{ + "hostgraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A", "type": "Event" } }, + { "key": "B", "attributes": { "label": "B", "type": "Function" } } + ], + "edges": [ + { "key": "aToB", "source": "A", "target": "B", "attributes": {} } + ] + }, + "rules": [ + { + "key": "add_node", + "patternGraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A" } } + ], + "edges": [] + }, + "replacementGraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A" } }, + { "key": "C", "attributes": { "label": "C", "type": "NewNode" } } + ], + "edges": [ + { "key": "aToC", "source": "A", "target": "C", "attributes": {} } + ] + } + } + ] +}' + +FIND_PAYLOAD='{ + "hostgraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A", "type": "Event" } }, + { "key": "B", "attributes": { "label": "B", "type": "Function" } } + ], + "edges": [ + { "key": "aToB", "source": "A", "target": "B", "attributes": {} } + ] + }, + "rules": [ + { + "key": "find_node", + "patternGraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": {} } + ], + "edges": [] + } + } + ] +}' + +HOSTGRAPH_PAYLOAD='{ + "hostgraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "X", "attributes": { "label": "X" } }, + { "key": "Y", "attributes": { "label": "Y" } } + ], + "edges": [ + { "key": "xToY", "source": "X", "target": "Y", "attributes": {} } + ] + }, + "rules": [] +}' + +NODE_PAYLOAD='{ "key": "smokeNode1", "attributes": { "label": "SmokeTest" } }' +EDGE_PAYLOAD='{ "key": "smokeEdge1", "source": "smokeNode1", "target": "smokeNode2", "attributes": { "type": "test" } }' + +# ---- core API tests (run for both backends) -------------------------------- + +run_api_tests() { + local backend="$1" + echo "" + echo "── API tests (backend: ${backend}) ─────────────────────────────────" + + # 1. Health check + get "health" "200" "${BASE_URL}/health" + + # 2. Create node + post "create node" "201" "${BASE_URL}/node" "$NODE_PAYLOAD" + + # 3. Get node + get "get node" "200" "${BASE_URL}/node/smokeNode1" + + # 4. Create second node for edge + post "create node 2" "201" "${BASE_URL}/node" \ + '{ "key": "smokeNode2", "attributes": { "label": "SmokeTest2" } }' + + # 5. Create edge + post "create edge" "201" "${BASE_URL}/edge" "$EDGE_PAYLOAD" + + # 6. Get edge + get "get edge" "200" "${BASE_URL}/edge/smokeEdge1" + + # 7. Get all nodes + get "get all nodes" "200" "${BASE_URL}/nodes" + + # 8. Delete edge + local del_response + del_response=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "${BASE_URL}/edge/smokeEdge1") + assert "delete edge" "204" "$del_response" "" + + # 9. Delete node + del_response=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "${BASE_URL}/node/smokeNode1") + assert "delete node" "204" "$del_response" "" + + # 10. Delete all nodes + del_response=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "${BASE_URL}/nodes") + assert "delete all nodes" "204" "$del_response" "" + + # 11. Import hostgraph via /hostgraph + post_body_contains "import hostgraph" "200" "${BASE_URL}/hostgraph" \ + "$HOSTGRAPH_PAYLOAD" "success" + + # 12. Transform + post_body_contains "transform graph" "200" "${BASE_URL}/transform" \ + "$TRANSFORM_PAYLOAD" "NewNode" + + # 13. Find pattern + post_body_contains "find pattern" "200" "${BASE_URL}/find" \ + "$FIND_PAYLOAD" "data" + + # 14. Get node not found + get "get missing node (404)" "404" "${BASE_URL}/node/nonexistent_node_xyz" + + # 15. Invalid transform payload + post "invalid transform (400)" "400" "${BASE_URL}/transform" \ + '{"invalid": "payload"}' +} + +# ---- memory backend tests -------------------------------------------------- + +memory_tests() { + echo "" + echo "══════════════════════════════════════════════════════════════════════" + echo " Testing IN-MEMORY backend (container)" + echo "══════════════════════════════════════════════════════════════════════" + + local CONTAINER_NAME="grs-smoke-memory" + local IMAGE_NAME="graph-rewriting-service:smoke-test" + + # Build Docker image (includes its own npm run build via builder stage) + echo " Building Docker image..." + if ! docker build -t "$IMAGE_NAME" . >/dev/null 2>&1; then + red " FAIL Docker image build failed" + (( FAIL++ )) || true + return + fi + echo " Docker image built." + + # Remove any leftover container from a previous interrupted run + docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + + # Free port 8080 if something else is already bound to it + local occupant + occupant=$(docker ps -q --filter "publish=8080" 2>/dev/null) + if [[ -n "$occupant" ]]; then + echo " Removing container occupying port 8080 ($occupant)..." + docker rm -f "$occupant" >/dev/null 2>&1 || true + sleep 1 + fi + + # Start container with memory backend (default, but explicit for clarity) + docker run -d --name "$CONTAINER_NAME" \ + -p 8080:8080 \ + -e DB_BACKEND=memory \ + "$IMAGE_NAME" >/dev/null + + trap "docker rm -f $CONTAINER_NAME >/dev/null 2>&1 || true" RETURN + + if ! wait_for_service "$BASE_URL"; then + red " FAIL Container did not start (memory backend)" + (( FAIL++ )) || true + docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + return + fi + + run_api_tests "memory (container)" + + # Memory-specific health check + get "memory health" "200" "${BASE_URL}/memory/health" + + docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + sleep 1 +} + +# ---- neo4j backend tests --------------------------------------------------- + +neo4j_tests() { + if [[ -z "${NEO4J_URI:-}" ]]; then + echo "" + echo "══════════════════════════════════════════════════════════════════════" + echo " Neo4j backend — SKIPPED (NEO4J_URI not set)" + echo " Set NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD to run these tests." + echo "══════════════════════════════════════════════════════════════════════" + return + fi + + echo "" + echo "══════════════════════════════════════════════════════════════════════" + echo " Testing NEO4J backend (${NEO4J_URI})" + echo "══════════════════════════════════════════════════════════════════════" + + DB_BACKEND=neo4j APP_ENV=production node dist/index.js & + local SERVER_PID=$! + trap "kill $SERVER_PID 2>/dev/null || true" RETURN + + if ! wait_for_service "$BASE_URL"; then + red " FAIL Server did not start (neo4j backend)" + (( FAIL++ )) || true + kill "$SERVER_PID" 2>/dev/null || true + return + fi + + run_api_tests "neo4j" + + # Neo4j-specific health check + get "neo4j health" "200" "${BASE_URL}/neo4j/health" + + kill "$SERVER_PID" 2>/dev/null || true + sleep 1 +} + +# ---- CLI tests (Neo4j only) ------------------------------------------------ + +cli_tests() { + if [[ -z "${NEO4J_URI:-}" ]]; then + echo "" + echo "══════════════════════════════════════════════════════════════════════" + echo " CLI tests — SKIPPED (NEO4J_URI not set)" + echo " Set NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD to run these tests." + echo "══════════════════════════════════════════════════════════════════════" + return + fi + + echo "" + echo "══════════════════════════════════════════════════════════════════════" + echo " Testing CLI (Neo4j backend: ${NEO4J_URI})" + echo "══════════════════════════════════════════════════════════════════════" + + local cli="node dist/cli.js" + local tmpdir + tmpdir=$(mktemp -d) + + # helper: run CLI command, check exit code and optionally a string in output + cli_ok() { + local label="$1" needle="$2" + shift 2 + local output + if output=$($cli "$@" 2>&1); then + if [[ -n "$needle" ]] && ! echo "$output" | grep -q "$needle"; then + red " FAIL $label — missing \"$needle\" in output" + red " output: $output" + (( FAIL++ )) || true + else + green " PASS $label" + (( PASS++ )) || true + fi + else + red " FAIL $label — command exited with error" + red " output: $output" + (( FAIL++ )) || true + fi + } + + # Write fixture files + cat > "$tmpdir/node1.json" <<'EOF' +{ "key": "cliNode1", "attributes": { "label": "CLI-Test" } } +EOF + cat > "$tmpdir/node2.json" <<'EOF' +{ "key": "cliNode2", "attributes": { "label": "CLI-Test2" } } +EOF + cat > "$tmpdir/edge.json" <<'EOF' +{ "key": "cliEdge1", "source": "cliNode1", "target": "cliNode2", "attributes": { "type": "cli-test" } } +EOF + cat > "$tmpdir/transform.json" <<'EOF' +{ + "hostgraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A", "type": "Event" } }, + { "key": "B", "attributes": { "label": "B", "type": "Function" } } + ], + "edges": [{ "key": "aToB", "source": "A", "target": "B", "attributes": {} }] + }, + "rules": [{ + "key": "add_node", + "patternGraph": { + "options": { "type": "directed" }, + "nodes": [{ "key": "A", "attributes": { "label": "A" } }], + "edges": [] + }, + "replacementGraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A" } }, + { "key": "C", "attributes": { "label": "C", "type": "NewNode" } } + ], + "edges": [{ "key": "aToC", "source": "A", "target": "C", "attributes": {} }] + } + }] +} +EOF + cat > "$tmpdir/find.json" <<'EOF' +{ + "hostgraph": { + "options": { "type": "directed" }, + "nodes": [ + { "key": "A", "attributes": { "label": "A", "type": "Event" } }, + { "key": "B", "attributes": { "label": "B", "type": "Function" } } + ], + "edges": [{ "key": "aToB", "source": "A", "target": "B", "attributes": {} }] + }, + "rules": [{ + "key": "find_node", + "patternGraph": { + "options": { "type": "directed" }, + "nodes": [{ "key": "A", "attributes": {} }], + "edges": [] + } + }] +} +EOF + + # 1. Node CRUD + cli_ok "cli: create-node" '"key"' create-node "$tmpdir/node1.json" + cli_ok "cli: create-node 2" '"key"' create-node "$tmpdir/node2.json" + cli_ok "cli: get-nodes" 'cliNode1' get-nodes + cli_ok "cli: get-node" '"label"' get-node "cliNode1" + + # 2. Edge CRUD + cli_ok "cli: create-edge" '"key"' create-edge "$tmpdir/edge.json" + cli_ok "cli: get-edge" '"key"' get-edge "cliEdge1" + + # 3. Graph transformation (stateless — operates on provided hostgraph) + cli_ok "cli: transform" 'NewNode' transform "$tmpdir/transform.json" + cli_ok "cli: find" 'nodes' find "$tmpdir/find.json" + + # 4. Cleanup + cli_ok "cli: delete-edge" '' delete-edge "cliEdge1" + cli_ok "cli: delete-node" '' delete-node "cliNode1" + cli_ok "cli: delete-nodes" '' delete-nodes + + rm -rf "$tmpdir" +} + +# ---- main ------------------------------------------------------------------ + +echo "graph-rewriting-service smoke tests" +echo " BASE_URL: ${BASE_URL}" + +# Always rebuild dist to ensure latest code is used +echo " Building dist/..." +npm run build + +memory_tests +neo4j_tests +cli_tests + +echo "" +echo "════════════════════════════════════════════════════════════════════════" +echo "Results: ${PASS} passed, ${FAIL} failed" +echo "════════════════════════════════════════════════════════════════════════" + +[[ "$FAIL" -eq 0 ]] diff --git a/services/graph-rewriting-service/src/app.ts b/services/graph-rewriting-service/src/app.ts index b1f712a..18d399d 100644 --- a/services/graph-rewriting-service/src/app.ts +++ b/services/graph-rewriting-service/src/app.ts @@ -17,6 +17,7 @@ import { CorsOptions } from './config/cors'; import healthRoutes from './routes/health'; import neo4jConnector from './plugins/neo4j/index'; +import memoryConnector from './plugins/memory/index'; import grsPlugin from './plugins/grs/index'; import { logger } from './utils/logger'; @@ -48,8 +49,13 @@ export function buildServer(): FastifyInstance { server.register(healthRoutes); - // Use neo4j - server.register(neo4jConnector); + // Register database backend based on DB_BACKEND env var + if (appEnvConfig.DB_BACKEND === 'neo4j') { + server.register(neo4jConnector); + } else { + server.register(memoryConnector); + } + // Add grs plugin server.register(grsPlugin); diff --git a/services/graph-rewriting-service/src/cli.test.ts b/services/graph-rewriting-service/src/cli.test.ts new file mode 100644 index 0000000..b944b65 --- /dev/null +++ b/services/graph-rewriting-service/src/cli.test.ts @@ -0,0 +1,543 @@ +import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { + parseArgs, + readJsonInput, + outputResult, + cmdTransform, + cmdFind, + cmdImport, + cmdGetNodes, + cmdGetNode, + cmdCreateNode, + cmdDeleteNode, + cmdDeleteNodes, + cmdGetEdge, + cmdCreateEdge, + cmdDeleteEdge, +} from './cli'; +import { Neo4jGraphService } from './service/db/neo4j/graph.service'; +import { GraphSchema } from './types/graph.schema'; + +// Mock fs module +vi.mock('node:fs', () => ({ + readFileSync: vi.fn(), + writeFileSync: vi.fn(), +})); + +// Mock dotenv (imported at top of cli.ts) +vi.mock('dotenv/config', () => ({})); + +// Mock neo4j-driver (imported at top of cli.ts) +vi.mock('neo4j-driver', () => ({ + default: { + driver: vi.fn(), + auth: { basic: vi.fn() }, + }, +})); + +// ─── Sample data ──────────────────────────────────────────────────────────── + +const sampleGraph: GraphSchema = { + options: { type: 'directed' }, + nodes: [ + { key: 'A', attributes: { label: 'Start' } }, + { key: 'B', attributes: { label: 'End' } }, + ], + edges: [{ key: 'e1', source: 'A', target: 'B', attributes: {} }], +}; + +const sampleTransformRequest = { + hostgraph: sampleGraph, + rules: [], + sequence: [], + options: {}, +}; + +const sampleFindRequest = { + hostgraph: sampleGraph, + rules: [], +}; + +// ─── parseArgs ────────────────────────────────────────────────────────────── + +describe('parseArgs', () => { + let exitSpy: ReturnType; + + beforeEach(() => { + exitSpy = vi + .spyOn(process, 'exit') + .mockImplementation(() => undefined as never); + }); + + afterEach(() => { + exitSpy.mockRestore(); + }); + + test('parses command with positional arg', () => { + const result = parseArgs(['node', 'cli.js', 'transform', 'input.json']); + expect(result).toEqual({ + command: 'transform', + positional: 'input.json', + outputPath: undefined, + }); + }); + + test('parses command with -o flag', () => { + const result = parseArgs([ + 'node', + 'cli.js', + 'transform', + 'input.json', + '-o', + 'output.json', + ]); + expect(result).toEqual({ + command: 'transform', + positional: 'input.json', + outputPath: 'output.json', + }); + }); + + test('parses command with --output flag', () => { + const result = parseArgs([ + 'node', + 'cli.js', + 'find', + 'data.json', + '--output', + 'result.json', + ]); + expect(result).toEqual({ + command: 'find', + positional: 'data.json', + outputPath: 'result.json', + }); + }); + + test('parses command without positional (e.g. get-nodes)', () => { + const result = parseArgs(['node', 'cli.js', 'get-nodes']); + expect(result).toEqual({ + command: 'get-nodes', + positional: undefined, + outputPath: undefined, + }); + }); + + test('exits on --help', () => { + parseArgs(['node', 'cli.js', '--help']); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + + test('exits on -h', () => { + parseArgs(['node', 'cli.js', '-h']); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + + test('exits when no args provided', () => { + parseArgs(['node', 'cli.js']); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + + test('exits on -o without value', () => { + parseArgs(['node', 'cli.js', 'transform', 'input.json', '-o']); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + + test('exits on unexpected extra argument', () => { + parseArgs(['node', 'cli.js', 'transform', 'input.json', 'extra-arg']); + expect(exitSpy).toHaveBeenCalledWith(1); + }); +}); + +// ─── readJsonInput ────────────────────────────────────────────────────────── + +describe('readJsonInput', () => { + let exitSpy: ReturnType; + + beforeEach(() => { + exitSpy = vi + .spyOn(process, 'exit') + .mockImplementation(() => undefined as never); + vi.mocked(readFileSync).mockReset(); + }); + + afterEach(() => { + exitSpy.mockRestore(); + }); + + test('reads and parses a JSON file', () => { + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(sampleGraph)); + + const result = readJsonInput('test.json'); + + expect(readFileSync).toHaveBeenCalledWith(resolve('test.json'), 'utf-8'); + expect(result).toEqual(sampleGraph); + }); + + test('reads from stdin when path is "-"', () => { + vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ ok: true })); + + const result = readJsonInput('-'); + + expect(readFileSync).toHaveBeenCalledWith(0, 'utf-8'); + expect(result).toEqual({ ok: true }); + }); + + test('exits on read error', () => { + vi.mocked(readFileSync).mockImplementation(() => { + throw new Error('file not found'); + }); + + readJsonInput('missing.json'); + expect(exitSpy).toHaveBeenCalledWith(1); + }); + + test('exits on invalid JSON', () => { + vi.mocked(readFileSync).mockReturnValue('not valid json {{{'); + + readJsonInput('bad.json'); + expect(exitSpy).toHaveBeenCalledWith(1); + }); +}); + +// ─── outputResult ─────────────────────────────────────────────────────────── + +describe('outputResult', () => { + let consoleSpy: ReturnType; + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.mocked(writeFileSync).mockReset(); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + }); + + test('writes to stdout when no output path', () => { + outputResult({ key: 'value' }); + expect(consoleSpy).toHaveBeenCalledWith( + JSON.stringify({ key: 'value' }, null, 2) + ); + }); + + test('writes to file when output path given', () => { + outputResult({ key: 'value' }, 'out.json'); + expect(writeFileSync).toHaveBeenCalledWith( + resolve('out.json'), + JSON.stringify({ key: 'value' }, null, 2) + '\n', + 'utf-8' + ); + expect(consoleSpy).not.toHaveBeenCalled(); + }); +}); + +// ─── Command handlers ─────────────────────────────────────────────────────── + +describe('CLI command handlers', () => { + let mockGraphService: Neo4jGraphService; + let consoleSpy: ReturnType; + let consoleErrorSpy: ReturnType; + let exitSpy: ReturnType; + + beforeEach(() => { + mockGraphService = { + createNode: vi + .fn() + .mockResolvedValue({ key: 'new-node', attributes: {} }), + getNode: vi.fn(), + getAllNodes: vi.fn().mockResolvedValue(sampleGraph.nodes), + deleteNode: vi.fn().mockResolvedValue(undefined), + deleteAllNodes: vi.fn().mockResolvedValue([]), + createEdge: vi.fn().mockResolvedValue({ + key: 'new-edge', + source: 'A', + target: 'B', + attributes: {}, + }), + getEdge: vi.fn(), + deleteEdge: vi.fn().mockResolvedValue(undefined), + getAllEdges: vi.fn().mockResolvedValue(sampleGraph.edges), + } as unknown as Neo4jGraphService; + + // eslint-disable-next-line @typescript-eslint/no-empty-function + consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + // eslint-disable-next-line @typescript-eslint/no-empty-function + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + exitSpy = vi + .spyOn(process, 'exit') + .mockImplementation(() => undefined as never); + vi.mocked(readFileSync).mockReset(); + vi.mocked(writeFileSync).mockReset(); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + exitSpy.mockRestore(); + vi.restoreAllMocks(); + }); + + // ── transform ─────────────────────────────────────────────────────── + + describe('cmdTransform', () => { + test('calls transformGraph and outputs result', async () => { + vi.mocked(readFileSync).mockReturnValue( + JSON.stringify(sampleTransformRequest) + ); + + const transformResult = [sampleGraph]; + const mockTransformGraph = vi.fn().mockResolvedValue(transformResult); + vi.spyOn( + await import('./service/grs/graph-transformation.service'), + 'GraphTransformationService' + ).mockImplementation( + () => + ({ + transformGraph: mockTransformGraph, + }) as unknown as InstanceType< + typeof import('./service/grs/graph-transformation.service').GraphTransformationService + > + ); + + await cmdTransform(mockGraphService, 'transform-req.json'); + + expect(mockTransformGraph).toHaveBeenCalledWith( + sampleTransformRequest.hostgraph, + sampleTransformRequest.rules, + sampleTransformRequest.sequence, + sampleTransformRequest.options + ); + expect(consoleSpy).toHaveBeenCalled(); + }); + + test('writes to file with -o', async () => { + vi.mocked(readFileSync).mockReturnValue( + JSON.stringify(sampleTransformRequest) + ); + + const transformResult = [sampleGraph]; + vi.spyOn( + await import('./service/grs/graph-transformation.service'), + 'GraphTransformationService' + ).mockImplementation( + () => + ({ + transformGraph: vi.fn().mockResolvedValue(transformResult), + }) as unknown as InstanceType< + typeof import('./service/grs/graph-transformation.service').GraphTransformationService + > + ); + + await cmdTransform(mockGraphService, 'req.json', 'out.json'); + + expect(writeFileSync).toHaveBeenCalledWith( + resolve('out.json'), + expect.any(String), + 'utf-8' + ); + }); + }); + + // ── find ──────────────────────────────────────────────────────────── + + describe('cmdFind', () => { + test('calls matchPattern and outputs result', async () => { + vi.mocked(readFileSync).mockReturnValue( + JSON.stringify(sampleFindRequest) + ); + + const findResult = [sampleGraph]; + const mockMatchPattern = vi.fn().mockResolvedValue(findResult); + vi.spyOn( + await import('./service/grs/graph-transformation.service'), + 'GraphTransformationService' + ).mockImplementation( + () => + ({ + matchPattern: mockMatchPattern, + }) as unknown as InstanceType< + typeof import('./service/grs/graph-transformation.service').GraphTransformationService + > + ); + + await cmdFind(mockGraphService, 'find-req.json'); + + expect(mockMatchPattern).toHaveBeenCalledWith( + sampleFindRequest.hostgraph, + sampleFindRequest.rules + ); + expect(consoleSpy).toHaveBeenCalled(); + }); + }); + + // ── import ────────────────────────────────────────────────────────── + + describe('cmdImport', () => { + test('calls importHostgraph and outputs result', async () => { + vi.mocked(readFileSync).mockReturnValue( + JSON.stringify({ hostgraph: sampleGraph }) + ); + + const importResult = sampleGraph; + const mockImportHostgraph = vi.fn().mockResolvedValue(importResult); + vi.spyOn( + await import('./service/grs/graph-transformation.service'), + 'GraphTransformationService' + ).mockImplementation( + () => + ({ + importHostgraph: mockImportHostgraph, + }) as unknown as InstanceType< + typeof import('./service/grs/graph-transformation.service').GraphTransformationService + > + ); + + await cmdImport(mockGraphService, 'import-req.json'); + + expect(mockImportHostgraph).toHaveBeenCalledWith(sampleGraph); + expect(consoleSpy).toHaveBeenCalled(); + }); + }); + + // ── node CRUD ─────────────────────────────────────────────────────── + + describe('cmdGetNodes', () => { + test('lists all nodes', async () => { + await cmdGetNodes(mockGraphService); + + expect(mockGraphService.getAllNodes).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + JSON.stringify(sampleGraph.nodes, null, 2) + ); + }); + }); + + describe('cmdGetNode', () => { + test('returns a node by id', async () => { + const node = { key: 'A', attributes: { label: 'Start' } }; + vi.mocked(mockGraphService.getNode).mockResolvedValue(node); + + await cmdGetNode(mockGraphService, 'A'); + + expect(mockGraphService.getNode).toHaveBeenCalledWith('A'); + expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(node, null, 2)); + }); + + test('exits with error when node not found', async () => { + vi.mocked(mockGraphService.getNode).mockResolvedValue(undefined); + + await cmdGetNode(mockGraphService, 'missing'); + + expect(exitSpy).toHaveBeenCalledWith(1); + }); + }); + + describe('cmdCreateNode', () => { + test('creates a node from JSON file', async () => { + const nodeInput = { key: 'C', attributes: { label: 'New' } }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(nodeInput)); + + await cmdCreateNode(mockGraphService, 'node.json'); + + expect(mockGraphService.createNode).toHaveBeenCalledWith( + { label: 'New' }, + 'C' + ); + expect(consoleSpy).toHaveBeenCalled(); + }); + + test('creates a node with empty attributes when not provided', async () => { + vi.mocked(readFileSync).mockReturnValue(JSON.stringify({})); + + await cmdCreateNode(mockGraphService, 'minimal.json'); + + expect(mockGraphService.createNode).toHaveBeenCalledWith({}, undefined); + }); + }); + + describe('cmdDeleteNode', () => { + test('deletes a node by id', async () => { + await cmdDeleteNode(mockGraphService, 'A'); + + expect(mockGraphService.deleteNode).toHaveBeenCalledWith('A'); + expect(consoleErrorSpy).toHaveBeenCalledWith('Node "A" deleted'); + }); + }); + + describe('cmdDeleteNodes', () => { + test('deletes all nodes', async () => { + await cmdDeleteNodes(mockGraphService); + + expect(mockGraphService.deleteAllNodes).toHaveBeenCalled(); + expect(consoleErrorSpy).toHaveBeenCalledWith('All nodes deleted'); + }); + }); + + // ── edge CRUD ─────────────────────────────────────────────────────── + + describe('cmdGetEdge', () => { + test('returns an edge by id', async () => { + const edge = { key: 'e1', source: 'A', target: 'B', attributes: {} }; + vi.mocked(mockGraphService.getEdge).mockResolvedValue(edge); + + await cmdGetEdge(mockGraphService, 'e1'); + + expect(mockGraphService.getEdge).toHaveBeenCalledWith('e1'); + expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(edge, null, 2)); + }); + + test('exits with error when edge not found', async () => { + vi.mocked(mockGraphService.getEdge).mockResolvedValue(undefined); + + await cmdGetEdge(mockGraphService, 'missing'); + + expect(exitSpy).toHaveBeenCalledWith(1); + }); + }); + + describe('cmdCreateEdge', () => { + test('creates an edge from JSON file', async () => { + const edgeInput = { + key: 'e2', + source: 'A', + target: 'B', + attributes: { weight: 5 }, + }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(edgeInput)); + + await cmdCreateEdge(mockGraphService, 'edge.json'); + + expect(mockGraphService.createEdge).toHaveBeenCalledWith('A', 'B', 'e2', { + weight: 5, + }); + expect(consoleSpy).toHaveBeenCalled(); + }); + + test('creates an edge with empty attributes when not provided', async () => { + const edgeInput = { key: 'e3', source: 'X', target: 'Y' }; + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(edgeInput)); + + await cmdCreateEdge(mockGraphService, 'edge-minimal.json'); + + expect(mockGraphService.createEdge).toHaveBeenCalledWith( + 'X', + 'Y', + 'e3', + {} + ); + }); + }); + + describe('cmdDeleteEdge', () => { + test('deletes an edge by id', async () => { + await cmdDeleteEdge(mockGraphService, 'e1'); + + expect(mockGraphService.deleteEdge).toHaveBeenCalledWith('e1'); + expect(consoleErrorSpy).toHaveBeenCalledWith('Edge "e1" deleted'); + }); + }); +}); diff --git a/services/graph-rewriting-service/src/cli.ts b/services/graph-rewriting-service/src/cli.ts new file mode 100644 index 0000000..16b7280 --- /dev/null +++ b/services/graph-rewriting-service/src/cli.ts @@ -0,0 +1,395 @@ +#!/usr/bin/env node + +import 'dotenv/config'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import neo4j, { Driver } from 'neo4j-driver'; +import { getNeo4jEnvConfig } from './plugins/neo4j/env'; +import { Neo4jGraphService } from './service/db/neo4j/graph.service'; +import { GraphTransformationService } from './service/grs/graph-transformation.service'; + +// ── Helpers ───────────────────────────────────────────────────────────────── + +function usage(): never { + const bin = 'graph-rewriting-service'; + console.error(`Usage: ${bin} [options] + +Graph Rewriting Service CLI — all API operations available from the command line. + +Commands (graph transformation): + transform Transform a hostgraph using rewrite rules + find Find pattern matches in a hostgraph + import Import a hostgraph into Neo4j + +Commands (node CRUD): + get-nodes List all nodes + get-node Get a single node + create-node Create a node + delete-node Delete a single node + delete-nodes Delete all nodes + +Commands (edge CRUD): + get-edge Get a single edge + create-edge Create an edge + delete-edge Delete a single edge + +Options: + -o, --output Write result to file instead of stdout + -h, --help Show this help message + +Environment variables: + NEO4J_URI Neo4j Bolt URI (default: bolt://localhost:7687) + NEO4J_USERNAME Neo4j username (default: neo4j) + NEO4J_PASSWORD Neo4j password (required) + +Examples: + ${bin} transform examples/data/grs/sierpinsky.json + ${bin} transform examples/data/grs/sierpinsky.json -o result.json + cat request.json | ${bin} transform - + ${bin} get-nodes + ${bin} get-node my-node-id + ${bin} delete-nodes`); + process.exit(1); +} + +export function readJsonInput(pathArg: string): unknown { + try { + const raw = + pathArg === '-' + ? readFileSync(0, 'utf-8') + : readFileSync(resolve(pathArg), 'utf-8'); + return JSON.parse(raw); + } catch (err) { + console.error(`Error reading input "${pathArg}":`, (err as Error).message); + process.exit(1); + } +} + +export function outputResult(data: unknown, outputPath?: string): void { + const json = JSON.stringify(data, null, 2); + if (outputPath) { + writeFileSync(resolve(outputPath), json + '\n', 'utf-8'); + } else { + console.log(json); + } +} + +function createDriver(): Driver { + const config = getNeo4jEnvConfig(); + + if (!config.NEO4J_URI || !config.NEO4J_USERNAME || !config.NEO4J_PASSWORD) { + console.error( + 'Error: NEO4J_URI, NEO4J_USERNAME and NEO4J_PASSWORD must be set.\n' + + 'Set them as environment variables or in a .env file.' + ); + process.exit(1); + } + + return neo4j.driver( + config.NEO4J_URI, + neo4j.auth.basic(config.NEO4J_USERNAME, config.NEO4J_PASSWORD) + ); +} + +// ── Argument parsing ──────────────────────────────────────────────────────── + +interface ParsedArgs { + command: string; + positional: string | undefined; + outputPath: string | undefined; +} + +export function parseArgs(argv: string[]): ParsedArgs { + const args = argv.slice(2); + + if (args.includes('-h') || args.includes('--help') || args.length === 0) { + usage(); + } + + const command = args[0]; + let positional: string | undefined; + let outputPath: string | undefined; + + for (let i = 1; i < args.length; i++) { + if (args[i] === '-o' || args[i] === '--output') { + outputPath = args[++i]; + if (!outputPath) { + console.error('Error: -o requires a file path argument'); + process.exit(1); + } + } else if (!positional) { + positional = args[i]; + } else { + console.error(`Error: unexpected argument "${args[i]}"`); + usage(); + } + } + + return { command, positional, outputPath }; +} + +// ── Command handlers ──────────────────────────────────────────────────────── +// Each one directly reuses the same service classes that the HTTP handlers use. + +export async function cmdTransform( + graphService: Neo4jGraphService, + inputPath: string, + outputPath?: string +) { + const body = readJsonInput(inputPath) as { + hostgraph: Parameters[0]; + rules?: Parameters[1]; + sequence?: Parameters[2]; + options?: Parameters[3]; + }; + + const grsService = new GraphTransformationService(graphService); + const result = await grsService.transformGraph( + body.hostgraph, + body.rules || [], + body.sequence || [], + body.options || {} + ); + outputResult(result, outputPath); +} + +export async function cmdFind( + graphService: Neo4jGraphService, + inputPath: string, + outputPath?: string +) { + const body = readJsonInput(inputPath) as { + hostgraph: Parameters[0]; + rules?: Parameters[1]; + }; + + const grsService = new GraphTransformationService(graphService); + const result = await grsService.matchPattern( + body.hostgraph, + body.rules || [] + ); + outputResult(result, outputPath); +} + +export async function cmdImport( + graphService: Neo4jGraphService, + inputPath: string, + outputPath?: string +) { + const body = readJsonInput(inputPath) as { + hostgraph: Parameters[0]; + }; + + const grsService = new GraphTransformationService(graphService); + const result = await grsService.importHostgraph(body.hostgraph); + outputResult(result, outputPath); +} + +export async function cmdGetNodes( + graphService: Neo4jGraphService, + outputPath?: string +) { + const result = await graphService.getAllNodes(); + outputResult(result, outputPath); +} + +export async function cmdGetNode( + graphService: Neo4jGraphService, + internalId: string, + outputPath?: string +) { + const result = await graphService.getNode(internalId); + if (!result) { + console.error(`Node "${internalId}" not found`); + process.exit(1); + } + outputResult(result, outputPath); +} + +export async function cmdCreateNode( + graphService: Neo4jGraphService, + inputPath: string, + outputPath?: string +) { + const body = readJsonInput(inputPath) as { + key?: string; + attributes?: Record; + }; + const result = await graphService.createNode(body.attributes || {}, body.key); + outputResult(result, outputPath); +} + +export async function cmdDeleteNode( + graphService: Neo4jGraphService, + internalId: string +) { + await graphService.deleteNode(internalId); + console.error(`Node "${internalId}" deleted`); +} + +export async function cmdDeleteNodes(graphService: Neo4jGraphService) { + await graphService.deleteAllNodes(); + console.error('All nodes deleted'); +} + +export async function cmdGetEdge( + graphService: Neo4jGraphService, + internalId: string, + outputPath?: string +) { + const result = await graphService.getEdge(internalId); + if (!result) { + console.error(`Edge "${internalId}" not found`); + process.exit(1); + } + outputResult(result, outputPath); +} + +export async function cmdCreateEdge( + graphService: Neo4jGraphService, + inputPath: string, + outputPath?: string +) { + const body = readJsonInput(inputPath) as { + key: string; + source: string; + target: string; + attributes?: Record; + }; + const result = await graphService.createEdge( + body.source, + body.target, + body.key, + body.attributes || {} + ); + outputResult(result, outputPath); +} + +export async function cmdDeleteEdge( + graphService: Neo4jGraphService, + internalId: string +) { + await graphService.deleteEdge(internalId); + console.error(`Edge "${internalId}" deleted`); +} + +// ── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + const { command, positional, outputPath } = parseArgs(process.argv); + + const driver = createDriver(); + const graphService = new Neo4jGraphService(() => driver.session()); + + try { + switch (command) { + // Graph transformation commands + case 'transform': { + if (!positional) { + console.error( + 'Error: transform requires an input file path (or - for stdin)' + ); + process.exit(1); + } + await cmdTransform(graphService, positional, outputPath); + break; + } + case 'find': { + if (!positional) { + console.error( + 'Error: find requires an input file path (or - for stdin)' + ); + process.exit(1); + } + await cmdFind(graphService, positional, outputPath); + break; + } + case 'import': { + if (!positional) { + console.error( + 'Error: import requires an input file path (or - for stdin)' + ); + process.exit(1); + } + await cmdImport(graphService, positional, outputPath); + break; + } + + // Node CRUD commands + case 'get-nodes': + await cmdGetNodes(graphService, outputPath); + break; + case 'get-node': { + if (!positional) { + console.error('Error: get-node requires a node internalId'); + process.exit(1); + } + await cmdGetNode(graphService, positional, outputPath); + break; + } + case 'create-node': { + if (!positional) { + console.error( + 'Error: create-node requires an input file path (or - for stdin)' + ); + process.exit(1); + } + await cmdCreateNode(graphService, positional, outputPath); + break; + } + case 'delete-node': { + if (!positional) { + console.error('Error: delete-node requires a node internalId'); + process.exit(1); + } + await cmdDeleteNode(graphService, positional); + break; + } + case 'delete-nodes': + await cmdDeleteNodes(graphService); + break; + + // Edge CRUD commands + case 'get-edge': { + if (!positional) { + console.error('Error: get-edge requires an edge internalId'); + process.exit(1); + } + await cmdGetEdge(graphService, positional, outputPath); + break; + } + case 'create-edge': { + if (!positional) { + console.error( + 'Error: create-edge requires an input file path (or - for stdin)' + ); + process.exit(1); + } + await cmdCreateEdge(graphService, positional, outputPath); + break; + } + case 'delete-edge': { + if (!positional) { + console.error('Error: delete-edge requires an edge internalId'); + process.exit(1); + } + await cmdDeleteEdge(graphService, positional); + break; + } + + default: + console.error(`Unknown command: ${command}`); + usage(); + } + } catch (err) { + console.error('Error:', (err as Error).message); + process.exit(1); + } finally { + await driver.close(); + } +} + +// Only run main() when this file is the entry point, not when imported for testing. +if (require.main === module) { + main(); +} diff --git a/services/graph-rewriting-service/src/config/env.ts b/services/graph-rewriting-service/src/config/env.ts index da96466..7b5e1a7 100644 --- a/services/graph-rewriting-service/src/config/env.ts +++ b/services/graph-rewriting-service/src/config/env.ts @@ -1,13 +1,16 @@ export type EnvVarAppEnvironment = 'production' | 'development' | 'test'; +export type EnvVarDbBackend = 'neo4j' | 'memory'; export interface AppEnvConfig { APP_ENV: EnvVarAppEnvironment; + DB_BACKEND: EnvVarDbBackend; } export function getAppEnvConfig(): AppEnvConfig { - const { APP_ENV } = process.env as Record; + const { APP_ENV, DB_BACKEND } = process.env as Record; return { - APP_ENV: APP_ENV ?? 'production', + APP_ENV: (APP_ENV as EnvVarAppEnvironment) ?? 'production', + DB_BACKEND: (DB_BACKEND as EnvVarDbBackend) ?? 'memory', }; } diff --git a/services/graph-rewriting-service/src/plugins/memory/index.ts b/services/graph-rewriting-service/src/plugins/memory/index.ts new file mode 100644 index 0000000..9bf4160 --- /dev/null +++ b/services/graph-rewriting-service/src/plugins/memory/index.ts @@ -0,0 +1,59 @@ +import fastifyPlugin from 'fastify-plugin'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; + +import healthRoutes from './routes/health'; +import nodeRoutes from '../neo4j/routes/nodes'; +import edgeRoutes from '../neo4j/routes/edges'; + +import GraphNodeSchema from '../../schemas/node.schema.json'; +import GraphEdgeSchema from '../../schemas/edge.schema.json'; +import GraphSchema from '../../schemas/graph.schema.json'; +import GraphInstantiatedAttribute from '../../schemas/instantiated-attribute.schema.json'; + +import { InMemoryGraphService } from '../../service/db/memory/graph.service'; +import { IGraphDB } from '../../service/db/types'; + +declare module 'fastify' { + interface FastifyRequest { + dbGraphService: IGraphDB | null; + } +} + +const memoryConnector: FastifyPluginAsync = async ( + fastify: FastifyInstance +) => { + fastify.log.info( + 'Fastify InMemory Plugin: Setting up in-memory graph database' + ); + + const graphService = new InMemoryGraphService(); + + fastify.decorateRequest( + 'dbGraphService', + null + ); + + fastify.addHook('onRequest', (request, _reply, done) => { + // All requests share the same in-memory graph instance + request.dbGraphService = graphService; + done(); + }); + + fastify.log.debug('Fastify InMemory Plugin: Adding relevant schemas'); + fastify.addSchema(GraphNodeSchema); + fastify.addSchema(GraphEdgeSchema); + fastify.addSchema(GraphSchema); + fastify.addSchema(GraphInstantiatedAttribute); + + fastify.log.debug('Fastify InMemory Plugin: Adding routes'); + fastify.register(healthRoutes, { + prefix: '/memory', + }); + + fastify.register(nodeRoutes); + fastify.register(edgeRoutes); +}; + +export default fastifyPlugin(memoryConnector, { + name: 'fastify-memory-connector', +}); diff --git a/services/graph-rewriting-service/src/plugins/memory/routes/health.ts b/services/graph-rewriting-service/src/plugins/memory/routes/health.ts new file mode 100644 index 0000000..de74b28 --- /dev/null +++ b/services/graph-rewriting-service/src/plugins/memory/routes/health.ts @@ -0,0 +1,17 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { okReply } from '../../../utils/response'; + +const healthcheck = async ( + request: FastifyRequest, + reply: FastifyReply +): Promise => { + if (request.dbGraphService) { + return okReply(reply, { backend: 'memory' }); + } + + throw Error('In-memory graph service not found'); +}; + +export default async function routes(fastify: FastifyInstance) { + fastify.get('/health', healthcheck); +} diff --git a/services/graph-rewriting-service/src/plugins/neo4j/index.ts b/services/graph-rewriting-service/src/plugins/neo4j/index.ts index 48ea6da..af8c289 100644 --- a/services/graph-rewriting-service/src/plugins/neo4j/index.ts +++ b/services/graph-rewriting-service/src/plugins/neo4j/index.ts @@ -13,6 +13,7 @@ import GraphSchema from '../../schemas/graph.schema.json'; import GraphInstantiatedAttribute from '../../schemas/instantiated-attribute.schema.json'; import { Neo4jGraphService } from '../../service/db/neo4j/graph.service'; +import { IGraphDB } from '../../service/db/types'; declare module 'fastify' { interface FastifyInstance { @@ -20,7 +21,7 @@ declare module 'fastify' { } interface FastifyRequest { - dbGraphService: Neo4jGraphService | null; + dbGraphService: IGraphDB | null; } } @@ -62,7 +63,7 @@ const neo4jConnector: FastifyPluginAsync = async (fastify: FastifyInstance) => { if (driver) { if (!fastify?.neo4j) { fastify.decorate('neo4j', driver); - fastify.decorateRequest( + fastify.decorateRequest( 'dbGraphService', null ); diff --git a/services/graph-rewriting-service/src/service/db/memory/graph.service.test.ts b/services/graph-rewriting-service/src/service/db/memory/graph.service.test.ts new file mode 100644 index 0000000..03671da --- /dev/null +++ b/services/graph-rewriting-service/src/service/db/memory/graph.service.test.ts @@ -0,0 +1,650 @@ +import { expect, test, describe, beforeEach } from 'vitest'; + +import { InMemoryGraphService } from './graph.service'; +import { DBGraphNACs } from '../types'; +import { PatternNodeSchema } from '../../../types/patternnode.schema'; + +let graphService: InMemoryGraphService; + +describe('InMemoryGraphService', () => { + beforeEach(() => { + graphService = new InMemoryGraphService(); + }); + + describe('Node CRUD', () => { + test('createNode', async () => { + const result = await graphService.createNode( + { hello: 'world' }, + 'testnode1' + ); + expect(result).toEqual({ + key: 'testnode1', + attributes: { hello: 'world' }, + }); + }); + + test('createNode with type attribute', async () => { + const result = await graphService.createNode( + { type: 'Gate', test: 'testMe' }, + 'testnode4' + ); + expect(result).toEqual({ + key: 'testnode4', + attributes: { type: 'Gate', test: 'testMe' }, + }); + }); + + test('createNode strips _grs_internalId from attributes', async () => { + const result = await graphService.createNode( + { hello: 'world', _grs_internalId: 'testnode1' }, + 'testnode1' + ); + expect(result.attributes).not.toHaveProperty('_grs_internalId'); + expect(result).toEqual({ + key: 'testnode1', + attributes: { hello: 'world' }, + }); + }); + + test('getNode', async () => { + await graphService.createNode({ label: 'Test' }, 'testnode1'); + const result = await graphService.getNode('testnode1'); + expect(result).toEqual({ + key: 'testnode1', + attributes: { label: 'Test' }, + }); + }); + + test('getNode returns undefined for missing node', async () => { + const result = await graphService.getNode('nonexistent'); + expect(result).toBeUndefined(); + }); + + test('updateNode with default (modify) mode', async () => { + await graphService.createNode({ label: 'Test', foo: 'bar' }, 'testnode1'); + const result = await graphService.updateNode( + { hello: 'world' }, + 'testnode1' + ); + expect(result).toEqual({ + key: 'testnode1', + attributes: { label: 'Test', foo: 'bar', hello: 'world' }, + }); + }); + + test('updateNode with replace mode', async () => { + await graphService.createNode({ label: 'Test', foo: 'bar' }, 'testnode1'); + const result = await graphService.updateNode( + { hello: 'world' }, + 'testnode1', + [], + { attributeReplacementMode: 'replace' } + ); + expect(result).toEqual({ + key: 'testnode1', + attributes: { hello: 'world' }, + }); + }); + + test('updateNode with delete mode', async () => { + await graphService.createNode({ label: 'Test', foo: 'bar' }, 'testnode1'); + const result = await graphService.updateNode( + { hello: 'world' }, + 'testnode1', + [], + { attributeReplacementMode: 'delete' } + ); + expect(result).toEqual({ + key: 'testnode1', + attributes: {}, + }); + }); + + test('getAllNodes', async () => { + await graphService.createNode({ label: 'A' }, 'testnodeA'); + await graphService.createNode({ label: 'B' }, 'testnodeB'); + await graphService.createNode({ label: 'C' }, 'testnodeC'); + const result = await graphService.getAllNodes(); + expect(result).toEqual( + expect.arrayContaining([ + { key: 'testnodeA', attributes: { label: 'A' } }, + { key: 'testnodeB', attributes: { label: 'B' } }, + { key: 'testnodeC', attributes: { label: 'C' } }, + ]) + ); + }); + + test('deleteNode', async () => { + await graphService.createNode({ label: 'A' }, 'testnodeA'); + await graphService.createNode({ label: 'B' }, 'testnodeB'); + await graphService.deleteNode('testnodeA'); + const all = await graphService.getAllNodes(); + expect(all).toHaveLength(1); + expect(all[0].key).toBe('testnodeB'); + }); + + test('deleteNode detaches edges', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createEdge('a', 'b', 'e1', { type: 'rel' }); + await graphService.deleteNode('a'); + const edges = await graphService.getAllEdges(); + expect(edges).toHaveLength(0); + }); + + test('deleteNodes', async () => { + await graphService.createNode({ label: 'A' }, 'testnodeA'); + await graphService.createNode({ label: 'B' }, 'testnodeB'); + await graphService.createNode({ label: 'C' }, 'testnodeC'); + await graphService.deleteNodes(['testnodeA', 'testnodeB']); + const all = await graphService.getAllNodes(); + expect(all).toHaveLength(1); + expect(all[0].key).toBe('testnodeC'); + }); + + test('deleteAllNodes', async () => { + await graphService.createNode({ label: 'A' }, 'testnodeA'); + await graphService.createNode({ label: 'B' }, 'testnodeB'); + const result = await graphService.deleteAllNodes(); + expect(result).toHaveLength(2); + const all = await graphService.getAllNodes(); + expect(all).toHaveLength(0); + }); + }); + + describe('Edge CRUD', () => { + test('createEdge', async () => { + await graphService.createNode({}, 'testnodeA'); + await graphService.createNode({}, 'testnodeB'); + const result = await graphService.createEdge( + 'testnodeA', + 'testnodeB', + 'testrelation', + { type: 'relation', hello: 'world' } + ); + expect(result).toEqual({ + key: 'testrelation', + source: 'testnodeA', + target: 'testnodeB', + attributes: { type: 'relation', hello: 'world' }, + }); + }); + + test('getEdge', async () => { + await graphService.createNode({}, 'testnodeA'); + await graphService.createNode({}, 'testnodeB'); + await graphService.createEdge('testnodeA', 'testnodeB', 'testedge', { + hello: 'world', + }); + const result = await graphService.getEdge('testedge'); + expect(result).toEqual({ + key: 'testedge', + source: 'testnodeA', + target: 'testnodeB', + attributes: { hello: 'world' }, + }); + }); + + test('getEdge returns undefined for missing edge', async () => { + const result = await graphService.getEdge('nonexistent'); + expect(result).toBeUndefined(); + }); + + test('deleteEdge', async () => { + await graphService.createNode({}, 'testnodeA'); + await graphService.createNode({}, 'testnodeB'); + await graphService.createEdge('testnodeA', 'testnodeB', 'testedge', {}); + await graphService.deleteEdge('testedge'); + const result = await graphService.getEdge('testedge'); + expect(result).toBeUndefined(); + }); + + test('deleteEdge returns undefined for missing edge', async () => { + const result = await graphService.deleteEdge('nonexistent'); + expect(result).toBeUndefined(); + }); + + test('deleteEdges', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createNode({}, 'c'); + await graphService.createEdge('a', 'b', 'e1', {}); + await graphService.createEdge('a', 'c', 'e2', {}); + await graphService.createEdge('b', 'c', 'e3', {}); + await graphService.deleteEdges(['e1', 'e2']); + const edges = await graphService.getAllEdges(); + expect(edges).toHaveLength(1); + expect(edges[0].key).toBe('e3'); + }); + + test('getAllEdges', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createEdge('a', 'b', 'e1', { type: 'x' }); + const edges = await graphService.getAllEdges(); + expect(edges).toHaveLength(1); + expect(edges[0]).toEqual({ + key: 'e1', + source: 'a', + target: 'b', + attributes: { type: 'x' }, + }); + }); + + test('updateEdge with modify mode', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createEdge('a', 'b', 'e1', { + type: 'x', + old: 'value', + }); + const result = await graphService.updateEdge('a', 'b', 'e1', { + type: 'y', + new: 'attr', + }); + expect(result.attributes).toEqual({ + type: 'y', + old: 'value', + new: 'attr', + }); + }); + + test('updateEdge with replace mode', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createEdge('a', 'b', 'e1', { + type: 'x', + old: 'value', + }); + const result = await graphService.updateEdge( + 'a', + 'b', + 'e1', + { type: 'y' }, + { attributeReplacementMode: 'replace' } + ); + expect(result.attributes).toEqual({ type: 'y' }); + }); + + test('updateEdge with delete mode', async () => { + await graphService.createNode({}, 'a'); + await graphService.createNode({}, 'b'); + await graphService.createEdge('a', 'b', 'e1', { + type: 'x', + old: 'value', + }); + const result = await graphService.updateEdge( + 'a', + 'b', + 'e1', + { type: 'y' }, + { attributeReplacementMode: 'delete' } + ); + expect(result.attributes).toEqual({}); + }); + }); + + describe('Pattern Matching', () => { + test('empty pattern returns single empty match', async () => { + const result = await graphService.findPatternMatch([], []); + expect(result).toEqual([{ nodes: {}, edges: {} }]); + }); + + test('single node pattern', async () => { + await graphService.createNode({}, 'testnodeA'); + await graphService.createNode({}, 'testnodeB'); + await graphService.createNode({}, 'testnodeC'); + + const patternNodes: PatternNodeSchema[] = [{ key: 'A', attributes: {} }]; + + const result = await graphService.findPatternMatch(patternNodes, []); + expect(result).toHaveLength(3); + expect(result).toContainEqual({ + edges: {}, + nodes: { A: { key: 'testnodeA', attributes: {} } }, + }); + expect(result).toContainEqual({ + edges: {}, + nodes: { A: { key: 'testnodeB', attributes: {} } }, + }); + expect(result).toContainEqual({ + edges: {}, + nodes: { A: { key: 'testnodeC', attributes: {} } }, + }); + }); + + test('single node pattern with attributes', async () => { + await graphService.createNode({ hello: 'world' }, 'testnodeA'); + await graphService.createNode({ test: 'test2' }, 'testnodeB'); + await graphService.createNode({}, 'testnodeC'); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'A', attributes: { test: 'test2' } }, + ]; + + const result = await graphService.findPatternMatch(patternNodes, []); + expect(result).toHaveLength(1); + expect(result).toContainEqual({ + edges: {}, + nodes: { + A: { key: 'testnodeB', attributes: { test: 'test2' } }, + }, + }); + }); + + test('single edge pattern (undirected)', async () => { + await graphService.createNode({ hello: 'world' }, 'testnodeA'); + await graphService.createNode({ test: 'test2' }, 'testnodeB'); + await graphService.createNode({}, 'testnodeC'); + await graphService.createEdge('testnodeA', 'testnodeB', 'testedge1', {}); + await graphService.createEdge('testnodeA', 'testnodeC', 'testedge2', {}); + + const patternEdges = [ + { key: 'edge', source: 'A', target: 'B', attributes: {} }, + ]; + + // For edge-only pattern, nodes aren't in the pattern but are referenced via edges + // The edge-only pattern requires corresponding pattern nodes to be matched + // Since no pattern nodes, it won't be able to resolve source/target + // Actually, looking at Neo4j impl, edge-only matches don't require explicit nodes + const result = await graphService.findPatternMatch([], patternEdges); + // Without explicit pattern nodes bound, edge source/target refs can't be resolved + // This matches the Neo4j behavior where edge variables like (A)-[edge]-(B) auto-bind + expect(result).toEqual([]); + }); + + test('two connected nodes (directed)', async () => { + await graphService.createNode({ hello: 'world' }, 'testnodeA'); + await graphService.createNode({ test: 'test2' }, 'testnodeB'); + await graphService.createNode({}, 'testnodeC'); + await graphService.createEdge('testnodeA', 'testnodeB', 'testedge1', {}); + await graphService.createEdge('testnodeA', 'testnodeC', 'testedge2', {}); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'A', attributes: {} }, + { key: 'B', attributes: {} }, + ]; + + const patternEdges = [ + { key: 'edge', source: 'A', target: 'B', attributes: {} }, + ]; + + const result = await graphService.findPatternMatch( + patternNodes, + patternEdges, + 'directed' + ); + + expect(result).toHaveLength(2); + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'testedge1', + source: 'testnodeA', + target: 'testnodeB', + }, + }, + nodes: { + A: { key: 'testnodeA', attributes: { hello: 'world' } }, + B: { key: 'testnodeB', attributes: { test: 'test2' } }, + }, + }); + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'testedge2', + source: 'testnodeA', + target: 'testnodeC', + }, + }, + nodes: { + A: { key: 'testnodeA', attributes: { hello: 'world' } }, + B: { key: 'testnodeC', attributes: {} }, + }, + }); + }); + + test('isomorphic matching (homo=false) prevents same host node', async () => { + await graphService.createNode({}, 'n1'); + await graphService.createNode({}, 'n2'); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'A', attributes: {} }, + { key: 'B', attributes: {} }, + ]; + + // Isomorphic: A and B must be different host nodes + const result = await graphService.findPatternMatch( + patternNodes, + [], + 'undirected', + false + ); + // 2 nodes, 2 pattern nodes → 2 permutations (n1→A,n2→B) and (n2→A,n1→B) + expect(result).toHaveLength(2); + // No match should have A and B pointing to same node + for (const match of result) { + expect(match.nodes['A'].key).not.toBe(match.nodes['B'].key); + } + }); + + test('homomorphic matching allows same host node', async () => { + await graphService.createNode({}, 'n1'); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'A', attributes: {} }, + { key: 'B', attributes: {} }, + ]; + + // Homomorphic: A and B CAN be same host node + const result = await graphService.findPatternMatch( + patternNodes, + [], + 'undirected', + true + ); + // 1 node, 2 pattern nodes → 1 match (both → n1) + expect(result).toHaveLength(1); + expect(result[0].nodes['A'].key).toBe('n1'); + expect(result[0].nodes['B'].key).toBe('n1'); + }); + + test('NAC: excluding attribute', async () => { + await graphService.createNode({ hello: 'world' }, 'testnodeA'); + await graphService.createNode({ test: 'wert' }, 'testnodeB'); + await graphService.createNode({ hello: 'world' }, 'testnodeC'); + await graphService.createNode({ attribute: 'value' }, 'testnodeD'); + await graphService.createNode({}, 'testnodeE'); + await graphService.createNode({}, 'testnodeF'); + + await graphService.createEdge('testnodeA', 'testnodeB', 'aToB', {}); + await graphService.createEdge('testnodeA', 'testnodeC', 'aToC', {}); + await graphService.createEdge('testnodeC', 'testnodeD', 'cToD', {}); + await graphService.createEdge('testnodeD', 'testnodeE', 'dToE', {}); + await graphService.createEdge('testnodeE', 'testnodeC', 'eToC', {}); + await graphService.createEdge('testnodeA', 'testnodeF', 'aToF', {}); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'A', attributes: {} }, + { key: 'B', attributes: {} }, + ]; + + const patternEdges = [ + { key: 'edge', source: 'A', target: 'B', attributes: {} }, + ]; + + const nacs: DBGraphNACs[] = [ + { + nodes: [{ key: 'A', attributes: { hello: 'world' } }], + edges: [], + }, + ]; + + const result = await graphService.findPatternMatch( + patternNodes, + patternEdges, + 'directed', + false, + nacs + ); + + // Nodes with hello='world' (A, C) should be excluded from pattern-node A + // So only D→E and E→C should match + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'dToE', + source: 'testnodeD', + target: 'testnodeE', + }, + }, + nodes: { + A: { key: 'testnodeD', attributes: { attribute: 'value' } }, + B: { key: 'testnodeE', attributes: {} }, + }, + }); + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'eToC', + source: 'testnodeE', + target: 'testnodeC', + }, + }, + nodes: { + A: { key: 'testnodeE', attributes: {} }, + B: { key: 'testnodeC', attributes: { hello: 'world' } }, + }, + }); + }); + + test('NAC: exclude node with connection to NAC-Node', async () => { + await graphService.createNode({ name: 'A' }, 'testnodeA'); + await graphService.createNode({ name: 'B' }, 'testnodeB'); + await graphService.createNode({ name: 'C' }, 'testnodeC'); + await graphService.createNode({ name: 'D' }, 'testnodeD'); + + await graphService.createEdge('testnodeA', 'testnodeB', 'aToB', {}); + await graphService.createEdge('testnodeA', 'testnodeC', 'aToC', {}); + await graphService.createEdge('testnodeA', 'testnodeD', 'aToD', {}); + await graphService.createEdge('testnodeC', 'testnodeB', 'cToB', {}); + + const patternNodes: PatternNodeSchema[] = [ + { key: 'node1', attributes: {} }, + { key: 'node2', attributes: {} }, + ]; + + const patternEdges = [ + { key: 'edge', source: 'node1', target: 'node2', attributes: {} }, + ]; + + const nacs: DBGraphNACs[] = [ + { + options: { type: 'directed' }, + nodes: [ + { key: 'node2' }, + { key: 'node3', attributes: { name: 'B' } }, + ], + edges: [ + { + key: 'edge2', + source: 'node2', + target: 'node3', + attributes: {}, + }, + ], + }, + ]; + + const result = await graphService.findPatternMatch( + patternNodes, + patternEdges, + 'directed', + false, + nacs + ); + + // A->C should be excluded because C has connection to B (NAC node3) + // Valid matches: A->B, A->D, C->B + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'aToB', + source: 'testnodeA', + target: 'testnodeB', + }, + }, + nodes: { + node1: { key: 'testnodeA', attributes: { name: 'A' } }, + node2: { key: 'testnodeB', attributes: { name: 'B' } }, + }, + }); + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'aToD', + source: 'testnodeA', + target: 'testnodeD', + }, + }, + nodes: { + node1: { key: 'testnodeA', attributes: { name: 'A' } }, + node2: { key: 'testnodeD', attributes: { name: 'D' } }, + }, + }); + expect(result).toContainEqual({ + edges: { + edge: { + attributes: {}, + key: 'cToB', + source: 'testnodeC', + target: 'testnodeB', + }, + }, + nodes: { + node1: { key: 'testnodeC', attributes: { name: 'C' } }, + node2: { key: 'testnodeB', attributes: { name: 'B' } }, + }, + }); + + // A->C should NOT be in result + expect(result).not.toContainEqual( + expect.objectContaining({ + edges: expect.objectContaining({ + edge: expect.objectContaining({ key: 'aToC' }), + }), + }) + ); + }); + + test('pattern matching with array attribute (IN semantics)', async () => { + await graphService.createNode({ type: 'Gate' }, 'n1'); + await graphService.createNode({ type: 'Place' }, 'n2'); + await graphService.createNode({ type: 'Transition' }, 'n3'); + + const patternNodes: PatternNodeSchema[] = [ + { + key: 'A', + attributes: { type: ['Gate', 'Place'] }, + }, + ]; + + const result = await graphService.findPatternMatch(patternNodes, []); + expect(result).toHaveLength(2); + expect(result).toContainEqual({ + edges: {}, + nodes: { A: { key: 'n1', attributes: { type: 'Gate' } } }, + }); + expect(result).toContainEqual({ + edges: {}, + nodes: { A: { key: 'n2', attributes: { type: 'Place' } } }, + }); + }); + }); +}); diff --git a/services/graph-rewriting-service/src/service/db/memory/graph.service.ts b/services/graph-rewriting-service/src/service/db/memory/graph.service.ts new file mode 100644 index 0000000..d507b34 --- /dev/null +++ b/services/graph-rewriting-service/src/service/db/memory/graph.service.ts @@ -0,0 +1,489 @@ +import { GrafeoDB } from '@grafeo-db/js'; +import { + DBGraphEdgeInternalId, + DBGraphNodeInternalId, + DBGraphNodeMetadata, + DBGraphNodeResult, + IGraphDB, + DBGraphEdgeResult, + DBGraphEdgeMetadata, + DBGraphType, + DBGraphPatternMatchResult, + DBGraphNACs, + EdgeUpdateRewriteOptions, + NodeUpdateRewriteOptions, + DBGraphEdge, +} from '../types'; +import { PatternNodeSchema } from '../../../types/patternnode.schema'; +import { + computeEdgeQuery, + computeInjectivityClause, + computeNodeQuery, +} from '../neo4j/cypher/rewrite'; +import { + DEFAULT_NODE_LABEL, + DEFAULT_RELATIONSHIP_LABEL, +} from '../neo4j/constants'; +import { sanitizeIdentifier } from '../neo4j/cypher/utils'; +import { createNodeUuid, createParameterUuid } from '../../../utils/uuid'; + +export class InMemoryGraphService implements IGraphDB { + private readonly db = GrafeoDB.create(); + /** Supplemental edge index — grafeo doesn't support WHERE on relationship properties */ + private readonly edgeStore = new Map(); + + public graphType: DBGraphType = 'undirected'; + + // ---------- helpers ---------- + + private propsToNodeResult(props: Record): DBGraphNodeResult { + const key = props._grs_internalId as string; + const attributes = { ...props }; + delete attributes._grs_internalId; + return { key, attributes }; + } + + private propsToEdgeResult(props: Record): DBGraphEdgeResult { + const key = props._grs_internalId as string; + const source = props._grs_source as string; + const target = props._grs_target as string; + const attributes = { ...props }; + delete attributes._grs_internalId; + delete attributes._grs_source; + delete attributes._grs_target; + return { key, source, target, attributes }; + } + + // ---------- Node CRUD ---------- + + public async createNode( + metadata: DBGraphNodeMetadata, + internalId?: DBGraphNodeInternalId + ): Promise { + const key = internalId ?? createNodeUuid(); + const attrs = { ...metadata }; + delete attrs._grs_internalId; + const storedAttrs = { ...attrs, _grs_internalId: key }; + + await this.db.executeCypher( + `CREATE (n:\`${DEFAULT_NODE_LABEL}\`) SET n = $nodeMetadata`, + { nodeMetadata: storedAttrs } + ); + + return { key, attributes: attrs }; + } + + public async updateNode( + metadata: DBGraphNodeMetadata, + internalId: DBGraphNodeInternalId, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _oldTypes: string[] = [], + options: NodeUpdateRewriteOptions = {} + ): Promise { + if (!internalId) { + throw new Error( + 'InMemoryGraphService: no internalId given in updateNode clause' + ); + } + + const attrs = { ...metadata }; + delete attrs._grs_internalId; + const mode = options?.attributeReplacementMode; + + if (mode === 'delete') { + await this.db.executeCypher( + `MATCH (n) WHERE n._grs_internalId = $internalId SET n = $metadata`, + { internalId, metadata: { _grs_internalId: internalId } } + ); + return { key: internalId, attributes: {} }; + } else if (mode === 'replace') { + await this.db.executeCypher( + `MATCH (n) WHERE n._grs_internalId = $internalId SET n = $metadata`, + { internalId, metadata: { ...attrs, _grs_internalId: internalId } } + ); + return { key: internalId, attributes: attrs }; + } else { + await this.db.executeCypher( + `MATCH (n) WHERE n._grs_internalId = $internalId SET n += $metadata`, + { internalId, metadata: { ...attrs, _grs_internalId: internalId } } + ); + return (await this.getNode(internalId))!; + } + } + + public async getNode( + internalId: DBGraphNodeInternalId + ): Promise { + const result = await this.db.executeCypher( + `MATCH (n) WHERE n._grs_internalId = $internalId RETURN properties(n) AS props`, + { internalId } + ); + const rows = result.toArray(); + if (!rows.length) return undefined; + return this.propsToNodeResult(rows[0].props as Record); + } + + public async deleteNode( + internalId: DBGraphNodeInternalId + ): Promise { + const node = await this.getNode(internalId); + // Remove edges connected to this node from the supplemental store + for (const [id, edge] of this.edgeStore) { + if (edge.source === internalId || edge.target === internalId) { + this.edgeStore.delete(id); + } + } + await this.db.executeCypher( + `MATCH (n) WHERE n._grs_internalId = $internalId DETACH DELETE n`, + { internalId } + ); + return node; + } + + public async deleteNodes( + internalIds: DBGraphNodeInternalId[] + ): Promise { + const results: DBGraphNodeResult[] = []; + for (const id of internalIds) { + const r = await this.deleteNode(id); + if (r) results.push(r); + } + return results.length ? results : undefined; + } + + public async getAllNodes(): Promise { + const result = await this.db.executeCypher( + `MATCH (n:\`${DEFAULT_NODE_LABEL}\`) RETURN properties(n) AS props` + ); + return result + .toArray() + .map((row) => + this.propsToNodeResult(row.props as Record) + ); + } + + public async deleteAllNodes(): Promise { + const all = await this.getAllNodes(); + this.edgeStore.clear(); + await this.db.executeCypher(`MATCH (n) DETACH DELETE n`); + return all; + } + + // ---------- Edge CRUD ---------- + + public async createEdge( + internalIdSource: DBGraphNodeInternalId, + internalIdTarget: DBGraphNodeInternalId, + internalId: DBGraphEdgeInternalId, + metadata: DBGraphEdgeMetadata + ): Promise { + const storedAttrs = { + ...metadata, + _grs_internalId: internalId, + _grs_source: internalIdSource, + _grs_target: internalIdTarget, + }; + + const result = await this.db.executeCypher( + `MATCH (a:\`${DEFAULT_NODE_LABEL}\`), (b:\`${DEFAULT_NODE_LABEL}\`) WHERE a._grs_internalId = $src AND b._grs_internalId = $tgt CREATE (a)-[r:\`${DEFAULT_RELATIONSHIP_LABEL}\`]->(b) SET r = $attrs RETURN r`, + { src: internalIdSource, tgt: internalIdTarget, attrs: storedAttrs } + ); + + if (!result.toArray().length) { + throw new Error( + `InMemoryGraphService: createEdge failed — one or both endpoints do not exist (src=${internalIdSource}, tgt=${internalIdTarget})` + ); + } + + const edge: DBGraphEdgeResult = { + key: internalId, + source: internalIdSource, + target: internalIdTarget, + attributes: { ...metadata }, + }; + this.edgeStore.set(internalId, edge); + return edge; + } + + public async updateEdge( + internalIdSource: DBGraphNodeInternalId, + internalIdTarget: DBGraphNodeInternalId, + internalId: DBGraphEdgeInternalId, + metadata: DBGraphEdgeMetadata, + options: EdgeUpdateRewriteOptions = {} + ): Promise { + if (!internalId) { + throw new Error( + 'InMemoryGraphService: no internalId given in updateEdge clause' + ); + } + + const oldEdge = this.edgeStore.get(internalId); + await this.deleteEdge(internalId); + + let attrs: DBGraphEdgeMetadata = {}; + const mode = options?.attributeReplacementMode; + + if (mode === 'delete') { + attrs = {}; + } else if (mode === 'replace') { + attrs = { ...metadata }; + } else { + attrs = oldEdge + ? { ...oldEdge.attributes, ...metadata } + : { ...metadata }; + } + + return this.createEdge( + internalIdSource, + internalIdTarget, + internalId, + attrs + ); + } + + public async getEdge( + internalId: DBGraphEdgeInternalId + ): Promise { + return this.edgeStore.get(internalId); + } + + public async deleteEdge( + internalId: DBGraphEdgeInternalId + ): Promise { + const edge = this.edgeStore.get(internalId); + if (!edge) return undefined; + this.edgeStore.delete(internalId); + + // Find other edges that share the same src/tgt (multigraph survivors) + const survivors = [...this.edgeStore.values()].filter( + (e) => e.source === edge.source && e.target === edge.target + ); + + // Delete ALL relationships between src/tgt (grafeo can't filter by rel property) + await this.db.executeCypher( + `MATCH (a)-[r:\`${DEFAULT_RELATIONSHIP_LABEL}\`]->(b) WHERE a._grs_internalId = $src AND b._grs_internalId = $tgt DELETE r`, + { src: edge.source, tgt: edge.target } + ); + + // Recreate the surviving parallel edges + for (const survivor of survivors) { + await this.db.executeCypher( + `MATCH (a:\`${DEFAULT_NODE_LABEL}\`), (b:\`${DEFAULT_NODE_LABEL}\`) WHERE a._grs_internalId = $src AND b._grs_internalId = $tgt CREATE (a)-[r:\`${DEFAULT_RELATIONSHIP_LABEL}\`]->(b) SET r = $attrs`, + { + src: survivor.source, + tgt: survivor.target, + attrs: { + ...survivor.attributes, + _grs_internalId: survivor.key, + _grs_source: survivor.source, + _grs_target: survivor.target, + }, + } + ); + } + + return edge; + } + + public async deleteEdges( + internalIds: DBGraphEdgeInternalId[] + ): Promise { + const results: DBGraphEdgeResult[] = []; + for (const id of internalIds) { + const r = await this.deleteEdge(id); + if (r) results.push(r); + } + return results; + } + + public async getAllEdges(): Promise { + return [...this.edgeStore.values()]; + } + + // ---------- Pattern matching ---------- + + public async findPatternMatch( + nodes: PatternNodeSchema[], + edges: DBGraphEdge[], + type: DBGraphType = 'undirected', + homo = true, + nacs: DBGraphNACs[] = [] + ): Promise { + if (!nodes.length && !edges.length) { + return [{ nodes: {}, edges: {} }]; + } + + // Edges referencing nodes not in the pattern cannot be resolved + const nodeKeySet = new Set(nodes.map((n) => n.key)); + for (const edge of edges) { + if (!nodeKeySet.has(edge.source) || !nodeKeySet.has(edge.target)) { + return []; + } + } + + let query = ''; + let hasWhere = false; + let parameters: Record = {}; + const whereClauses: string[] = []; + + const nodeVars = nodes.map((n) => sanitizeIdentifier(n.key)); + const edgeVars = edges.map((e) => sanitizeIdentifier(e.key)); + + const nodeQueries: string[] = []; + for (const node of nodes) { + const { cypher, where, params } = computeNodeQuery( + node.key, + [DEFAULT_NODE_LABEL], + node.attributes ?? {} + ); + if (where) whereClauses.push(where); + if (params) parameters = { ...parameters, ...params }; + nodeQueries.push(cypher); + } + query += nodeQueries.join(', '); + + const edgeQueries: string[] = []; + for (const edge of edges) { + const { cypher, where, params } = computeEdgeQuery( + edge.key, + DEFAULT_RELATIONSHIP_LABEL, + edge.attributes, + edge.source, + edge.target, + type === 'directed' + ); + if (where) whereClauses.push(where); + if (params) parameters = { ...parameters, ...params }; + edgeQueries.push(cypher); + } + if (nodeQueries.length && edgeQueries.length) query += ', '; + query += edgeQueries.join(', '); + + if (!homo) { + const nodeInj = computeInjectivityClause( + nodes.map((n) => n.key), + hasWhere + ); + query += nodeInj.cypher; + hasWhere = nodeInj.hasWhere; + const edgeInj = computeInjectivityClause( + edges.map((e) => e.key), + hasWhere + ); + query += edgeInj.cypher; + hasWhere = edgeInj.hasWhere; + } + + if (whereClauses.length) { + query += (!hasWhere ? ' WHERE' : ' AND') + whereClauses.join(' AND'); + } + + const returnParts = [ + ...nodeVars.map((v) => `properties(\`${v}\`) AS \`${v}\``), + ...edgeVars.map((v) => `properties(\`${v}\`) AS \`${v}\``), + ]; + const cypher = `MATCH ${query} RETURN ${returnParts.join(', ')}`; + + const res = await this.db.executeCypher(cypher, parameters); + const mainResults = res.toArray().map((row) => { + const matchResult: DBGraphPatternMatchResult = { nodes: {}, edges: {} }; + for (const v of nodeVars) { + const props = row[v] as Record | undefined; + if (props) matchResult.nodes[v] = this.propsToNodeResult(props); + } + for (const v of edgeVars) { + const props = row[v] as Record | undefined; + if (props) matchResult.edges[v] = this.propsToEdgeResult(props); + } + return matchResult; + }); + + // Apply NACs as post-query filter + if (!nacs.length) return mainResults; + + const finalResults: DBGraphPatternMatchResult[] = []; + for (const matchResult of mainResults) { + let violated = false; + for (const nac of nacs) { + if (await this.nacMatchExists(nac, matchResult)) { + violated = true; + break; + } + } + if (!violated) finalResults.push(matchResult); + } + return finalResults; + } + + /** + * Check whether a NAC pattern can be matched given the current binding. + * Returns true if the NAC fires (violation), false if the match is safe. + */ + private async nacMatchExists( + nac: DBGraphNACs, + matchResult: DBGraphPatternMatchResult + ): Promise { + const nacNodes = (nac.nodes || []) as PatternNodeSchema[]; + const nacEdges = (nac.edges || []) as DBGraphEdge[]; + const nacType = (nac.options?.type ?? 'undirected') as DBGraphType; + + if (!nacNodes.length && !nacEdges.length) return true; + + let parameters: Record = {}; + const whereClauses: string[] = []; + const nodeQueries: string[] = []; + const edgeQueries: string[] = []; + + for (const nacNode of nacNodes) { + const { cypher, where, params } = computeNodeQuery( + nacNode.key, + [DEFAULT_NODE_LABEL], + nacNode.attributes ?? {} + ); + nodeQueries.push(cypher); + if (where) whereClauses.push(where); + if (params) parameters = { ...parameters, ...params }; + + // Pin already-bound NAC nodes to their matched host node + if (nacNode.key in matchResult.nodes) { + const boundId = matchResult.nodes[nacNode.key].key; + const paramId = createParameterUuid(); + parameters[paramId] = boundId; + whereClauses.push( + `\`${sanitizeIdentifier(nacNode.key)}\`._grs_internalId = $${paramId}` + ); + } + } + + for (const edge of nacEdges) { + const { cypher, where, params } = computeEdgeQuery( + edge.key, + DEFAULT_RELATIONSHIP_LABEL, + edge.attributes, + edge.source, + edge.target, + nacType === 'directed' + ); + edgeQueries.push(cypher); + if (where) whereClauses.push(where); + if (params) parameters = { ...parameters, ...params }; + } + + let matchPart = nodeQueries.join(', '); + if (nodeQueries.length && edgeQueries.length) matchPart += ', '; + matchPart += edgeQueries.join(', '); + + if (!matchPart) return false; + + const whereStr = whereClauses.length + ? ' WHERE ' + whereClauses.join(' AND ') + : ''; + const cypher = `MATCH ${matchPart}${whereStr} RETURN COUNT(*) AS c`; + + const result = await this.db.executeCypher(cypher, parameters); + const rows = result.toArray(); + if (!rows.length) return false; + return (rows[0].c as number) > 0; + } +} diff --git a/services/graph-rewriting-service/src/service/db/types.ts b/services/graph-rewriting-service/src/service/db/types.ts index 938e4db..0937fa9 100644 --- a/services/graph-rewriting-service/src/service/db/types.ts +++ b/services/graph-rewriting-service/src/service/db/types.ts @@ -102,7 +102,9 @@ export interface IGraphDB { getEdge( internalId: DBGraphEdgeInternalId ): Promise; - deleteEdge(internalId: DBGraphEdgeInternalId): Promise; + deleteEdge( + internalId: DBGraphEdgeInternalId + ): Promise; deleteEdges( internalIds: DBGraphEdgeInternalId[] ): Promise; diff --git a/services/graph-rewriting-service/src/types/run-config.schema.d.ts b/services/graph-rewriting-service/src/types/run-config.schema.d.ts index 0277283..3dfb998 100644 --- a/services/graph-rewriting-service/src/types/run-config.schema.d.ts +++ b/services/graph-rewriting-service/src/types/run-config.schema.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable */ + /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, diff --git a/services/graph-rewriting-service/src/types/run-nested-config.schema.d.ts b/services/graph-rewriting-service/src/types/run-nested-config.schema.d.ts index 9c18ab0..a1d0897 100644 --- a/services/graph-rewriting-service/src/types/run-nested-config.schema.d.ts +++ b/services/graph-rewriting-service/src/types/run-nested-config.schema.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable */ + /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, diff --git a/services/graph-rewriting-service/supervisord.conf b/services/graph-rewriting-service/supervisord.conf deleted file mode 100644 index 4d27bf7..0000000 --- a/services/graph-rewriting-service/supervisord.conf +++ /dev/null @@ -1,19 +0,0 @@ -[supervisord] -nodaemon=true -logfile=/dev/stdout -logfile_maxbytes=0 -pidfile=/var/run/supervisord.pid -childlogdir=/var/log/supervisor - -; The Fastify app is the only process supervisord manages. -; Neo4j is started by the entrypoint script and runs as its own self-managed -; Java process — supervisord cannot monitor it via neo4j console since it was -; already daemonised by 'neo4j start'. -[program:app] -command=node /app/dist/index.js -autorestart=true -startretries=3 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 diff --git a/services/jsonpath-mapper-service/.dockerignore b/services/jsonpath-mapper-service/.dockerignore new file mode 100644 index 0000000..f004b08 --- /dev/null +++ b/services/jsonpath-mapper-service/.dockerignore @@ -0,0 +1,12 @@ +# Ignore everything by default, then allow only build inputs. +** + +# Build metadata +!package.json +!package-lock.json +!tsconfig.json +!import-meta-url.js + +# Source and build scripts +!src/** +!scripts/** diff --git a/services/jsonpath-mapper-service/Dockerfile b/services/jsonpath-mapper-service/Dockerfile index 8b9c301..c310dbf 100644 --- a/services/jsonpath-mapper-service/Dockerfile +++ b/services/jsonpath-mapper-service/Dockerfile @@ -1,23 +1,23 @@ -# ── Stage 1: deps ──────────────────────────────────────────────────────────── -# Installs all dependencies (dev deps needed to run tsx at runtime). -FROM node:22-slim AS deps +# ── Stage 1: build ─────────────────────────────────────────────────────────── +# Install all deps (incl. devDeps for esbuild) and bundle the API. +FROM node:22-slim AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci +COPY . . + +RUN node scripts/build-api.mjs + # ── Stage 2: runtime ────────────────────────────────────────────────────────── -# The API is served directly from TypeScript source via tsx (no separate -# compile step), which is the canonical start:api script in package.json. -FROM node:22-slim +# Alpine base + single bundled file — no node_modules needed. +FROM node:22-alpine WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY package.json ./ -COPY tsconfig.json ./ -COPY src/ ./src/ +COPY --from=build /app/dist/ ./ # ── Environment defaults ─────────────────────────────────────────────────────── ENV NODE_ENV=production \ @@ -28,4 +28,4 @@ ENV NODE_ENV=production \ # Defaultport: 3000 – Fastify HTTP API EXPOSE 3000 -CMD ["node_modules/.bin/tsx", "src/api/index.ts"] +CMD ["node", "api.cjs"] diff --git a/services/jsonpath-mapper-service/import-meta-url.js b/services/jsonpath-mapper-service/import-meta-url.js new file mode 100644 index 0000000..4ba3289 --- /dev/null +++ b/services/jsonpath-mapper-service/import-meta-url.js @@ -0,0 +1 @@ +export const import_meta_url = require('url').pathToFileURL(__filename).href; diff --git a/services/jsonpath-mapper-service/package-lock.json b/services/jsonpath-mapper-service/package-lock.json index 8a70f68..d6fcadf 100644 --- a/services/jsonpath-mapper-service/package-lock.json +++ b/services/jsonpath-mapper-service/package-lock.json @@ -1,5922 +1,6413 @@ { - "name": "jsonpath-mapper", - "version": "2.0.3", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "jsonpath-mapper", - "version": "2.0.3", - "license": "ISC", - "dependencies": { - "@fastify/swagger": "^9.7.0", - "@fastify/swagger-ui": "^5.2.5", - "@sinclair/typebox": "^0.34.48", - "fastify": "^5.7.4", - "fromentries": "^1.3.2", - "jsonpath-plus": "^10.2.0", - "nyc": "^18.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.15.0", - "@rollup/plugin-typescript": "^12.1.1", - "chai": "^5.1.2", - "eslint": "^9.15.0", - "eslint-plugin-mocha": "^10.5.0", - "globals": "^15.12.0", - "mocha": "^10.8.2", - "prettier": "^3.4.1", - "rimraf": "^6.0.1", - "rollup": "^4.27.4", - "rollup-plugin-dts": "^6.1.1", - "rollup-plugin-node-resolve": "^5.2.0", - "tslib": "^2.8.1", - "tsx": "^4.21.0", - "typescript": "^5.7.2", - "typescript-eslint": "^8.16.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "peer": true, - "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", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "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==", - "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", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", - "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@fastify/accept-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", - "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/ajv-compiler": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", - "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" - } - }, - "node_modules/@fastify/ajv-compiler/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/@fastify/error": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", - "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", - "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "fast-json-stringify": "^6.0.0" - } - }, - "node_modules/@fastify/forwarded": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", - "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", - "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@fastify/proxy-addr": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", - "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/forwarded": "^3.0.0", - "ipaddr.js": "^2.1.0" - } - }, - "node_modules/@fastify/send": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", - "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@lukeed/ms": "^2.0.2", - "escape-html": "~1.0.3", - "fast-decode-uri-component": "^1.0.1", - "http-errors": "^2.0.0", - "mime": "^3" - } - }, - "node_modules/@fastify/static": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz", - "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "@fastify/send": "^4.0.0", - "content-disposition": "^1.0.1", - "fastify-plugin": "^5.0.0", - "fastq": "^1.17.1", - "glob": "^13.0.0" - } - }, - "node_modules/@fastify/swagger": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", - "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "fastify-plugin": "^5.0.0", - "json-schema-resolver": "^3.0.0", - "openapi-types": "^12.1.3", - "rfdc": "^1.3.1", - "yaml": "^2.4.2" - } - }, - "node_modules/@fastify/swagger-ui": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.5.tgz", - "integrity": "sha512-ky3I0LAkXKX/prwSDpoQ3kscBKsj2Ha6Gp1/JfgQSqyx0bm9F2bE//XmGVGj2cR9l5hUjZYn60/hqn7e+OLgWQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/static": "^9.0.0", - "fastify-plugin": "^5.0.0", - "openapi-types": "^12.1.3", - "rfdc": "^1.3.1", - "yaml": "^2.4.1" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.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", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "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==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsep-plugin/assignment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", - "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@jsep-plugin/regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", - "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@rollup/plugin-typescript": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz", - "integrity": "sha512-t7O653DpfB5MbFrqPe/VcKFFkvRuFNp9qId3xq4Eth5xlyymzxNpye2z8Hrl0RIMuXTSr5GGcFpkdlMeacUiFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", - "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", - "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", - "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", - "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", - "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", - "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", - "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", - "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", - "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", - "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", - "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", - "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", - "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", - "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", - "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", - "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", - "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", - "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "14.14.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", - "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/type-utils": "8.16.0", - "@typescript-eslint/utils": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", - "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", - "debug": "^4.3.4" - }, - "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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", - "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/utils": "8.16.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", - "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.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", - "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/abstract-logging": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", - "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "license": "MIT", - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/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/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/avvio": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", - "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/error": "^4.0.0", - "fastq": "^1.17.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "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==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "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" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "license": "MIT", - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", - "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": "5.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", - "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "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==", - "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==", - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT" - }, - "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/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "license": "MIT", - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", - "@eslint/plugin-kit": "^0.2.3", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-mocha": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", - "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^3.0.0", - "globals": "^13.24.0", - "rambda": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-mocha/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/eslint/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/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stringify": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz", - "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.2.0", - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0", - "json-schema-ref-resolver": "^3.0.0", - "rfdc": "^1.2.0" - } - }, - "node_modules/fast-json-stringify/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastify": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.4.tgz", - "integrity": "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^4.0.5", - "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^5.0.0", - "@fastify/proxy-addr": "^5.0.0", - "abstract-logging": "^2.0.1", - "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", - "light-my-request": "^6.0.0", - "pino": "^10.1.0", - "process-warning": "^5.0.0", - "rfdc": "^1.3.1", - "secure-json-parse": "^4.0.0", - "semver": "^7.6.0", - "toad-cache": "^3.7.0" - } - }, - "node_modules/fastify-plugin": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", - "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-my-way": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz", - "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^5.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "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" - ], - "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", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/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==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", - "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-3.0.0.tgz", - "integrity": "sha512-P7nLXRRlo7Sqinty6lNa7+4o9jBUYGpqtejqCOZKfgXlRoxY/QArflcB86YO500Ahj4pDJEG34JjMRbQgePLnQ==", - "license": "ISC", - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^6.1.3", - "uuid": "^8.3.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsep": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", - "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 10.16.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-ref-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", - "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/json-schema-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-3.0.0.tgz", - "integrity": "sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fast-uri": "^3.0.5", - "rfdc": "^1.1.4" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonpath-plus": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz", - "integrity": "sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw==", - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/light-my-request": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", - "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^1.0.1", - "process-warning": "^4.0.0", - "set-cookie-parser": "^2.6.0" - } - }, - "node_modules/light-my-request/node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/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/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loupe": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", - "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "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/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "license": "MIT", - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-18.0.0.tgz", - "integrity": "sha512-G5UyHinFkB1BxqGTrmZdB6uIYH0+v7ZnVssuflUDi+J+RhKWyAhRT1RCehBSI6jLFLuUUgFDyLt49mUtdO1XeQ==", - "license": "ISC", - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^3.3.0", - "get-package-type": "^0.1.0", - "glob": "^13.0.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^6.0.2", - "istanbul-lib-processinfo": "^3.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^6.1.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^3.0.0", - "test-exclude": "^8.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/nyc/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "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==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "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/pino": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", - "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^4.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", - "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-on-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", - "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", - "license": "MIT", - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/rambda": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", - "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "license": "ISC", - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rimraf": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", - "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "glob": "^13.0.3", - "package-json-from-dist": "^1.0.1" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", - "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.4", - "@rollup/rollup-android-arm64": "4.27.4", - "@rollup/rollup-darwin-arm64": "4.27.4", - "@rollup/rollup-darwin-x64": "4.27.4", - "@rollup/rollup-freebsd-arm64": "4.27.4", - "@rollup/rollup-freebsd-x64": "4.27.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", - "@rollup/rollup-linux-arm-musleabihf": "4.27.4", - "@rollup/rollup-linux-arm64-gnu": "4.27.4", - "@rollup/rollup-linux-arm64-musl": "4.27.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", - "@rollup/rollup-linux-riscv64-gnu": "4.27.4", - "@rollup/rollup-linux-s390x-gnu": "4.27.4", - "@rollup/rollup-linux-x64-gnu": "4.27.4", - "@rollup/rollup-linux-x64-musl": "4.27.4", - "@rollup/rollup-win32-arm64-msvc": "4.27.4", - "@rollup/rollup-win32-ia32-msvc": "4.27.4", - "@rollup/rollup-win32-x64-msvc": "4.27.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-dts": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz", - "integrity": "sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==", - "dev": true, - "license": "LGPL-3.0-only", - "dependencies": { - "magic-string": "^0.30.10" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/Swatinem" - }, - "optionalDependencies": { - "@babel/code-frame": "^7.24.2" - }, - "peerDependencies": { - "rollup": "^3.29.4 || ^4", - "typescript": "^4.5 || ^5.0" - } - }, - "node_modules/rollup-plugin-node-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", - "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.11.1", - "rollup-pluginutils": "^2.8.1" - }, - "peerDependencies": { - "rollup": ">=1.11.0" - } - }, - "node_modules/rollup-plugin-node-resolve/node_modules/builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", - "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawn-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz", - "integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "cross-spawn": "^7.0.6", - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^6.1.3", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/spawn-wrap/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", - "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^13.0.6", - "minimatch": "^10.2.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/test-exclude/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==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/test-exclude/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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/thread-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", - "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-api-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.2.tgz", - "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.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", - "peer": true - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz", - "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.16.0", - "@typescript-eslint/parser": "8.16.0", - "@typescript-eslint/utils": "8.16.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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "jsonpath-mapper", + "version": "2.0.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jsonpath-mapper", + "version": "2.0.3", + "license": "ISC", + "dependencies": { + "@fastify/swagger": "^9.7.0", + "@fastify/swagger-ui": "^5.2.5", + "@sinclair/typebox": "^0.34.48", + "fastify": "^5.7.4", + "fromentries": "^1.3.2", + "jsonpath-plus": "^10.2.0", + "nyc": "^18.0.0" + }, + "bin": { + "jsonpath-mapper": "dist/cli.mjs" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@rollup/plugin-typescript": "^12.1.1", + "@types/node": "^25.5.2", + "chai": "^5.1.2", + "esbuild": "^0.25.0", + "eslint": "^9.15.0", + "eslint-plugin-mocha": "^10.5.0", + "globals": "^15.12.0", + "mocha": "^10.8.2", + "prettier": "^3.4.1", + "rimraf": "^6.0.1", + "rollup": "^4.27.4", + "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-node-resolve": "^5.2.0", + "tslib": "^2.8.1", + "tsx": "^4.21.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "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", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "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==", + "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", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz", + "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^1.0.1", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^13.0.0" + } + }, + "node_modules/@fastify/swagger": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", + "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "json-schema-resolver": "^3.0.0", + "openapi-types": "^12.1.3", + "rfdc": "^1.3.1", + "yaml": "^2.4.2" + } + }, + "node_modules/@fastify/swagger-ui": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.5.tgz", + "integrity": "sha512-ky3I0LAkXKX/prwSDpoQ3kscBKsj2Ha6Gp1/JfgQSqyx0bm9F2bE//XmGVGj2cR9l5hUjZYn60/hqn7e+OLgWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/static": "^9.0.0", + "fastify-plugin": "^5.0.0", + "openapi-types": "^12.1.3", + "rfdc": "^1.3.1", + "yaml": "^2.4.1" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.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", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-typescript": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz", + "integrity": "sha512-t7O653DpfB5MbFrqPe/VcKFFkvRuFNp9qId3xq4Eth5xlyymzxNpye2z8Hrl0RIMuXTSr5GGcFpkdlMeacUiFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", + "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", + "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", + "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", + "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", + "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", + "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", + "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", + "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", + "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", + "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", + "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", + "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", + "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", + "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", + "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", + "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", + "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", + "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4" + }, + "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" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.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" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "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.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.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" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.16.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "license": "MIT", + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/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/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "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==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "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/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "license": "MIT", + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/eslint": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", + "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^3.0.0", + "globals": "^13.24.0", + "rambda": "^7.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/eslint/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/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz", + "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.4.tgz", + "integrity": "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-my-way": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz", + "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "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" + ], + "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", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/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==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "license": "BSD-3-Clause", + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-3.0.0.tgz", + "integrity": "sha512-P7nLXRRlo7Sqinty6lNa7+4o9jBUYGpqtejqCOZKfgXlRoxY/QArflcB86YO500Ahj4pDJEG34JjMRbQgePLnQ==", + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^6.1.3", + "uuid": "^8.3.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-3.0.0.tgz", + "integrity": "sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fast-uri": "^3.0.5", + "rfdc": "^1.1.4" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz", + "integrity": "sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "license": "MIT", + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-18.0.0.tgz", + "integrity": "sha512-G5UyHinFkB1BxqGTrmZdB6uIYH0+v7ZnVssuflUDi+J+RhKWyAhRT1RCehBSI6jLFLuUUgFDyLt49mUtdO1XeQ==", + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^3.3.0", + "get-package-type": "^0.1.0", + "glob": "^13.0.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^6.0.2", + "istanbul-lib-processinfo": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^6.1.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^3.0.0", + "test-exclude": "^8.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "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==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "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/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "license": "ISC", + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.27.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", + "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.27.4", + "@rollup/rollup-android-arm64": "4.27.4", + "@rollup/rollup-darwin-arm64": "4.27.4", + "@rollup/rollup-darwin-x64": "4.27.4", + "@rollup/rollup-freebsd-arm64": "4.27.4", + "@rollup/rollup-freebsd-x64": "4.27.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", + "@rollup/rollup-linux-arm-musleabihf": "4.27.4", + "@rollup/rollup-linux-arm64-gnu": "4.27.4", + "@rollup/rollup-linux-arm64-musl": "4.27.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", + "@rollup/rollup-linux-riscv64-gnu": "4.27.4", + "@rollup/rollup-linux-s390x-gnu": "4.27.4", + "@rollup/rollup-linux-x64-gnu": "4.27.4", + "@rollup/rollup-linux-x64-musl": "4.27.4", + "@rollup/rollup-win32-arm64-msvc": "4.27.4", + "@rollup/rollup-win32-ia32-msvc": "4.27.4", + "@rollup/rollup-win32-x64-msvc": "4.27.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-dts": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz", + "integrity": "sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "magic-string": "^0.30.10" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.24.2" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0" + } + }, + "node_modules/rollup-plugin-node-resolve": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", + "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.1", + "rollup-pluginutils": "^2.8.1" + }, + "peerDependencies": { + "rollup": ">=1.11.0" + } + }, + "node_modules/rollup-plugin-node-resolve/node_modules/builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz", + "integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "cross-spawn": "^7.0.6", + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^6.1.3", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/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==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.2.tgz", + "integrity": "sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.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" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz", + "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.16.0", + "@typescript-eslint/parser": "8.16.0", + "@typescript-eslint/utils": "8.16.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" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "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==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } } diff --git a/services/jsonpath-mapper-service/package.json b/services/jsonpath-mapper-service/package.json index 086d667..4f48edf 100644 --- a/services/jsonpath-mapper-service/package.json +++ b/services/jsonpath-mapper-service/package.json @@ -20,12 +20,19 @@ } }, "docs": "https://github.com/dchester/jsonpath/blob/master/README.md", + "bin": { + "jsonpath-mapper": "./dist/cli.mjs" + }, "scripts": { "build": "rimraf ./dist && rollup -c", + "build:api": "node scripts/build-api.mjs", "prestart": "npm run build", "start": "npm run test", "start:api": "tsx src/api/index.ts", + "cli": "tsx src/cli.ts", "test": "mocha tests/json-mapper.tests.mjs", + "test:cli": "mocha tests/cli.tests.mjs --timeout 10000", + "test:all": "mocha tests/json-mapper.tests.mjs tests/cli.tests.mjs --timeout 10000", "test:coverage": "nyc mocha tests/json-mapper.tests.mjs", "lint": "eslint ./src", "generate:openapi": "tsx scripts/generate-openapi.ts" @@ -73,6 +80,7 @@ "devDependencies": { "@eslint/js": "^9.15.0", "@rollup/plugin-typescript": "^12.1.1", + "@types/node": "^25.5.2", "chai": "^5.1.2", "eslint": "^9.15.0", "eslint-plugin-mocha": "^10.5.0", @@ -83,9 +91,10 @@ "rollup": "^4.27.4", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-node-resolve": "^5.2.0", + "esbuild": "^0.25.0", "tslib": "^2.8.1", "tsx": "^4.21.0", "typescript": "^5.7.2", "typescript-eslint": "^8.16.0" } -} \ No newline at end of file +} diff --git a/services/jsonpath-mapper-service/rollup.config.mjs b/services/jsonpath-mapper-service/rollup.config.mjs index f07b45a..68db1ef 100644 --- a/services/jsonpath-mapper-service/rollup.config.mjs +++ b/services/jsonpath-mapper-service/rollup.config.mjs @@ -22,6 +22,18 @@ export default [ external: ['jsonpath-plus'], plugins: [typescriptPlugin()], }, + { + input: 'src/cli.ts', + output: [ + { + file: 'dist/cli.mjs', + format: 'es', + banner: '#!/usr/bin/env node', + }, + ], + external: ['jsonpath-plus', 'node:fs', 'node:path'], + plugins: [typescriptPlugin()], + }, { input: 'src/index.ts', output: [{ file: 'dist/jsonpath-mapper.d.ts', format: 'es' }], diff --git a/services/jsonpath-mapper-service/scripts/build-api.mjs b/services/jsonpath-mapper-service/scripts/build-api.mjs new file mode 100644 index 0000000..039f1bf --- /dev/null +++ b/services/jsonpath-mapper-service/scripts/build-api.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +import { build } from 'esbuild'; +import { readFileSync, cpSync, rmSync } from 'fs'; + +// Read version and description from package.json +const pkg = JSON.parse(readFileSync('./package.json', 'utf8')); +const PKG_VERSION = pkg.version; +const PKG_DESCRIPTION = pkg.description; + +const shared = { + bundle: true, + platform: 'node', + target: 'node22', + format: 'cjs', + minify: true, +}; + +// Clean dist directory +rmSync('dist', { recursive: true, force: true }); + +await Promise.all([ + // API server bundle + build({ + ...shared, + entryPoints: ['src/api/index.ts'], + outfile: 'dist/api.cjs', + // Shim import.meta.url → pathToFileURL(__filename) so that packages like + // @fastify/swagger-ui can resolve __dirname for their static assets. + inject: ['./import-meta-url.js'], + define: { + 'import.meta.url': 'import_meta_url', + '__PKG_VERSION__': JSON.stringify(PKG_VERSION), + '__PKG_DESCRIPTION__': JSON.stringify(PKG_DESCRIPTION), + }, + }), + // CLI bundle + build({ + ...shared, + entryPoints: ['src/cli.ts'], + outfile: 'dist/cli.cjs', + }), +]); + +// Copy @fastify/swagger-ui static files so they can be served at runtime +cpSync('node_modules/@fastify/swagger-ui/static', 'dist/static', { recursive: true }); diff --git a/services/jsonpath-mapper-service/src/api/server.ts b/services/jsonpath-mapper-service/src/api/server.ts index 8da9c0d..1cedd0d 100644 --- a/services/jsonpath-mapper-service/src/api/server.ts +++ b/services/jsonpath-mapper-service/src/api/server.ts @@ -10,14 +10,34 @@ import { MapResponseSchema, ErrorResponseSchema, } from './schemas/map.schema.js'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; -// Read version from package.json — tsx supports JSON imports -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); -const pkg = require('../../package.json') as { - version: string; - description: string; -}; +// Version and description are injected at build time by esbuild's define option. +// For development (tsx), fall back to reading from package.json. +declare const __PKG_VERSION__: string; +declare const __PKG_DESCRIPTION__: string; + +let PKG_VERSION = '0.0.0'; +let PKG_DESCRIPTION = ''; + +// Try to use injected constants (production build), fall back to package.json (development) +try { + PKG_VERSION = __PKG_VERSION__; + PKG_DESCRIPTION = __PKG_DESCRIPTION__; +} catch { + // Development mode: read from package.json + try { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const pkgPath = join(__dirname, '../../../package.json'); + const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')); + PKG_VERSION = pkg.version ?? '0.0.0'; + PKG_DESCRIPTION = pkg.description ?? ''; + } catch { + // Fallback defaults already set above + } +} /** * Creates and configures the Fastify server instance. @@ -41,14 +61,14 @@ export async function buildServer() { info: { title: 'jsonpath-mapper API', description: - pkg.description + + PKG_DESCRIPTION + '\n\n' + 'This REST API wraps the `jsonpath-mapper` library, exposing its ' + 'JSON-to-JSON transformation capabilities over HTTP.\n\n' + '**Limitations:** Template values that require JavaScript functions ' + '(`$formatting`, `$return`, `$disable`) cannot be expressed in a ' + 'JSON request body. Use the npm library directly for those cases.', - version: pkg.version, + version: PKG_VERSION, contact: { url: 'https://github.com/neilflatley/jsonpath-mapper', }, diff --git a/services/jsonpath-mapper-service/src/cli.ts b/services/jsonpath-mapper-service/src/cli.ts new file mode 100644 index 0000000..52152d4 --- /dev/null +++ b/services/jsonpath-mapper-service/src/cli.ts @@ -0,0 +1,93 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import mapJson from './json-mapper.js'; +import type { MappingTemplate } from './models/Template.js'; + +function usage(): never { + console.error(`Usage: jsonpath-mapper [-o output.json] + +Arguments: + data.json Path to the source JSON file + template.json Path to the mapping template JSON file + +Options: + -o, --output Write result to a file instead of stdout + -h, --help Show this help message + +Note: Template values that require JavaScript functions ($formatting, +$return, $disable) cannot be expressed in JSON. Use the npm library +directly for those cases.`); + process.exit(1); +} + +function main() { + const args = process.argv.slice(2); + + if (args.includes('-h') || args.includes('--help') || args.length < 2) { + usage(); + } + + let dataPath: string | undefined; + let templatePath: string | undefined; + let outputPath: string | undefined; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '-o' || args[i] === '--output') { + outputPath = args[++i]; + if (!outputPath) { + console.error('Error: -o requires a file path argument'); + process.exit(1); + } + } else if (!dataPath) { + dataPath = args[i]; + } else if (!templatePath) { + templatePath = args[i]; + } else { + console.error(`Error: unexpected argument "${args[i]}"`); + usage(); + } + } + + if (!dataPath || !templatePath) { + usage(); + } + + let data: unknown; + try { + const raw = + dataPath === '-' + ? readFileSync(0, 'utf-8') + : readFileSync(resolve(dataPath), 'utf-8'); + data = JSON.parse(raw); + } catch (err) { + console.error( + `Error reading data file "${dataPath}":`, + (err as Error).message + ); + process.exit(1); + } + + let template: MappingTemplate; + try { + template = JSON.parse(readFileSync(resolve(templatePath), 'utf-8')); + } catch (err) { + console.error( + `Error reading template file "${templatePath}":`, + (err as Error).message + ); + process.exit(1); + } + + const result = mapJson(data, template); + const output = JSON.stringify(result, null, 2); + + if (outputPath) { + writeFileSync(resolve(outputPath), output + '\n', 'utf-8'); + } else { + console.log(output); + } +} + +main(); diff --git a/services/jsonpath-mapper-service/tests/cli.tests.mjs b/services/jsonpath-mapper-service/tests/cli.tests.mjs new file mode 100644 index 0000000..5012b2f --- /dev/null +++ b/services/jsonpath-mapper-service/tests/cli.tests.mjs @@ -0,0 +1,153 @@ +import { expect } from 'chai'; +import { execFileSync, execSync } from 'node:child_process'; +import { writeFileSync, readFileSync, unlinkSync, mkdtempSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +const CLI = 'src/cli.ts'; +const run = (args, opts = {}) => + execFileSync('npx', ['tsx', CLI, ...args], { + encoding: 'utf-8', + cwd: new URL('..', import.meta.url).pathname, + ...opts, + }); + +const runFail = (args, opts = {}) => { + try { + run(args, { ...opts, stdio: ['pipe', 'pipe', 'pipe'] }); + throw new Error('Expected command to fail'); + } catch (err) { + if (err.message === 'Expected command to fail') throw err; + return { status: err.status, stderr: err.stderr?.toString() ?? '', stdout: err.stdout?.toString() ?? '' }; + } +}; + +let tmpDir; + +before(() => { + tmpDir = mkdtempSync(join(tmpdir(), 'cli-test-')); +}); + +const writeJson = (name, obj) => { + const p = join(tmpDir, name); + writeFileSync(p, JSON.stringify(obj)); + return p; +}; + +describe('CLI', () => { + describe('basic mapping', () => { + it('should map data using a template', () => { + const dataFile = writeJson('data.json', { + user: { firstName: 'Alice', lastName: 'Smith', address: { city: 'Vienna' } }, + }); + const templateFile = writeJson('template.json', { + name: '.user.firstName', + surname: '.user.lastName', + city: '.user.address.city', + }); + + const stdout = run([dataFile, templateFile]); + const result = JSON.parse(stdout); + + expect(result).to.deep.equal({ + name: 'Alice', + surname: 'Smith', + city: 'Vienna', + }); + }); + + it('should handle nested template objects', () => { + const dataFile = writeJson('nested-data.json', { + books: [ + { title: 'Clean Code', author: { name: 'Robert C. Martin' }, price: 17.96 }, + { title: 'The Good Parts', author: { name: 'Douglas Crockford' }, price: 15.67 }, + ], + }); + const templateFile = writeJson('nested-template.json', { + allTitles: '.books[*].title', + firstAuthor: '.books[0].author.name', + }); + + const result = JSON.parse(run([dataFile, templateFile])); + + expect(result.allTitles).to.deep.equal(['Clean Code', 'The Good Parts']); + expect(result.firstAuthor).to.equal('Robert C. Martin'); + }); + }); + + describe('-o / --output flag', () => { + it('should write result to the specified output file', () => { + const dataFile = writeJson('out-data.json', { value: 42 }); + const templateFile = writeJson('out-template.json', { num: '.value' }); + const outFile = join(tmpDir, 'result.json'); + + run([dataFile, templateFile, '-o', outFile]); + + const written = JSON.parse(readFileSync(outFile, 'utf-8')); + expect(written).to.deep.equal({ num: 42 }); + }); + + it('should also work with --output', () => { + const dataFile = writeJson('out2-data.json', { x: 'hello' }); + const templateFile = writeJson('out2-template.json', { msg: '.x' }); + const outFile = join(tmpDir, 'result2.json'); + + run([dataFile, templateFile, '--output', outFile]); + + const written = JSON.parse(readFileSync(outFile, 'utf-8')); + expect(written).to.deep.equal({ msg: 'hello' }); + }); + }); + + describe('--help flag', () => { + it('should print usage and exit with code 1', () => { + const { status, stderr } = runFail(['--help']); + expect(status).to.equal(1); + expect(stderr).to.include('Usage: jsonpath-mapper'); + }); + + it('should print usage with -h', () => { + const { stderr } = runFail(['-h']); + expect(stderr).to.include('Usage: jsonpath-mapper'); + }); + }); + + describe('error handling', () => { + it('should fail with no arguments', () => { + const { status, stderr } = runFail([]); + expect(status).to.equal(1); + expect(stderr).to.include('Usage:'); + }); + + it('should fail when data file does not exist', () => { + const templateFile = writeJson('err-template.json', { a: '.b' }); + const { status, stderr } = runFail(['/tmp/nonexistent-data-12345.json', templateFile]); + expect(status).to.equal(1); + expect(stderr).to.include('Error reading data file'); + }); + + it('should fail when template file does not exist', () => { + const dataFile = writeJson('err-data.json', { b: 1 }); + const { status, stderr } = runFail([dataFile, '/tmp/nonexistent-template-12345.json']); + expect(status).to.equal(1); + expect(stderr).to.include('Error reading template file'); + }); + + it('should fail when data file contains invalid JSON', () => { + const badFile = join(tmpDir, 'bad.json'); + writeFileSync(badFile, '{ not valid json }'); + const templateFile = writeJson('err2-template.json', { a: '.b' }); + const { status, stderr } = runFail([badFile, templateFile]); + expect(status).to.equal(1); + expect(stderr).to.include('Error reading data file'); + }); + + it('should fail when -o is missing its argument', () => { + const dataFile = writeJson('err3-data.json', { x: 1 }); + const templateFile = writeJson('err3-template.json', { y: '.x' }); + const { status, stderr } = runFail([dataFile, templateFile, '-o']); + expect(status).to.equal(1); + expect(stderr).to.include('-o requires a file path'); + }); + }); +}); diff --git a/services/sql-assessment-service/.dockerignore b/services/sql-assessment-service/.dockerignore index 66e7dcd..fbaa0fe 100644 --- a/services/sql-assessment-service/.dockerignore +++ b/services/sql-assessment-service/.dockerignore @@ -1,4 +1,5 @@ node_modules +build dist .git Dockerfile diff --git a/services/sql-assessment-service/Dockerfile b/services/sql-assessment-service/Dockerfile index a7a7964..52c0ffe 100644 --- a/services/sql-assessment-service/Dockerfile +++ b/services/sql-assessment-service/Dockerfile @@ -1,14 +1,30 @@ -FROM node:20 +# ---- build stage ---- +FROM node:20-alpine AS build WORKDIR /app -COPY package.json ./ -COPY tsconfig.json ./ +COPY package.json package-lock.json ./ +RUN npm ci -RUN npm install +COPY src/ src/ +COPY tsconfig.json import-meta-url.js ./ -COPY ./ ./ +RUN npm run build -EXPOSE 3333 +# Install only production deps for the runtime image. +# @electric-sql/pglite is excluded from the bundle (WASM cannot be inlined). +# swagger-ui-dist is excluded because it serves static files from the filesystem. +RUN npm ci --omit=dev -CMD ["npx", "ts-node", "src/index.ts"] +# ---- production stage ---- +FROM node:20-alpine + +WORKDIR /app + +COPY --from=build /app/build/ ./build/ +COPY --from=build /app/node_modules/@electric-sql/pglite ./node_modules/@electric-sql/pglite +COPY --from=build /app/node_modules/swagger-ui-dist ./node_modules/swagger-ui-dist + +EXPOSE 3000 + +CMD ["node", "build/index.js"] diff --git a/services/sql-assessment-service/Makefile b/services/sql-assessment-service/Makefile index 2644723..09a4d29 100644 --- a/services/sql-assessment-service/Makefile +++ b/services/sql-assessment-service/Makefile @@ -1,4 +1,4 @@ -.PHONY: prep build test lint start clean docker-build generate-openapi +.PHONY: prep build test lint start clean docker-build generate-openapi cli prep: npm ci @@ -24,3 +24,7 @@ docker-build: generate-openapi: npm run generate-openapi + +cli: + npm run build + @echo "CLI ready: node build/cli/index.js --help" diff --git a/services/sql-assessment-service/README.md b/services/sql-assessment-service/README.md index 7f18401..0b919eb 100644 --- a/services/sql-assessment-service/README.md +++ b/services/sql-assessment-service/README.md @@ -1,9 +1,42 @@ # sql-query-generation -To use this application add a .env file in the root level with the following key: `API_KEY={Add OpenAI key here}` +To use this application add a .env file in the root level with the following key: `OPENAI_API_KEY={Add OpenAI key here}` Run the application with the command: `npx ts-node src/index.ts` +## Docker + +```bash +# Build the image +docker build -t sql-assessment-service . + +# Run (PostgreSQL-only mode — no PGlite needed) +docker run -p 3000:3000 sql-assessment-service + +# Run with OpenAI key +docker run -p 3000:3000 -e OPENAI_API_KEY=sk-... sql-assessment-service +``` + +PGlite works out of the box inside the container — no external database required. The `@electric-sql/pglite` package (including its WASM binary) is copied into the image separately from the bundle because esbuild cannot inline WASM files. + +```bash +# Run with a pre-loaded PGlite schema (HTTP API mode) +docker run -p 3000:3000 \ + -e PGLITE_INIT_SQL_FILE=/data/schema.sql \ + -v /local/path/schema.sql:/data/schema.sql \ + sql-assessment-service +``` + +To start a local PostgreSQL alongside the service: + +```bash +docker compose up +``` + +The `docker-compose.yml` spins up a Postgres container. Uncomment the `app` service block to also run the API in Docker. + +## PostgreSQL (external database) + Database Analysis: Call the POST API: `http://localhost:3000/api/database/analyze-database` @@ -11,31 +44,18 @@ with the following DTO in the body: ```json { - "type": "postgres", - "host": "{DatabaseHost}", - "port": "{DatabasePort}", - "username": "{DatabaseUserName}", - "password": "{DatabasePassword}", - "database": "{DatabaseName}", - "schema": "{SchemaName}" -} -``` - -```json -{ - "type": "postgres", - "host": "localhost", - "port": "5432", - "username": "myuser", - "password": "mypass", - "database": "fussballdb", - "schema": "public" + "connectionInfo": { + "type": "postgres", + "host": "{DatabaseHost}", + "port": "{DatabasePort}", + "username": "{DatabaseUserName}", + "password": "{DatabasePassword}", + "database": "{DatabaseName}", + "schema": "{SchemaName}" + } } ``` -Task Generation: Call the GET API: -`http://localhost:3000/api/generation/generate` with the following body: - ```json { "connectionInfo": { @@ -46,21 +66,111 @@ Task Generation: Call the GET API: "password": "mypass", "database": "fussballdb", "schema": "public" - }, - "taskConfiguration": { - "aggregation": false, - "columnCount": 2, - "predicateCount": 2, - "operationTypes": [], - "joinDepth": 2, - "joinTypes": [], - "groupby": false, - "having": false, - "orderby": true } } ``` +## PGlite (in-process, no external database required) + +PGlite embeds a full PostgreSQL engine in-process. No server, no credentials — just pass your DDL + seed SQL and a unique `databaseId`. The instance is kept alive in memory for the lifetime of the service process. + +> **Auto-analyze**: The four endpoint groups (`/api/generation`, `/api/grading`, `/api/description`, `/api/query`) will **automatically** run the analyze step when `sqlContent` is present in the `connectionInfo`. This means a separate call to `/api/database/analyze-database` is **not required** — each request can be fully self-contained. + +### Pre-loading a schema via `PGLITE_INIT_SQL_FILE` / `--init-sql-file` + +Instead of including `sqlContent` in every request, you can point the service at a SQL file that will be used as the default `sqlContent` for **any** PGlite request where `sqlContent` is absent from the request body. + +| Method | How to set | +|---|---| +| Environment variable (HTTP API / Docker) | `PGLITE_INIT_SQL_FILE=/path/to/schema.sql` | +| CLI flag | `--init-sql-file /path/to/schema.sql` | + +**Priority**: an explicit `sqlContent` in the request body always takes precedence over the configured file. + +**Error behaviour**: if the configured path does not exist the service returns `500` for that request; all other requests are unaffected. + +**Example — CLI:** +```bash +npx ts-node src/cli/index.ts --init-sql-file ./schema.sql generation:generate '{"connectionInfo":{"type":"pglite","databaseId":"mydb"}}' +``` + +**Example — Docker:** +```bash +docker run -p 3000:3000 \ + -e PGLITE_INIT_SQL_FILE=/data/schema.sql \ + -v /local/path/schema.sql:/data/schema.sql \ + sql-assessment-service +``` + +After starting with `PGLITE_INIT_SQL_FILE`, you can omit `sqlContent` from every request body: + +```json +{ + "connectionInfo": { + "type": "pglite", + "databaseId": "my-db" + }, + "query": "SELECT * FROM products" +} +``` + + + +### 1. Analyze (register) a PGlite database (explicit, optional) + +Call `POST /api/database/analyze-database` with `type: "pglite"`: + +```json +{ + "connectionInfo": { + "type": "pglite", + "databaseId": "my-db", + "sqlContent": "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2));\nINSERT INTO products (name, price) VALUES ('Widget', 9.99);" + } +} +``` + +| Field | Required | Description | +|---|---|---| +| `type` | ✓ | Must be `"pglite"` | +| `databaseId` | ✓ | Arbitrary string key used to reference this instance in all subsequent calls | +| `sqlContent` | ✓ | Full DDL + optional seed DML executed once on a fresh in-memory PG instance | + +Calling this endpoint again with the same `databaseId` replaces the existing instance. + +### 2. Execute a query against a PGlite database + +**Self-contained (recommended)** — include `sqlContent` directly in the request body; no separate analyze call needed: + +```json +{ + "connectionInfo": { + "type": "pglite", + "databaseId": "my-db", + "sqlContent": "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2));\nINSERT INTO products (name, price) VALUES ('Widget', 9.99);" + }, + "query": "SELECT * FROM products" +} +``` + +**After a prior `analyze-database` call** — omit `sqlContent` to reuse the already-registered instance: + +```json +{ + "connectionInfo": { + "type": "pglite", + "databaseId": "my-db" + }, + "query": "SELECT * FROM products" +} +``` + +Only SELECT statements are accepted; INSERT / UPDATE / DELETE / DDL are rejected with 400. + +## Task Generation + +Call `POST /api/generation/generate` with the following body: + ```json { "connectionInfo": { @@ -86,30 +196,46 @@ Task Generation: Call the GET API: } ``` +The endpoint will automatically register the database schema if it has not been registered yet (auto-analyze). Subsequent calls within the same process skip the expensive round-trip. + What is possible to fill out in the configuration parameters can be found in interfaces.ts/ITaskConfiguration. -Grading: Call the POST API: -`http://localhost:3000/api/grading/grade` -with the following body: +## Grading + +Call `POST /api/grading/grade` with the following body: ```json { "connectionInfo": { "type": "postgres", - "host": "{DatabaseHost}", - "port": "{DatabasePort}", - "username": "{DatabaseUserName}", - "password": "{DatabasePassword}", - "database": "{DatabaseName}", - "schema": "{SchemaName}" + "host": "localhost", + "port": "5432", + "username": "myuser", + "password": "mypass", + "database": "fussballdb", + "schema": "public" }, "gradingRequest": { - "taskId": "{Copy this value from the response of the task generation}", - "studentQuery": "{Enter your query here}" + "taskId": "4f7c66ed-e4e5-422e-8d6f-ef4ee3876d07", + "studentQuery": "SELECT character.kindid, character.genderid FROM public.couple INNER JOIN public.character ON couple.femaleid = character.characterid INNER JOIN public.kind ON character.kindid = kind.kindid WHERE kind.kindid >= 1 AND kind.kindid <= 4 OR character.characterid = 108 ORDER BY couple.femaleid ASC" } } ``` +The endpoint will automatically register the database schema if it has not been registered yet (auto-analyze). Subsequent calls within the same process skip the expensive round-trip. + +## Description + +Call any of the following endpoints under `/api/description/`: + +- `POST /api/description/template` +- `POST /api/description/llm/default` +- `POST /api/description/llm/creative` +- `POST /api/description/llm/multi-step` +- `POST /api/description/hybrid` + +**PostgreSQL** — auto-analyze on first call: + ```json { "connectionInfo": { @@ -121,9 +247,22 @@ with the following body: "database": "fussballdb", "schema": "public" }, - "gradingRequest": { - "taskId": "4f7c66ed-e4e5-422e-8d6f-ef4ee3876d07", - "studentQuery": "SELECT character.kindid, character.genderid FROM public.couple INNER JOIN public.character ON couple.femaleid = character.characterid INNER JOIN public.kind ON character.kindid = kind.kindid WHERE kind.kindid >= 1 AND kind.kindid <= 4 OR character.characterid = 108 ORDER BY couple.femaleid ASC" - } + "query": "SELECT * FROM products WHERE price > 10" } ``` + +**PGlite** — self-contained (include `sqlContent`): + +```json +{ + "connectionInfo": { + "type": "pglite", + "databaseId": "my-db", + "sqlContent": "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2));\nINSERT INTO products VALUES (1, 'Widget', 9.99);" + }, + "query": "SELECT name, price FROM products WHERE price > 5" +} +``` + +The description endpoints only require the registered schema metadata, not a live database connection — making them fully compatible with PGlite. + diff --git a/services/sql-assessment-service/docker-compose.smoke.yml b/services/sql-assessment-service/docker-compose.smoke.yml new file mode 100644 index 0000000..f38d8f6 --- /dev/null +++ b/services/sql-assessment-service/docker-compose.smoke.yml @@ -0,0 +1,28 @@ +services: + postgres: + image: postgres:17-alpine + container_name: sql-smoke-postgres + environment: + POSTGRES_USER: smokeuser + POSTGRES_PASSWORD: smokepass + POSTGRES_DB: smokedb + ports: + - "5499:5432" + volumes: + - ./backup.sql:/docker-entrypoint-initdb.d/backup.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U smokeuser -d smokedb"] + interval: 5s + timeout: 5s + retries: 10 + tmpfs: + - /var/lib/postgresql/data + + app: + image: sql-assessment-service:smoke + container_name: sql-smoke-app + ports: + - "3099:3000" + depends_on: + postgres: + condition: service_healthy diff --git a/services/sql-assessment-service/import-meta-url.js b/services/sql-assessment-service/import-meta-url.js new file mode 100644 index 0000000..4ba3289 --- /dev/null +++ b/services/sql-assessment-service/import-meta-url.js @@ -0,0 +1 @@ +export const import_meta_url = require('url').pathToFileURL(__filename).href; diff --git a/services/sql-assessment-service/package-lock.json b/services/sql-assessment-service/package-lock.json index 2069372..0f07be9 100644 --- a/services/sql-assessment-service/package-lock.json +++ b/services/sql-assessment-service/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@electric-sql/pglite": "^0.4.4", "@eslint/js": "^10.0.1", "@langchain/community": "^1.1.19", "@langchain/core": "^1.1.28", @@ -30,9 +31,11 @@ "@types/swagger-ui-express": "^4.1.8", "@types/uuid": "^10.0.0", "@vitest/coverage-v8": "^4.0.18", + "esbuild": "^0.28.0", "node-sql-parser": "^5.3.5", "openai": "^4.52.0", "pg-mem": "^3.0.13", + "ts-node": "^10.9.2", "typescript": "^5.8.2", "vitest": "^4.0.18" } @@ -42,6 +45,7 @@ "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", "integrity": "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -57,6 +61,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -170,6 +175,7 @@ "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.6.0.tgz", "integrity": "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -185,6 +191,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -215,10 +222,40 @@ "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", "license": "MIT" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@electric-sql/pglite": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.4.tgz", + "integrity": "sha512-g/6CWAJ4XOkObWCWAQ2IReZD8VvsDy3poRHSKvpRR2F96F8WJ3HVbjpso3gN7l0q6QPPgvxSSpl/qo5k8a7mkQ==", + "license": "Apache-2.0" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -233,9 +270,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -250,9 +287,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -267,9 +304,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -284,9 +321,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -301,9 +338,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -318,9 +355,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -335,9 +372,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -352,9 +389,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -369,9 +406,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -386,9 +423,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -403,9 +440,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -420,9 +457,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -437,9 +474,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -454,9 +491,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -471,9 +508,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -488,9 +525,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -505,9 +542,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -522,9 +559,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -539,9 +576,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -556,9 +593,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -573,9 +610,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -590,9 +627,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -607,9 +644,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -624,9 +661,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -641,9 +678,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -701,6 +738,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", @@ -715,6 +753,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -731,13 +770,15 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@eslint/config-helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/core": "^1.1.0" }, @@ -750,6 +791,7 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -782,6 +824,7 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } @@ -791,6 +834,7 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@eslint/core": "^1.1.0", "levn": "^0.4.1" @@ -804,6 +848,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18.0" } @@ -813,6 +858,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" @@ -826,6 +872,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=12.22" }, @@ -839,6 +886,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18" }, @@ -852,6 +900,7 @@ "resolved": "https://registry.npmjs.org/@ibm-cloud/watsonx-ai/-/watsonx-ai-1.7.8.tgz", "integrity": "sha512-UpU/hTHRrCwzkqV1+H/CGbHIGRKty6SX1Aea2CBbyRonsEPIPQtFfhz8FHhs+o6Ca/TCupHNlcLAQUFektZmEQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "^18.0.0", "extend": "3.0.2", @@ -867,6 +916,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -971,7 +1021,7 @@ "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, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -981,7 +1031,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1538,7 +1588,6 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.28.tgz", "integrity": "sha512-6FAGdezEp8zHY92LtnsAiv54KaG41nBdsuukk+R+1484edV20cVOyIc36ANuGKPx0pmYFCBWhCUdO0jxB/zn2Q==", "license": "MIT", - "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -2123,6 +2172,35 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT", + "peer": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/body-parser": { @@ -2162,6 +2240,7 @@ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/ms": "*" } @@ -2177,7 +2256,8 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/estree": { "version": "1.0.8", @@ -2227,7 +2307,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/node": { "version": "20.12.7", @@ -2312,7 +2393,8 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/uuid": { "version": "10.0.0", @@ -2353,7 +2435,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -2768,7 +2849,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2781,10 +2861,24 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -2802,6 +2896,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2852,6 +2947,13 @@ "node": ">= 6.0.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3034,7 +3136,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/bytes": { "version": "3.1.2", @@ -3234,6 +3337,13 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3290,7 +3400,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -3347,6 +3458,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -3371,7 +3492,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=12" }, @@ -3404,6 +3524,7 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -3482,9 +3603,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3495,32 +3616,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -3543,6 +3664,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -3611,6 +3733,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", @@ -3641,6 +3764,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -3658,6 +3782,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -3666,13 +3791,15 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/espree": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", @@ -3690,6 +3817,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -3702,6 +3830,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -3714,6 +3843,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -3766,6 +3896,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -3785,7 +3916,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -3831,25 +3961,29 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fdir": { "version": "6.5.0", @@ -3873,6 +4007,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -3885,6 +4020,7 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", "license": "MIT", + "peer": true, "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", @@ -3920,6 +4056,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3945,6 +4082,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "license": "MIT", + "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3957,7 +4095,8 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -3970,6 +4109,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" }, @@ -4171,6 +4311,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4353,6 +4494,7 @@ "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.4.8.tgz", "integrity": "sha512-tLMlZv13cV6S1UPj/bhv8XfV9Z1BDDs/4DxHKWnCw7QlJMzmGdHLPX386x9nrFMQMPZ48eAH+Thsa06tzUZkaA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/debug": "^4.1.12", "@types/node": "^18.19.80", @@ -4379,6 +4521,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -4388,6 +4531,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -4404,7 +4548,8 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -4443,7 +4588,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } @@ -4460,6 +4604,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.19" } @@ -4507,6 +4652,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4525,6 +4671,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", + "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -4575,7 +4722,8 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4636,7 +4784,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -4673,13 +4820,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-stable-stringify": { "version": "1.3.0", @@ -4705,7 +4854,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jsonify": { "version": "0.0.1", @@ -4731,6 +4881,7 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", + "peer": true, "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", @@ -4752,13 +4903,15 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", + "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -4770,6 +4923,7 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", + "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -4780,6 +4934,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "license": "MIT", + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -4842,6 +4997,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -4855,6 +5011,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -4876,13 +5033,15 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -4895,25 +5054,29 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -4925,7 +5088,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lru-cache": { "version": "6.0.0", @@ -4978,6 +5142,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, + "license": "ISC" + }, "node_modules/math-expression-evaluator": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-2.0.7.tgz", @@ -5304,7 +5475,6 @@ "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -5343,14 +5513,14 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5377,6 +5547,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "license": "MIT", + "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -5392,6 +5563,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -5465,6 +5637,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5527,6 +5700,7 @@ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -5540,7 +5714,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.19.0.tgz", "integrity": "sha512-QIcLGi508BAHkQ3pJNptsFz5WQMlpGbuBGBaIaXsWK8mel2kQ/rThYI+DbgjUvZrIr7MiuEuc9LcChJoEZK1xQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.12.0", @@ -5707,7 +5880,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5720,6 +5892,7 @@ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -5738,6 +5911,7 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -5827,6 +6001,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -5836,6 +6011,7 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6.0" } @@ -5857,13 +6033,15 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", "license": "MIT", + "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -5876,6 +6054,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -5899,7 +6078,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/railroad-diagrams": { "version": "1.0.0", @@ -5951,6 +6131,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "license": "MIT", + "peer": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -5967,6 +6148,7 @@ "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^4.7.0" }, @@ -5997,7 +6179,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ret": { "version": "0.1.15", @@ -6014,6 +6197,7 @@ "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=10.7.0" }, @@ -6382,6 +6566,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -6445,6 +6630,7 @@ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", "license": "MIT", + "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" @@ -6615,6 +6801,7 @@ "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", "license": "MIT", + "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -6632,6 +6819,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -6660,6 +6848,50 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6671,6 +6903,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -6710,7 +6943,6 @@ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", "license": "MIT", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", @@ -6885,7 +7117,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6941,6 +7172,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 4.0.0" } @@ -6959,6 +7191,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -6968,6 +7201,7 @@ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "license": "MIT", + "peer": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -6995,6 +7229,13 @@ "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", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/validator": { "version": "13.15.26", "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", @@ -7019,7 +7260,6 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -7092,6 +7332,448 @@ } } }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/vitest/node_modules/@vitest/mocker": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", @@ -7119,6 +7801,48 @@ } } }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/vitest/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -7140,7 +7864,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7293,6 +8016,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7379,6 +8103,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -7462,11 +8187,22 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -7518,6 +8254,7 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.25 || ^4" } diff --git a/services/sql-assessment-service/package.json b/services/sql-assessment-service/package.json index 45ee0ca..87f54f8 100644 --- a/services/sql-assessment-service/package.json +++ b/services/sql-assessment-service/package.json @@ -6,22 +6,27 @@ "@types/swagger-ui-express": "^4.1.8", "@types/uuid": "^10.0.0", "@vitest/coverage-v8": "^4.0.18", + "esbuild": "^0.28.0", "node-sql-parser": "^5.3.5", "openai": "^4.52.0", "pg-mem": "^3.0.13", + "ts-node": "^10.9.2", "typescript": "^5.8.2", "vitest": "^4.0.18" }, "scripts": { - "build": "tsc -p tsconfig.json", + "build": "esbuild src/index.ts src/cli/index.ts --bundle --platform=node --target=node20 --format=cjs --outdir=build --sourcemap --inject:./import-meta-url.js --define:import.meta.url=import_meta_url --external:@electric-sql/pglite --external:swagger-ui-dist", + "build:tsc": "tsc -p tsconfig.json", "dev": "npx ts-node src/index.ts", "generate-openapi": "npx ts-node src/generate-openapi.ts", "lint": "npx eslint ./src", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "cli": "node build/cli/index.js" }, "dependencies": { + "@electric-sql/pglite": "^0.4.4", "@eslint/js": "^10.0.1", "@langchain/community": "^1.1.19", "@langchain/core": "^1.1.28", diff --git a/services/sql-assessment-service/scripts/smoke-test.sh b/services/sql-assessment-service/scripts/smoke-test.sh new file mode 100755 index 0000000..4e5a73e --- /dev/null +++ b/services/sql-assessment-service/scripts/smoke-test.sh @@ -0,0 +1,472 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- +# smoke-test.sh — End-to-end smoke tests for sql-assessment-service +# +# Usage: +# ./scripts/smoke-test.sh [BASE_URL] +# +# Environment variables: +# BASE_URL Service base URL (default: http://localhost:3000) +# +# PGlite path (no external DB needed): +# PGLITE_DB_ID databaseId to use (default: smoke-db) +# +# Init-SQL-file feature (optional — tests the PGLITE_INIT_SQL_FILE / --init-sql-file feature): +# SMOKE_TEST_INIT_SQL Set to '1' to enable (server must be started with PGLITE_INIT_SQL_FILE +# pointing to a valid .sql file containing at least one table) +# SMOKE_INIT_SQL_TABLE A table name that exists in the configured init SQL file +# (default: products) — used to verify the file was actually loaded +# +# PostgreSQL path (optional — skipped if PG_HOST is not set): +# PG_HOST Postgres host +# PG_PORT Postgres port (default: 5432) +# PG_USER Postgres username +# PG_PASSWORD Postgres password +# PG_DATABASE Postgres database name +# PG_SCHEMA Postgres schema (default: public) +# +# Exit code: 0 = all tests passed, 1 = one or more failures +# --------------------------------------------------------------------------- +set -euo pipefail + +BASE_URL="${1:-${BASE_URL:-http://localhost:3000}}" +PGLITE_DB_ID="${PGLITE_DB_ID:-smoke-db}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLI_PATH="$(dirname "$SCRIPT_DIR")/build/cli/index.js" + +PASS=0 +FAIL=0 + +# ---- helpers --------------------------------------------------------------- + +green() { printf '\033[0;32m%s\033[0m\n' "$*"; } +red() { printf '\033[0;31m%s\033[0m\n' "$*"; } + +assert() { + local label="$1" + local expected_status="$2" + local actual_status="$3" + local body="$4" + + if [[ "$actual_status" == "$expected_status" ]]; then + green " PASS [$actual_status] $label" + (( PASS++ )) || true + else + red " FAIL [$actual_status != $expected_status] $label" + red " body: $body" + (( FAIL++ )) || true + fi +} + +# Sends a POST request, prints body+status, calls assert. +# Usage: post LABEL EXPECTED_STATUS URL JSON_BODY +post() { + local label="$1" expected="$2" url="$3" body="$4" + local response + response=$(curl -s -w '\n%{http_code}' -X POST "$url" \ + -H 'Content-Type: application/json' \ + -d "$body") + local http_body http_status + http_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -n 1) + assert "$label" "$expected" "$http_status" "$http_body" +} + +# Like post, but also asserts that the response body contains NEEDLE. +# Usage: post_body_contains LABEL EXPECTED_STATUS URL JSON_BODY NEEDLE +post_body_contains() { + local label="$1" expected="$2" url="$3" body="$4" needle="$5" + local response + response=$(curl -s -w '\n%{http_code}' -X POST "$url" \ + -H 'Content-Type: application/json' \ + -d "$body") + local http_body http_status + http_body=$(echo "$response" | head -n -1) + http_status=$(echo "$response" | tail -n 1) + assert "$label (HTTP $expected)" "$expected" "$http_status" "$http_body" + if [[ "$http_status" == "$expected" ]]; then + if echo "$http_body" | grep -q "$needle"; then + green " PASS body contains \"$needle\"" + (( PASS++ )) || true + else + red " FAIL body does not contain \"$needle\"" + red " body: $http_body" + (( FAIL++ )) || true + fi + fi +} + +# Runs the CLI in-process; checks exit code. +# Usage: cli_invoke LABEL EXPECTED_EXIT [CLI_ARGS...] +cli_invoke() { + local label="$1" expected_exit="$2" + shift 2 + local output exit_code=0 + output=$(node "$CLI_PATH" "$@" 2>&1) || exit_code=$? + if [[ "$exit_code" -eq "$expected_exit" ]]; then + green " PASS [exit=$exit_code] $label" + (( PASS++ )) || true + else + red " FAIL [exit=$exit_code != $expected_exit] $label" + red " output: $output" + (( FAIL++ )) || true + fi +} + +# Like cli_invoke but also asserts that output contains NEEDLE. +# Usage: cli_invoke_contains LABEL EXPECTED_EXIT NEEDLE [CLI_ARGS...] +cli_invoke_contains() { + local label="$1" expected_exit="$2" needle="$3" + shift 3 + local output exit_code=0 + output=$(node "$CLI_PATH" "$@" 2>&1) || exit_code=$? + if [[ "$exit_code" -eq "$expected_exit" ]]; then + green " PASS [exit=$exit_code] $label" + (( PASS++ )) || true + else + red " FAIL [exit=$exit_code != $expected_exit] $label" + red " output: $output" + (( FAIL++ )) || true + return + fi + if echo "$output" | grep -q "$needle"; then + green " PASS output contains \"$needle\"" + (( PASS++ )) || true + else + red " FAIL output does not contain \"$needle\"" + red " output: $output" + (( FAIL++ )) || true + fi +} + +# ---- PGlite tests ---------------------------------------------------------- + +pglite_tests() { + echo "" + echo "── PGlite path (${BASE_URL}) ──────────────────────────────────────────" + + local DDL + DDL='CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2)); +CREATE TABLE orders (id SERIAL PRIMARY KEY, product_id INTEGER REFERENCES products(id), quantity INTEGER NOT NULL); +INSERT INTO products (name, price) VALUES ('\''Widget'\'', 9.99), ('\''Gadget'\'', 19.99); +INSERT INTO orders (product_id, quantity) VALUES (1, 5), (2, 3);' + + # 1. Register DB + post "analyze-database — valid DDL" "200" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --arg id "$PGLITE_DB_ID" --arg sql "$DDL" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql}}')" + + # 2. Simple SELECT + post "execute — SELECT *" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT * FROM products ORDER BY id"}')" + + # 3. JOIN + post "execute — JOIN" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT p.name, o.quantity FROM products p JOIN orders o ON p.id = o.product_id ORDER BY p.id"}')" + + # 4. WHERE + filter + post "execute — WHERE no results" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT * FROM products WHERE name = '\''NoSuchProduct'\''"}')" + + # 5. INSERT rejected + post "execute — INSERT (expect 400 NON_SELECT)" "400" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"INSERT INTO products (name, price) VALUES ('"'"'X'"'"', 1.0)"}')" + + # 6. DELETE rejected + post "execute — DELETE (expect 400 NON_SELECT)" "400" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"DELETE FROM products"}')" + + # 7. Non-existent table → 500 + post "execute — unknown table (expect 500)" "500" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT * FROM no_such_table"}')" + + # 8. Missing sqlContent → 400 when no init file; 200 when init file is configured + if [[ -z "${SMOKE_TEST_INIT_SQL:-}" ]]; then + post "analyze-database — missing sqlContent (expect 400)" "400" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id}}')" + else + post "analyze-database — missing sqlContent (init-file provides it, expect 200)" "200" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id}}')" + fi + + # 9. Missing databaseId → 400 + post "analyze-database — missing databaseId (expect 400)" "400" \ + "${BASE_URL}/api/database/analyze-database" \ + '{"connectionInfo":{"type":"pglite","sqlContent":"SELECT 1"}}' + + # 10. Unregistered databaseId → 400 (only when no init-SQL file is configured, + # because an init file auto-initialises any unknown databaseId) + if [[ -z "${SMOKE_TEST_INIT_SQL:-}" ]]; then + post "execute — unregistered databaseId (expect 400)" "400" \ + "${BASE_URL}/api/query/execute" \ + '{"connectionInfo":{"type":"pglite","databaseId":"__not_registered__"},"query":"SELECT 1"}' + else + green " SKIP execute — unregistered databaseId (init-SQL-file auto-initialises any databaseId)" + fi + + # 11. Re-register with different schema (replace) + post "analyze-database — replace existing instance" "200" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:"CREATE TABLE categories (id SERIAL PRIMARY KEY, label TEXT NOT NULL);"}}')" + + post "execute — query new schema after replace" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$PGLITE_DB_ID" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT * FROM categories"}')" +} + +# ---- Init-SQL-file tests (optional) --------------------------------------- + +pglite_init_sql_file_tests() { + if [[ -z "${SMOKE_TEST_INIT_SQL:-}" ]]; then + echo "" + echo "── PGlite init-SQL-file tests — skipped (set SMOKE_TEST_INIT_SQL=1 to enable) ─" + return + fi + + echo "" + echo "── PGlite init-SQL-file feature ────────────────────────────────────────" + echo " (server must be started with PGLITE_INIT_SQL_FILE set to a valid file)" + + local INIT_TABLE="${SMOKE_INIT_SQL_TABLE:-products}" + local NEW_DB="smoke-init-${RANDOM}" + local OVERRIDE_DB="smoke-override-${RANDOM}" + + # 1. Fresh databaseId without sqlContent — init file provides the schema + post "init-sql-file — fresh databaseId, no sqlContent (expect 200)" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$NEW_DB" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:"SELECT 1 AS ping"}')" + + # 2. The init-file schema is actually usable (query a known table) + post "init-sql-file — query table from init file (expect 200)" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$NEW_DB" --arg tbl "$INIT_TABLE" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:("SELECT * FROM "+$tbl+" LIMIT 1")}')" + + # 3. Explicit sqlContent overrides the init file + post "init-sql-file — explicit sqlContent takes priority (expect 200)" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --arg id "$OVERRIDE_DB" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:"CREATE TABLE override_tbl (id SERIAL PRIMARY KEY);"},query:"SELECT * FROM override_tbl"}')" + + # 4. analyze-database without sqlContent — init file counts as the schema + post "init-sql-file — analyze-database without sqlContent (expect 200)" "200" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --arg id "smoke-analyze-${RANDOM}" \ + '{connectionInfo:{type:"pglite",databaseId:$id}}')" +} + +# ---- CLI tests ------------------------------------------------------------ + +cli_tests() { + echo "" + echo "── CLI tests (build/cli/index.js) ──────────────────────────────────────" + + if [[ ! -f "$CLI_PATH" ]]; then + red " SKIP CLI binary not found at ${CLI_PATH} — run 'npm run build'" + return + fi + + local CLI_DB_ID="cli-smoke-db" + + local DDL + DDL='CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2)); +CREATE TABLE orders (id SERIAL PRIMARY KEY, product_id INTEGER REFERENCES products(id), quantity INTEGER NOT NULL); +INSERT INTO products (name, price) VALUES ('\''Widget'\'', 9.99), ('\''Gadget'\'', 19.99); +INSERT INTO orders (product_id, quantity) VALUES (1, 5), (2, 3);' + + local DDL_PRODUCTS_ONLY + DDL_PRODUCTS_ONLY='CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC(10,2)); +INSERT INTO products (name, price) VALUES ('\''Widget'\'', 9.99), ('\''Gadget'\'', 19.99);' + + # 1. --list shows available commands + cli_invoke_contains "--list shows commands" 0 "database:analyze-database" \ + --list + + # 2. --help shows usage text + cli_invoke_contains "--help shows usage" 0 "Usage:" \ + --help + + # 3. Unknown command → exit 1 + cli_invoke "unknown command (→ exit 1)" 1 \ + no:such:command '{"foo":"bar"}' + + # 4. analyze-database — valid DDL via inline JSON arg + cli_invoke "database:analyze-database — valid DDL" 0 \ + database:analyze-database \ + "$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql}}')" + + # 5. query:execute — SELECT * (sqlContent provided inline so process is self-contained) + cli_invoke_contains "query:execute — SELECT * FROM products" 0 "Widget" \ + query:execute \ + "$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL_PRODUCTS_ONLY" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:"SELECT * FROM products ORDER BY id"}')" + + # 6. query:execute — JOIN (both tables in sqlContent) + cli_invoke "query:execute — JOIN products + orders" 0 \ + query:execute \ + "$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:"SELECT p.name, o.quantity FROM products p JOIN orders o ON p.id = o.product_id ORDER BY p.id"}')" + + # 7. INSERT rejected → exit 1 + cli_invoke "query:execute — INSERT (NON_SELECT → exit 1)" 1 \ + query:execute \ + "$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL_PRODUCTS_ONLY" --arg q "INSERT INTO products (name, price) VALUES ('X', 1.0)" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:$q}')" + + # 8. Unknown table → exit 1 + cli_invoke "query:execute — unknown table (→ exit 1)" 1 \ + query:execute \ + "$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL_PRODUCTS_ONLY" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:"SELECT * FROM no_such_table"}')" + + # 9. Body via stdin pipe (auto-detected when stdin is not a TTY) + local stdin_body stdin_out stdin_exit=0 + stdin_body=$(jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL_PRODUCTS_ONLY" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:"SELECT name FROM products WHERE price > 15 ORDER BY id"}') + stdin_out=$(echo "$stdin_body" | node "$CLI_PATH" query:execute 2>&1) || stdin_exit=$? + if [[ "$stdin_exit" -eq 0 ]]; then + green " PASS [exit=0] query:execute — body via stdin pipe" + (( PASS++ )) || true + if echo "$stdin_out" | grep -q "Gadget"; then + green " PASS output contains \"Gadget\"" + (( PASS++ )) || true + else + red " FAIL output does not contain \"Gadget\"" + red " output: $stdin_out" + (( FAIL++ )) || true + fi + else + red " FAIL [exit=$stdin_exit != 0] query:execute — body via stdin pipe" + red " output: $stdin_out" + (( FAIL++ )) || true + fi + + # 10. Body via -f file + local tmp_body + tmp_body=$(mktemp /tmp/cli-smoke-XXXXXX.json) + jq -n --arg id "$CLI_DB_ID" --arg sql "$DDL_PRODUCTS_ONLY" \ + '{connectionInfo:{type:"pglite",databaseId:$id,sqlContent:$sql},query:"SELECT COUNT(*) AS cnt FROM products"}' \ + > "$tmp_body" + cli_invoke_contains "query:execute — body via -f file" 0 "cnt" \ + query:execute -f "$tmp_body" + rm -f "$tmp_body" + + # 11. Missing databaseId → exit 1 + cli_invoke "database:analyze-database — missing databaseId (→ exit 1)" 1 \ + database:analyze-database \ + '{"connectionInfo":{"type":"pglite","sqlContent":"SELECT 1"}}' + + # 12. --init-sql-file flag: command must come first, flag comes after the JSON body + if [[ -n "${SMOKE_TEST_INIT_SQL:-}" && -n "${PGLITE_INIT_SQL_FILE:-}" ]]; then + local INIT_TABLE="${SMOKE_INIT_SQL_TABLE:-products}" + local FRESH_DB="cli-init-${RANDOM}" + local INIT_QUERY="SELECT * FROM ${INIT_TABLE} LIMIT 1" + cli_invoke_contains \ + "query:execute — --init-sql-file, query table from init file" 0 "rows" \ + query:execute \ + "$(jq -n --arg id "$FRESH_DB" --arg q "$INIT_QUERY" \ + '{connectionInfo:{type:"pglite",databaseId:$id},query:$q}')" \ + --init-sql-file "$PGLITE_INIT_SQL_FILE" + elif [[ -n "${SMOKE_TEST_INIT_SQL:-}" ]]; then + echo " SKIP CLI --init-sql-file test (set PGLITE_INIT_SQL_FILE to enable)" + fi +} + +# ---- PostgreSQL tests (optional) ------------------------------------------- + +postgres_tests() { + if [[ -z "${PG_HOST:-}" ]]; then + echo "" + echo "── PostgreSQL path — skipped (PG_HOST not set) ────────────────────────" + return + fi + + echo "" + echo "── PostgreSQL path (${PG_HOST}:${PG_PORT:-5432}) ─────────────────────────" + + local CONN + CONN=$(jq -n \ + --arg host "${PG_HOST}" \ + --argjson port "${PG_PORT:-5432}" \ + --arg user "${PG_USER:-postgres}" \ + --arg pass "${PG_PASSWORD:-}" \ + --arg db "${PG_DATABASE:-postgres}" \ + --arg schema "${PG_SCHEMA:-public}" \ + '{type:"postgres",host:$host,port:$port,username:$user,password:$pass,database:$db,schema:$schema}') + + post "analyze-database — valid PG connection" "200" \ + "${BASE_URL}/api/database/analyze-database" \ + "$(jq -n --argjson conn "$CONN" '{connectionInfo:$conn}')" + + post "execute — SELECT 1" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --argjson conn "$CONN" '{connectionInfo:$conn,query:"SELECT 1 AS value"}')" + + # Prove we are actually talking to PostgreSQL (pg-specific system function) + post_body_contains "execute — version() contains 'PostgreSQL'" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --argjson conn "$CONN" '{connectionInfo:$conn,query:"SELECT version() AS ver"}')" \ + "PostgreSQL" + + # Prove backup.sql was loaded — pg_catalog.pg_tables lists aladin-owned tables + post_body_contains "execute — pg_tables shows backup.sql tables (angest)" "200" \ + "${BASE_URL}/api/query/execute" \ + "$(jq -n --argjson conn "$CONN" \ + '{connectionInfo:$conn,query:"SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='"'"'public'"'"' ORDER BY tablename"}')" \ + "angest" +} + +# ---- main ------------------------------------------------------------------ + +echo "sql-assessment-service smoke tests" +echo " BASE_URL: ${BASE_URL}" + +# Wait for service to be reachable (up to 10 s) +echo -n " Waiting for service..." +for i in $(seq 1 10); do + if curl -sf "${BASE_URL}/api/health" >/dev/null 2>&1 || \ + curl -sf -o /dev/null -w '%{http_code}' "${BASE_URL}/" 2>/dev/null | grep -qE '^[0-9]'; then + break + fi + # Try any endpoint — a 404 still means the server is up + code=$(curl -s -o /dev/null -w '%{http_code}' "${BASE_URL}/api/query/execute" 2>/dev/null || true) + if [[ "$code" =~ ^[0-9]+$ && "$code" != "000" ]]; then + break + fi + echo -n "." + sleep 1 +done +echo " OK" + +pglite_tests +pglite_init_sql_file_tests +postgres_tests +cli_tests + +echo "" +echo "────────────────────────────────────────────────" +echo "Results: ${PASS} passed, ${FAIL} failed" +echo "────────────────────────────────────────────────" + +[[ "$FAIL" -eq 0 ]] diff --git a/services/sql-assessment-service/services/postgres/postgres.Dockerfile b/services/sql-assessment-service/services/postgres/postgres.Dockerfile deleted file mode 100644 index 6f67714..0000000 --- a/services/sql-assessment-service/services/postgres/postgres.Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM postgres:13 \ No newline at end of file diff --git a/services/sql-assessment-service/src/api/rest-api.ts b/services/sql-assessment-service/src/api/rest-api.ts index de39693..2a9fa62 100644 --- a/services/sql-assessment-service/src/api/rest-api.ts +++ b/services/sql-assessment-service/src/api/rest-api.ts @@ -8,7 +8,6 @@ import { GradingController } from '../grading/grading-controller'; import { DescriptionController } from '../generation/description/description-controller'; import { QueryExecutionController } from '../query/query-execution-controller'; import { getSwaggerSpec } from '../openapi'; -import dotenv from 'dotenv'; const api = express(); api.use(bodyParser.json()); @@ -34,8 +33,6 @@ export function registerControllers( } export function startRestApi() { - dotenv.config(); - const PORT = process.env.PORT || 3000; api.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); diff --git a/services/sql-assessment-service/src/bootstrap.ts b/services/sql-assessment-service/src/bootstrap.ts new file mode 100644 index 0000000..2585322 --- /dev/null +++ b/services/sql-assessment-service/src/bootstrap.ts @@ -0,0 +1,108 @@ +import 'reflect-metadata'; +import { DatabaseAnalyzer } from './database/database-analyzer'; +import { DatabaseController } from './database/database-controller'; +import { DatabaseService } from './database/database-service'; +import { SQLQueryGradingService } from './grading/query-grading-service'; +import { GradingController } from './grading/grading-controller'; +import { ResultSetComparator } from './grading/result-set-comparator'; +import { ASTComparator } from './grading/comparators/ast-comparator'; +import { ExecutionPlanComparator } from './grading/comparators/execution-plan-comparator'; +import { ExecutionPlanParser } from './grading/execution-plan-parser'; +import { JoinComparator } from './grading/join-comparator'; +import { FeedbackAssembler } from './grading/feedback/feedback-assembler'; +import { GradeCalculator } from './grading/grading/grade-calculator'; +import { SQLQueryGenerationService } from './generation/query/sql-query-generation-service'; +import { SelectQueryGenerationDirector } from './generation/query/select-query-generation-director'; +import { TemplateTaskDescriptionGenerationEngine } from './generation/description/template-task-description-generation-engine'; +import { LLMTaskDescriptionGenerationEngine } from './generation/description/llm-task-description-generation-engine'; +import { TaskDescriptionGenerationService } from './generation/description/task-description-generation-service'; +import { TaskGenerationController } from './generation/task-generation-controller'; +import { DescriptionController } from './generation/description/description-controller'; +import { QueryExecutionController } from './query/query-execution-controller'; +import { QueryExecutionService } from './query/query-execution-service'; + +export interface CreateControllersOptions { + /** Path to a SQL file used as PGlite initialisation SQL when `sqlContent` + * is not provided in the request. Falls back to `PGLITE_INIT_SQL_FILE` + * environment variable when omitted. */ + pgliteInitSqlFile?: string; +} + +export function createControllers(options: CreateControllersOptions = {}) { + const pgliteInitSqlFile = + options.pgliteInitSqlFile ?? process.env.PGLITE_INIT_SQL_FILE; + + const databaseAnalyzer = new DatabaseAnalyzer(); + const databaseService = new DatabaseService( + databaseAnalyzer, + pgliteInitSqlFile, + ); + const connectionController = new DatabaseController( + databaseAnalyzer, + databaseService, + ); + + const selectQueryGenerator = new SQLQueryGenerationService( + new SelectQueryGenerationDirector(), + ); + const templateTaskDescriptionGenerationEngine = + new TemplateTaskDescriptionGenerationEngine(); + const llmTaskDescriptionGenerationEngine = process.env.OPENAI_API_KEY + ? new LLMTaskDescriptionGenerationEngine() + : undefined; + const taskDescriptionGenerationService = new TaskDescriptionGenerationService( + llmTaskDescriptionGenerationEngine, + templateTaskDescriptionGenerationEngine, + ); + const taskGenerationController = new TaskGenerationController( + selectQueryGenerator, + taskDescriptionGenerationService, + databaseService, + ); + + // ── Grading dependencies ────────────────────────────────────────────────── + const joinComparator = new JoinComparator(); + const resultSetComparator = new ResultSetComparator(); + const astComparator = new ASTComparator(joinComparator); + const executionPlanComparator = new ExecutionPlanComparator( + new ExecutionPlanParser(), + joinComparator, + ); + const gradeCalculator = new GradeCalculator(); + const feedbackAssembler = new FeedbackAssembler(); + + const queryGradingService = new SQLQueryGradingService( + resultSetComparator, + astComparator, + executionPlanComparator, + gradeCalculator, + feedbackAssembler, + ); + const gradingController = new GradingController( + queryGradingService, + taskDescriptionGenerationService, + resultSetComparator, + astComparator, + executionPlanComparator, + undefined, + databaseService, + ); + + const descriptionController = new DescriptionController( + taskDescriptionGenerationService, + databaseService, + ); + const queryExecutionService = new QueryExecutionService(); + const queryExecutionController = new QueryExecutionController( + queryExecutionService, + databaseService, + ); + + return { + connectionController, + taskGenerationController, + gradingController, + descriptionController, + queryExecutionController, + }; +} diff --git a/services/sql-assessment-service/src/cli/express-cli-adapter.ts b/services/sql-assessment-service/src/cli/express-cli-adapter.ts new file mode 100644 index 0000000..f8dd32a --- /dev/null +++ b/services/sql-assessment-service/src/cli/express-cli-adapter.ts @@ -0,0 +1,107 @@ +import { Router } from 'express'; + +export interface CLIRoute { + command: string; + method: string; + path: string; + handler: (req: any, res: any) => any; +} + +export interface CLIResponse { + statusCode: number; + data: unknown; +} + +/** + * Walk an Express Router's internal layer stack and extract every registered + * route. The command name is derived from the full path by stripping the + * leading `/api/` prefix and replacing `/` with `:`. + * + * Example: `/api/grading/compare/result-set` → `grading:compare:result-set` + */ +export function extractRoutes(router: Router, basePath: string): CLIRoute[] { + const routes: CLIRoute[] = []; + for (const layer of (router as any).stack ?? []) { + if (!layer.route) continue; + for (const method of Object.keys(layer.route.methods)) { + const fullPath: string = basePath + layer.route.path; + routes.push({ + command: fullPath.replace(/^\/api\//, '').replace(/\//g, ':'), + method: method.toUpperCase(), + path: fullPath, + handler: layer.route.stack[layer.route.stack.length - 1].handle, + }); + } + } + return routes; +} + +/** + * Invoke an Express route handler in-process with a mock req / res, + * capturing the JSON response it produces. + * + * Handles three failure modes: + * 1. The handler returns a rejected promise (normal async throw). + * 2. The handler throws synchronously. + * 3. The route's arrow wrapper does not `return` the inner async result, + * causing an unhandledRejection (common Express pattern). + */ +export function invokeHandler( + handler: CLIRoute['handler'], + body: unknown, +): Promise { + return new Promise((resolve) => { + let settled = false; + const settle = (statusCode: number, data: unknown) => { + if (!settled) { + settled = true; + process.removeListener('unhandledRejection', onUnhandled); + resolve({ statusCode, data }); + } + }; + + // Catch fire-and-forget async handlers whose promise is not returned + // by the wrapping arrow function (e.g. `(req, res) => { this.handle(req, res); }`). + const onUnhandled = (err: unknown) => { + const message = err instanceof Error ? err.message : String(err); + settle(500, { message }); + }; + process.on('unhandledRejection', onUnhandled); + + const req = { body }; + let statusCode = 200; + const res = { + status(code: number) { + statusCode = code; + return res; + }, + json(data: unknown) { + settle(statusCode, data); + return res; + }, + send(data: unknown) { + settle(statusCode, data); + return res; + }, + }; + + try { + const result = handler(req, res); + if (result && typeof result.then === 'function') { + result + .then(() => { + // Handler resolved without calling res.json/send — + // clean up the listener (tests that never reply will + // hang, which is the expected vitest behaviour). + process.removeListener('unhandledRejection', onUnhandled); + }) + .catch((err: Error) => { + settle(500, { message: err.message }); + }); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + settle(500, { message }); + } + }); +} diff --git a/services/sql-assessment-service/src/cli/index.ts b/services/sql-assessment-service/src/cli/index.ts new file mode 100644 index 0000000..a04b2ac --- /dev/null +++ b/services/sql-assessment-service/src/cli/index.ts @@ -0,0 +1,183 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { createControllers } from '../bootstrap'; +import { CLIRoute, extractRoutes, invokeHandler } from './express-cli-adapter'; +import * as fs from 'fs'; +import * as path from 'path'; + +// --------------------------------------------------------------------------- +// Route table builder +// --------------------------------------------------------------------------- + +/** + * Build the full route table by walking every controller's Express Router. + * The mount prefixes mirror those in rest-api.ts registerControllers(). + */ +function buildRouteTable(initSqlFile?: string): CLIRoute[] { + const c = createControllers({ pgliteInitSqlFile: initSqlFile }); + return [ + ...extractRoutes(c.connectionController.router, '/api/database'), + ...extractRoutes(c.taskGenerationController.router, '/api/generation'), + ...extractRoutes(c.gradingController.router, '/api/grading'), + ...extractRoutes(c.descriptionController.router, '/api/description'), + ...extractRoutes(c.queryExecutionController.router, '/api/query'), + ]; +} + +function printHelp(routes: CLIRoute[]) { + const lines = [ + 'SQL Assessment Service CLI', + '', + 'Usage: sql-assess [options]', + '', + 'Options:', + ' -f, --file Read JSON body from file', + ' --stdin Read JSON body from stdin', + ' --list List all commands', + ' --init-sql-file SQL file used to initialise a PGlite database', + ' when sqlContent is not in the request body.', + ' Overrides the PGLITE_INIT_SQL_FILE env var.', + ' -h, --help Show help', + '', + 'Commands:', + ...routes.map((r) => ` ${r.command.padEnd(40)} ${r.method} ${r.path}`), + '', + 'Body can also be provided as a positional JSON string argument,', + 'or piped via stdin (auto-detected when not a TTY).', + ]; + console.log(lines.join('\n')); +} + +async function readStdin(): Promise { + return new Promise((resolve, reject) => { + let data = ''; + process.stdin.setEncoding('utf-8'); + process.stdin.on('data', (chunk) => (data += chunk)); + process.stdin.on('end', () => resolve(data)); + process.stdin.on('error', reject); + }); +} + +/** + * Returns the index of the -f / --file flag in args, giving -f priority over + * --file when both are present. Returns -1 when neither flag is present. + */ +export function resolveFileFlag(args: string[]): number { + const shortIdx = args.indexOf('-f'); + if (shortIdx !== -1) return shortIdx; + return args.indexOf('--file'); +} + +/** + * Resolves the --init-sql-file flag value from the raw CLI args. + * Throws if the flag is present but not followed by a non-flag argument, + * so the caller can surface a clear error instead of silently ignoring it. + */ +export function resolveInitSqlFile(args: string[]): string | undefined { + const idx = args.indexOf('--init-sql-file'); + if (idx === -1) return undefined; + const value = args[idx + 1]; + if (!value || value.startsWith('-')) { + throw new Error('--init-sql-file requires a path argument.'); + } + return path.resolve(value); +} + +/** Flags that consume the following token as their value. */ +const FLAGS_WITH_VALUES = new Set(['--init-sql-file', '-f', '--file']); + +/** + * Scans args (from index 1, after the command) and returns the first token + * that is neither a flag nor the value of a flag that takes a value. + * Returns undefined when no such positional argument exists. + */ +export function resolvePositionalBody(args: string[]): string | undefined { + let i = 1; // skip args[0] (the command) + while (i < args.length) { + const arg = args[i]; + if (FLAGS_WITH_VALUES.has(arg)) { + i += 2; // skip flag + its value + } else if (arg.startsWith('-')) { + i += 1; // skip boolean flag + } else { + return arg; + } + } + return undefined; +} + +async function main() { + const args = process.argv.slice(2); + + // ── Resolve --init-sql-file early so it feeds buildRouteTable ──────────── + let initSqlFile: string | undefined; + try { + initSqlFile = resolveInitSqlFile(args); + } catch (e) { + console.error((e as Error).message); + process.exit(1); + } + + if ( + args.includes('--list') || + args.includes('--help') || + args.includes('-h') || + args.length === 0 + ) { + const routes = buildRouteTable(initSqlFile); + if (args.includes('--list')) { + for (const r of routes) + console.log(`${r.command}\t${r.method}\t${r.path}`); + } else { + printHelp(routes); + } + return; + } + + const command = args[0]; + const routes = buildRouteTable(initSqlFile); + const route = routes.find((r) => r.command === command); + + if (!route) { + console.error(`Unknown command: ${command}`); + console.error('Run with --list to see available commands.'); + process.exit(1); + } + + // ── Resolve request body ───────────────────────────────────────────── + let body: unknown; + const fileIdx = resolveFileFlag(args); + + if (args.includes('--stdin')) { + body = JSON.parse(await readStdin()); + } else if (fileIdx !== -1) { + const filePath = args[fileIdx + 1]; + if (!filePath || filePath.startsWith('-')) { + console.error('-f/--file requires a path argument.'); + process.exit(1); + } + body = JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf-8')); + } else { + const positional = resolvePositionalBody(args); + if (positional !== undefined) { + body = JSON.parse(positional); + } else if (!process.stdin.isTTY) { + body = JSON.parse(await readStdin()); + } else { + console.error( + 'No input provided. Use -f , --stdin, or pass JSON as an argument.', + ); + process.exit(1); + } + } + + const result = await invokeHandler(route.handler, body); + console.log(JSON.stringify(result.data, null, 2)); + process.exit(result.statusCode >= 400 ? 1 : 0); +} + +main().catch((err) => { + console.error(err.message ?? err); + process.exit(1); +}); diff --git a/services/sql-assessment-service/src/database/database-analyzer.ts b/services/sql-assessment-service/src/database/database-analyzer.ts index fbe0cd4..fbb221f 100644 --- a/services/sql-assessment-service/src/database/database-analyzer.ts +++ b/services/sql-assessment-service/src/database/database-analyzer.ts @@ -22,123 +22,316 @@ export class DatabaseAnalyzer { ): Promise { try { const queryRunner = dataSource.createQueryRunner(); - const tables: Table[] = await queryRunner.getTables(); - await queryRunner.release(); const filteredTables = tables.filter((table) => table.schema === schema); + return this.buildAndStoreParsedTables( + filteredTables, + schema, + databaseKey, + aliasMap, + ); + } catch (error: any) { + console.log('Unable to parse database schema', error); + return false; + } + } - if (filteredTables.length == 0) return false; + /** + * Analyzes the schema of an in-process PGlite database using + * `information_schema` SQL queries and stores the result in the same + * in-memory registry used by the PostgreSQL path. + * + * @param db - A live PGlite instance that has already been initialised + * with the desired DDL. + * @param key - The registry key under which metadata is stored (use + * `generatePGliteKey(databaseId)`). + * @param aliasMap - Optional human-readable alias map. + */ + public async extractSchemaFromPGlite( + db: any, + key: string, + aliasMap?: IAliasMap, + ): Promise { + try { + const schema = 'public'; + + // --- Discover tables in the public schema --- + const tablesRes = await db.query( + `SELECT table_name FROM information_schema.tables + WHERE table_schema = $1 AND table_type = 'BASE TABLE' + ORDER BY table_name`, + [schema], + ); + const tableNames: string[] = tablesRes.rows.map((r: any) => r.table_name); - // ---------------------------------------------------------------- - // First pass: build base IParsedTable entries (columns + join paths) - // ---------------------------------------------------------------- - const parsedTables: IParsedTable[] = []; - const selfJoinTable: IParsedTable[] = []; + if (tableNames.length === 0) return false; - filteredTables.forEach((table) => { - const tableName = this.getTableName(table.name); + // --- Build a synthetic Table-like array from information_schema --- + const syntheticTables: Table[] = []; - // --- Collect PK column names for this table --- - const pkColumnNames = new Set( - table.primaryColumns.map((pc) => pc.name), + for (const tableName of tableNames) { + const colRes = await db.query( + `SELECT column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_schema = $1 AND table_name = $2 + ORDER BY ordinal_position`, + [schema, tableName], ); - // --- Collect FK column names for this table --- - const fkColumnNames = new Set( - table.foreignKeys.flatMap((fk) => fk.columnNames), + const pkRes = await db.query( + `SELECT kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_schema = $1 AND tc.table_name = $2 + ORDER BY kcu.ordinal_position`, + [schema, tableName], ); - // --- Collect unique-indexed column names (used for 1:1 detection) --- - const uniqueColumnNames = new Set( - table.uniques.flatMap((u) => u.columnNames), + const fkRes = await db.query( + `SELECT kcu.column_name, ccu.table_name AS referenced_table, + ccu.column_name AS referenced_column + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage ccu + ON ccu.constraint_name = tc.constraint_name + AND ccu.constraint_schema = tc.constraint_schema + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = $1 AND tc.table_name = $2 + ORDER BY kcu.ordinal_position`, + [schema, tableName], ); - // --- Build parsed columns --- - const parsedColumns: IParsedColumn[] = table.columns.map((column) => ({ - name: column.name, - tableName: tableName, - type: column.type, - isNullable: column.isNullable, - isPrimaryKey: pkColumnNames.has(column.name), - isForeignKey: fkColumnNames.has(column.name), - alternativeName: aliasMap?.columns?.[tableName]?.[column.name], - })); - - // --- Build FK relationships --- - const relationships = this.buildRelationships( - table, - tableName, - pkColumnNames, - uniqueColumnNames, + const uqRes = await db.query( + `SELECT tc.constraint_name, kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + WHERE tc.constraint_type = 'UNIQUE' + AND tc.table_schema = $1 AND tc.table_name = $2 + ORDER BY tc.constraint_name, kcu.ordinal_position`, + [schema, tableName], ); - // --- Build join paths --- - const joinPaths = this.findJoinPaths( - tableName, - [], - 0, - new Set(), - filteredTables, - schema, - false, - ); + // Group columns by constraint name so that composite UNIQUE constraints + // are represented as a single entry with multiple columnNames, matching + // the shape TypeORM returns for the real-DB path. + const uqGroups = new Map(); + for (const r of uqRes.rows as Array<{ + constraint_name: string; + column_name: string; + }>) { + const cols = uqGroups.get(r.constraint_name) ?? []; + cols.push(r.column_name); + uqGroups.set(r.constraint_name, cols); + } - const [selfJoinPaths, otherPaths] = - this.separateSelfAndNonSelfJoinPaths(joinPaths); - - const baseEntry: Omit< - IParsedTable, - | 'entityType' - | 'relationships' - | 'supertableOf' - | 'subtableOf' - | 'alternativeName' - > = { - name: tableName, - joinPaths: [], // filled below - columns: parsedColumns, - }; - - parsedTables.push({ - ...baseEntry, - joinPaths: this.filterJoinPaths(tableName, otherPaths), - // Placeholder values — overwritten in second pass - entityType: EntityType.Strong, - relationships, - alternativeName: aliasMap?.tables?.[tableName], - }); - - selfJoinTable.push({ - ...baseEntry, - joinPaths: this.filterJoinPaths(tableName, selfJoinPaths), - entityType: EntityType.Strong, - relationships, - alternativeName: aliasMap?.tables?.[tableName], - }); - }); + // Query unique indices from pg_catalog so that explicit + // CREATE UNIQUE INDEX statements are also captured. Primary-key + // indices are excluded because they are handled via primaryColumns. + // Results are ordered by attnum so that composite index column order + // is preserved. + const idxRes = await db.query( + `SELECT i.relname AS index_name, a.attname AS column_name + FROM pg_class t + JOIN pg_namespace n ON n.oid = t.relnamespace + JOIN pg_index ix ON t.oid = ix.indrelid + JOIN pg_class i ON i.oid = ix.indexrelid + JOIN pg_attribute a + ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) + WHERE n.nspname = $1 AND t.relname = $2 + AND ix.indisunique = true AND NOT ix.indisprimary + ORDER BY i.relname, a.attnum`, + [schema, tableName], + ); - // ---------------------------------------------------------------- - // Second pass: classify entity types and wire supertype/subtype - // ---------------------------------------------------------------- - this.classifyEntities(parsedTables, filteredTables, schema); - this.classifyEntities(selfJoinTable, filteredTables, schema); + // Group per index name (composite indices → multiple columns). + const idxGroups = new Map(); + for (const r of idxRes.rows as Array<{ + index_name: string; + column_name: string; + }>) { + const cols = idxGroups.get(r.index_name) ?? []; + cols.push(r.column_name); + idxGroups.set(r.index_name, cols); + } - // Propagate N:M cardinality onto the two tables bridged by each - // associative table. - this.propagateManyToMany(parsedTables); - this.propagateManyToMany(selfJoinTable); + // Cast as unknown as Table so the existing private helpers + // (which type-check against TypeORM's Table) can be reused. + // At runtime only the properties accessed by those helpers need + // to be present, and they are all provided below. + syntheticTables.push({ + name: `${schema}.${tableName}`, + schema, + columns: colRes.rows.map((r: any) => ({ + name: r.column_name, + type: r.data_type, + isNullable: r.is_nullable === 'YES', + })), + primaryColumns: pkRes.rows.map((r: any) => ({ name: r.column_name })), + foreignKeys: fkRes.rows.map((r: any) => ({ + columnNames: [r.column_name], + referencedTableName: r.referenced_table, + referencedColumnNames: [r.referenced_column], + })), + uniques: [...uqGroups.values()].map((columnNames) => ({ + columnNames, + })), + indices: [...idxGroups.entries()].map(([name, columnNames]) => ({ + name, + columnNames, + isUnique: true, + })), + } as unknown as Table); + } - databaseMetadata.set(databaseKey, parsedTables); - selfJoinDatabaseMetadata.set(databaseKey, selfJoinTable); - return true; + return this.buildAndStoreParsedTables( + syntheticTables, + schema, + key, + aliasMap, + ); } catch (error: any) { - console.log('Unable to parse database schema', error); + console.log('Unable to parse PGlite database schema', error); return false; } } + // ------------------------------------------------------------------------- + // Shared schema processing + // ------------------------------------------------------------------------- + + /** + * Builds `IParsedTable[]` from an already-filtered array of raw Table + * objects (either real TypeORM Tables or synthetic objects from PGlite), + * runs entity-type classification and N:M propagation, then stores the + * results in the in-memory registry. + */ + private buildAndStoreParsedTables( + filteredTables: Table[], + schema: string, + key: string, + aliasMap?: IAliasMap, + ): boolean { + if (filteredTables.length === 0) return false; + + // ---------------------------------------------------------------- + // First pass: build base IParsedTable entries (columns + join paths) + // ---------------------------------------------------------------- + const parsedTables: IParsedTable[] = []; + const selfJoinTable: IParsedTable[] = []; + + filteredTables.forEach((table) => { + const tableName = this.getTableName(table.name); + + // --- Collect PK column names for this table --- + const pkColumnNames = new Set( + table.primaryColumns.map((pc) => pc.name), + ); + + // --- Collect FK column names for this table --- + const fkColumnNames = new Set( + table.foreignKeys.flatMap((fk) => fk.columnNames), + ); + + // --- Collect unique-indexed column names (used for 1:1 detection) --- + // Only single-column UNIQUE constraints make a column individually unique. + // A composite UNIQUE(col_a, col_b) must NOT mark col_a or col_b as unique + // on their own, otherwise buildRelationships() would misclassify 1:N FKs + // as 1:1. + const uniqueColumnNames = new Set( + table.uniques + .filter((u) => u.columnNames.length === 1) + .map((u) => u.columnNames[0]), + ); + + // --- Build parsed columns --- + const parsedColumns: IParsedColumn[] = table.columns.map((column) => ({ + name: column.name, + tableName: tableName, + type: column.type, + isNullable: column.isNullable, + isPrimaryKey: pkColumnNames.has(column.name), + isForeignKey: fkColumnNames.has(column.name), + alternativeName: aliasMap?.columns?.[tableName]?.[column.name], + })); + + // --- Build FK relationships --- + const relationships = this.buildRelationships( + table, + tableName, + pkColumnNames, + uniqueColumnNames, + ); + + // --- Build join paths --- + const joinPaths = this.findJoinPaths( + tableName, + [], + 0, + new Set(), + filteredTables, + schema, + false, + ); + + const [selfJoinPaths, otherPaths] = + this.separateSelfAndNonSelfJoinPaths(joinPaths); + + const baseEntry: Omit< + IParsedTable, + | 'entityType' + | 'relationships' + | 'supertableOf' + | 'subtableOf' + | 'alternativeName' + > = { + name: tableName, + joinPaths: [], // filled below + columns: parsedColumns, + }; + + parsedTables.push({ + ...baseEntry, + joinPaths: this.filterJoinPaths(tableName, otherPaths), + // Placeholder values — overwritten in second pass + entityType: EntityType.Strong, + relationships, + alternativeName: aliasMap?.tables?.[tableName], + }); + + selfJoinTable.push({ + ...baseEntry, + joinPaths: this.filterJoinPaths(tableName, selfJoinPaths), + entityType: EntityType.Strong, + relationships, + alternativeName: aliasMap?.tables?.[tableName], + }); + }); + + // ---------------------------------------------------------------- + // Second pass: classify entity types and wire supertype/subtype + // ---------------------------------------------------------------- + this.classifyEntities(parsedTables, filteredTables, schema); + this.classifyEntities(selfJoinTable, filteredTables, schema); + + // Propagate N:M cardinality onto the two tables bridged by each + // associative table. + this.propagateManyToMany(parsedTables); + this.propagateManyToMany(selfJoinTable); + + databaseMetadata.set(key, parsedTables); + selfJoinDatabaseMetadata.set(key, selfJoinTable); + return true; + } + // ------------------------------------------------------------------------- // Entity-type classification // ------------------------------------------------------------------------- @@ -289,11 +482,16 @@ export class DatabaseAnalyzer { ? Participation.Optional : Participation.Mandatory; - // 1:1 if the FK column has a unique constraint or unique index + // 1:1 if the FK column has a unique constraint or unique index that covers + // exactly that one column. Composite unique constraints / indices must not + // qualify, because UNIQUE(a, b) does not mean column a is individually unique. const hasUniqueConstraint = uniqueColumnNames.has(fkCol) || table.indices.some( - (idx) => idx.isUnique && idx.columnNames.includes(fkCol), + (idx) => + idx.isUnique && + idx.columnNames.length === 1 && + idx.columnNames[0] === fkCol, ); // N:M is assigned later by propagateManyToMany; default to 1:N here diff --git a/services/sql-assessment-service/src/database/database-controller.ts b/services/sql-assessment-service/src/database/database-controller.ts index 038a4f7..9f43546 100644 --- a/services/sql-assessment-service/src/database/database-controller.ts +++ b/services/sql-assessment-service/src/database/database-controller.ts @@ -1,12 +1,6 @@ import { Router, Request, Response } from 'express'; -import { DataSource } from 'typeorm'; -import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; import { DatabaseAnalyzer } from './database-analyzer'; -import { - connectToDatabase, - generateDatabaseKey, -} from '../shared/utils/database-utils'; -import { validateConnectionInfo } from '../shared/utils/validation'; +import { DatabaseService } from './database-service'; import { t, resolveLanguageCode } from '../shared/i18n'; import { IAliasMap } from '../shared/interfaces/domain'; @@ -51,9 +45,15 @@ import { IAliasMap } from '../shared/interfaces/domain'; export class DatabaseController { public router: Router; databaseAnalyzer: DatabaseAnalyzer; + private readonly databaseService: DatabaseService; - constructor(databaseAnalyzer: DatabaseAnalyzer) { + constructor( + databaseAnalyzer: DatabaseAnalyzer, + databaseService?: DatabaseService, + ) { this.databaseAnalyzer = databaseAnalyzer; + this.databaseService = + databaseService ?? new DatabaseService(databaseAnalyzer); this.router = Router(); this.initializeRoutes(); } @@ -65,33 +65,13 @@ export class DatabaseController { } public async analyzeDatabase(req: Request, res: Response): Promise { - let connectionInfo: PostgresConnectionOptions; - let dataSource: DataSource; - let isConnected: boolean; - const lang = resolveLanguageCode(req.body?.languageCode); - try { - connectionInfo = req.body.connectionInfo; - } catch (err: any) { - console.log('Invalid connection info', err); + const connectionInfo = req.body?.connectionInfo; + if (!connectionInfo) { return res .status(400) - .json({ message: t('INVALID_CONNECTION_INFO', lang) }); - } - - const validationError = validateConnectionInfo(connectionInfo, lang); - if (validationError) { - return res.status(400).json({ message: validationError }); - } - - console.log('Received connection info:', connectionInfo); - try { - dataSource = new DataSource(connectionInfo); - isConnected = await connectToDatabase(dataSource); - } catch (error) { - console.error(error); - return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + .json({ message: t('MISSING_CONNECTION_INFO', lang) }); } // Parse and validate the optional alias map @@ -111,35 +91,12 @@ export class DatabaseController { } } - if (isConnected) { - if ( - await this.databaseAnalyzer.extractDatabaseSchema( - dataSource, - connectionInfo.schema!, - generateDatabaseKey( - connectionInfo.host!, - connectionInfo.port!, - connectionInfo.schema!, - ), - aliasMap, - ) - ) { - await dataSource.destroy(); - return res - .status(200) - .json({ message: t('DATABASE_ANALYSIS_SUCCESS', lang) }); - } - await dataSource.destroy(); - return res - .status(500) - .json({ message: t('DATABASE_SCHEMA_EXTRACTION_FAILED', lang) }); - } - - try { - await dataSource.destroy(); - } catch (error) { - console.log(error); - } - return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + const result = await this.databaseService.ensureAnalyzed( + connectionInfo, + aliasMap, + lang, + true, + ); + return res.status(result.status).json({ message: result.message }); } } diff --git a/services/sql-assessment-service/src/database/database-service.ts b/services/sql-assessment-service/src/database/database-service.ts new file mode 100644 index 0000000..e725cb2 --- /dev/null +++ b/services/sql-assessment-service/src/database/database-service.ts @@ -0,0 +1,380 @@ +import { DataSource } from 'typeorm'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; +import * as fs from 'fs/promises'; +import { PGlite } from '@electric-sql/pglite'; +import { DatabaseAnalyzer } from './database-analyzer'; +import { + connectToDatabase, + generateDatabaseKey, + generatePGliteKey, +} from '../shared/utils/database-utils'; +import { + isDatabaseRegistered, + validateConnectionInfo, +} from '../shared/utils/validation'; +import { t, SupportedLanguage } from '../shared/i18n'; +import { IAliasMap } from '../shared/interfaces/domain'; +import { + databaseMetadata, + pgliteInstances, + selfJoinDatabaseMetadata, +} from './internal-memory'; + +/** Canonical registry key used for the single shared init-SQL PGlite instance. */ +const INIT_PGLITE_ID = '__pglite_init__'; + +export interface AnalyzeResult { + ok: boolean; + status: number; + message: string; +} + +/** + * Pure business-logic service for analyzing / registering databases. + * + * Used by {@link DatabaseController} to handle `/api/database/analyze-database` + * and by the four downstream controllers to auto-analyze before their own logic. + * + * When {@link initSqlFilePath} is configured (via constructor, the + * `PGLITE_INIT_SQL_FILE` environment variable, or the `--init-sql-file` CLI + * flag), a PGlite request that carries **no** `sqlContent` field will + * automatically fall back to reading that file as the initialisation SQL. + */ +export class DatabaseService { + private readonly initSqlFilePath?: string; + private cachedInitSql?: string; + /** Single shared PGlite instance used for all init-SQL-backed databases. */ + private cachedInitSqlPGlite?: PGlite; + /** In-flight promise for the first initialisation of {@link cachedInitSqlPGlite}. */ + private initSqlPGlitePromise?: Promise; + + constructor( + private readonly databaseAnalyzer: DatabaseAnalyzer, + initSqlFilePath?: string, + ) { + this.initSqlFilePath = initSqlFilePath; + } + /** + * Ensures the database described by `connectionInfo` is analyzed and its + * schema metadata stored in the in-memory registry. + * + * Behaviour by connection type: + * - **PGlite** – always (re-)creates the in-process instance when + * `sqlContent` is present. If `sqlContent` is absent and `required` is + * `false` (the default for the four downstream controllers), the call is a + * no-op (assumes a prior registration is still valid for the process). + * When `required` is `true` (used by the `/api/database/analyze-database` + * endpoint), `sqlContent` is mandatory and its absence is an error. + * - **PostgreSQL** – runs a full schema extraction only when the database + * key is not yet registered. Subsequent calls within the same process + * skip the expensive round-trip. + */ + async ensureAnalyzed( + connectionInfo: any, + aliasMap?: IAliasMap, + lang: SupportedLanguage = 'en', + required = false, + ): Promise { + if (!connectionInfo) { + return { + ok: false, + status: 400, + message: t('MISSING_CONNECTION_INFO', lang), + }; + } + + if (connectionInfo.type === 'pglite') { + // Resolve sqlContent: use the value from the request body first. + const sqlContent: string | undefined = connectionInfo.sqlContent; + + // Distinguish "absent" (null/undefined) from "present but invalid" + // (e.g. empty string). An explicitly-supplied empty string is not + // treated as absent — it falls through to analyzePGlite, which + // validates it and returns a 400. + if (sqlContent == null) { + // If the DB is already registered and the caller did not supply new + // sqlContent, treat this as a no-op — same semantics as the PG branch. + const databaseId: string | undefined = connectionInfo.databaseId; + if ( + !required && + databaseId && + isDatabaseRegistered(generatePGliteKey(databaseId)) + ) { + return { ok: true, status: 200, message: 'already registered' }; + } + + // Fall back to the configured init-SQL file (if any). + // All callers share a single PGlite instance — no per-databaseId copy. + if (this.initSqlFilePath) { + if (!databaseId || typeof databaseId !== 'string') { + return { + ok: false, + status: 400, + message: t('INVALID_CONNECTION_INFO', lang), + }; + } + return this.ensureInitSqlPGliteRegistered(databaseId, aliasMap, lang); + } + + if (required) { + return { + ok: false, + status: 400, + message: t('INVALID_CONNECTION_INFO', lang), + }; + } + return { ok: true, status: 200, message: 'skipped' }; + } + return this.analyzePGlite( + { ...connectionInfo, sqlContent }, + aliasMap, + lang, + ); + } + + // PostgreSQL – validate first so we never generate a bogus key from undefined fields. + const pgInfo = connectionInfo as PostgresConnectionOptions; + const pgValidationError = validateConnectionInfo(pgInfo, lang); + if (pgValidationError) { + return { ok: false, status: 400, message: pgValidationError }; + } + + // Skip the expensive round-trip only when the caller did not explicitly + // require a fresh analysis (required=true is used by /analyze-database). + const key = generateDatabaseKey(pgInfo.host!, pgInfo.port!, pgInfo.schema!); + if (!required && isDatabaseRegistered(key)) { + return { ok: true, status: 200, message: 'already registered' }; + } + + return this.analyzePostgres(connectionInfo, aliasMap, lang); + } + + // ------------------------------------------------------------------------- + // PGlite helpers + // ------------------------------------------------------------------------- + + private async analyzePGlite( + rawInfo: any, + aliasMap: IAliasMap | undefined, + lang: SupportedLanguage, + ): Promise { + const { databaseId, sqlContent } = rawInfo ?? {}; + + if (!databaseId || typeof databaseId !== 'string') { + return { + ok: false, + status: 400, + message: t('INVALID_CONNECTION_INFO', lang), + }; + } + if (!sqlContent || typeof sqlContent !== 'string') { + return { + ok: false, + status: 400, + message: t('INVALID_CONNECTION_INFO', lang), + }; + } + + // Close and evict any existing instance for this id, + // but never close the shared init-SQL instance. + const existing = pgliteInstances.get(databaseId); + if (existing && existing !== this.cachedInitSqlPGlite) { + try { + await existing.close(); + } catch { + /* ignore */ + } + } + pgliteInstances.delete(databaseId); + + let db: PGlite; + try { + db = new PGlite(); + await db.exec(sqlContent); + } catch (error) { + console.error('PGlite initialisation failed', error); + return { + ok: false, + status: 500, + message: t('DATABASE_SCHEMA_EXTRACTION_FAILED', lang), + }; + } + + const key = generatePGliteKey(databaseId); + const success = await this.databaseAnalyzer.extractSchemaFromPGlite( + db, + key, + aliasMap, + ); + + if (!success) { + try { + await db.close(); + } catch { + /* ignore */ + } + return { + ok: false, + status: 500, + message: t('DATABASE_SCHEMA_EXTRACTION_FAILED', lang), + }; + } + + pgliteInstances.set(databaseId, db); + return { + ok: true, + status: 200, + message: t('DATABASE_ANALYSIS_SUCCESS', lang), + }; + } + + /** + * Lazily creates a single shared PGlite instance from the configured init + * SQL file, then registers `databaseId` as an alias pointing at that shared + * instance. All callers that omit `sqlContent` share the same in-memory + * database — no per-databaseId copy is ever created. + */ + private async ensureInitSqlPGliteRegistered( + databaseId: string, + aliasMap: IAliasMap | undefined, + lang: SupportedLanguage, + ): Promise { + // Read and cache the init SQL file. + try { + if (!this.cachedInitSql) { + this.cachedInitSql = await fs.readFile(this.initSqlFilePath!, 'utf-8'); + } + } catch (err) { + console.error( + `Failed to read init SQL file: ${this.initSqlFilePath}`, + err, + ); + return { + ok: false, + status: 500, + message: t('INIT_SQL_READ_ERROR', lang), + }; + } + + // Create the shared PGlite instance once — race-safe via a stored Promise + // so concurrent callers coalesce onto the same initialisation rather than + // each spawning their own PGlite instance. + if (!this.initSqlPGlitePromise) { + this.initSqlPGlitePromise = (async (): Promise => { + let db: PGlite; + try { + db = new PGlite(); + await db.exec(this.cachedInitSql!); + } catch (error) { + console.error('PGlite initialisation failed', error); + return null; + } + + const canonicalKey = generatePGliteKey(INIT_PGLITE_ID); + const success = await this.databaseAnalyzer.extractSchemaFromPGlite( + db, + canonicalKey, + aliasMap, + ); + if (!success) { + try { + await db.close(); + } catch { + /* ignore */ + } + return null; + } + + return db; + })(); + } + const db = await this.initSqlPGlitePromise; + if (!db) { + // Allow future calls to retry after a failed initialisation. + this.initSqlPGlitePromise = undefined; + return { + ok: false, + status: 500, + message: t('DATABASE_SCHEMA_EXTRACTION_FAILED', lang), + }; + } + this.cachedInitSqlPGlite = db; + + // Register the caller's databaseId pointing to the shared instance. + const databaseKey = generatePGliteKey(databaseId); + if (!databaseMetadata.has(databaseKey)) { + const canonicalKey = generatePGliteKey(INIT_PGLITE_ID); + databaseMetadata.set(databaseKey, databaseMetadata.get(canonicalKey)!); + const selfJoin = selfJoinDatabaseMetadata.get(canonicalKey); + if (selfJoin !== undefined) { + selfJoinDatabaseMetadata.set(databaseKey, selfJoin); + } + } + pgliteInstances.set(databaseId, this.cachedInitSqlPGlite); + + return { + ok: true, + status: 200, + message: t('DATABASE_ANALYSIS_SUCCESS', lang), + }; + } + + // ------------------------------------------------------------------------- + // PostgreSQL helpers + // ------------------------------------------------------------------------- + + private async analyzePostgres( + connectionInfo: PostgresConnectionOptions, + aliasMap: IAliasMap | undefined, + lang: SupportedLanguage, + ): Promise { + const validationError = validateConnectionInfo(connectionInfo, lang); + if (validationError) { + return { ok: false, status: 400, message: validationError }; + } + + let dataSource: DataSource; + let isConnected: boolean; + try { + dataSource = new DataSource(connectionInfo); + isConnected = await connectToDatabase(dataSource); + } catch { + return { ok: false, status: 400, message: t('UNABLE_TO_CONNECT', lang) }; + } + + if (!isConnected) { + try { + await dataSource!.destroy(); + } catch { + /* ignore */ + } + return { ok: false, status: 400, message: t('UNABLE_TO_CONNECT', lang) }; + } + + const key = generateDatabaseKey( + connectionInfo.host!, + connectionInfo.port!, + connectionInfo.schema!, + ); + const extracted = await this.databaseAnalyzer.extractDatabaseSchema( + dataSource, + connectionInfo.schema!, + key, + aliasMap, + ); + await dataSource.destroy(); + + if (!extracted) { + return { + ok: false, + status: 500, + message: t('DATABASE_SCHEMA_EXTRACTION_FAILED', lang), + }; + } + return { + ok: true, + status: 200, + message: t('DATABASE_ANALYSIS_SUCCESS', lang), + }; + } +} diff --git a/services/sql-assessment-service/src/database/internal-memory.ts b/services/sql-assessment-service/src/database/internal-memory.ts index 0b5da39..8599aae 100644 --- a/services/sql-assessment-service/src/database/internal-memory.ts +++ b/services/sql-assessment-service/src/database/internal-memory.ts @@ -1,5 +1,10 @@ +import type { PGlite } from '@electric-sql/pglite'; import { IParsedTable } from '../shared/interfaces/domain'; export const databaseMetadata: Map = new Map(); export const selfJoinDatabaseMetadata: Map = new Map(); + +// Stores live PGlite instances indexed by databaseId. + +export const pgliteInstances: Map = new Map(); diff --git a/services/sql-assessment-service/src/generation/description/description-controller.ts b/services/sql-assessment-service/src/generation/description/description-controller.ts index 871bef9..97947c8 100644 --- a/services/sql-assessment-service/src/generation/description/description-controller.ts +++ b/services/sql-assessment-service/src/generation/description/description-controller.ts @@ -13,6 +13,7 @@ import { import { buildAliasMapFromTables, generateDatabaseKey, + generatePGliteKey, } from '../../shared/utils/database-utils'; import { isDatabaseRegistered, @@ -24,6 +25,7 @@ import { } from '../../database/internal-memory'; import { TaskDescriptionGenerationService } from './task-description-generation-service'; import { t, resolveLanguageCode, SupportedLanguage } from '../../shared/i18n'; +import { DatabaseService } from '../../database/database-service'; const sqlParser = new Parser(); @@ -220,11 +222,14 @@ const sqlParser = new Parser(); export class DescriptionController { public router: Router; private taskDescriptionGenerationService: TaskDescriptionGenerationService; + private readonly databaseService?: DatabaseService; constructor( taskDescriptionGenerationService: TaskDescriptionGenerationService, + databaseService?: DatabaseService, ) { this.taskDescriptionGenerationService = taskDescriptionGenerationService; + this.databaseService = databaseService; this.router = Router(); this.initializeRoutes(); } @@ -251,16 +256,17 @@ export class DescriptionController { // Shared request validation — returns null on success, a Response on failure // --------------------------------------------------------------------------- - private validateRequest( + private async validateRequest( req: Request, res: Response, - ): { + ): Promise<{ options: IRequestDescriptionOptions; databaseKey: string; lang: SupportedLanguage; tables: IParsedTable[]; schemaAliasMap: IAliasMap | undefined; - } | null { + schema: string; + } | null> { let options: IRequestDescriptionOptions; try { @@ -287,17 +293,49 @@ export class DescriptionController { return null; } - const connectionError = validateConnectionInfo( - options.connectionInfo, - lang, - ); - if (connectionError) { - res.status(400).json({ message: connectionError }); - return null; + // ---- auto-analyze --------------------------------------------------- + if (this.databaseService) { + const analyzed = await this.databaseService.ensureAnalyzed( + options.connectionInfo, + undefined, + lang, + ); + if (!analyzed.ok) { + res.status(analyzed.status).json({ message: analyzed.message }); + return null; + } } - - const { host, port, schema } = options.connectionInfo; - const databaseKey = generateDatabaseKey(host!, port!, schema!); + // --------------------------------------------------------------------- + + // ---- PGlite branch -------------------------------------------------- + const connectionInfoAny = options.connectionInfo as any; + let databaseKey: string; + let schema: string; + + if (connectionInfoAny?.type === 'pglite') { + const { databaseId } = connectionInfoAny; + if (!databaseId || typeof databaseId !== 'string') { + res.status(400).json({ message: t('INVALID_CONNECTION_INFO', lang) }); + return null; + } + databaseKey = generatePGliteKey(databaseId); + schema = 'public'; + } else { + // PostgreSQL path + const connectionError = validateConnectionInfo( + options.connectionInfo, + lang, + ); + if (connectionError) { + res.status(400).json({ message: connectionError }); + return null; + } + + const { host, port, schema: pgSchema } = options.connectionInfo; + databaseKey = generateDatabaseKey(host!, port!, pgSchema!); + schema = pgSchema!; + } + // --------------------------------------------------------------------- if (!isDatabaseRegistered(databaseKey)) { res.status(400).json({ message: t('DATABASE_NOT_REGISTERED', lang) }); @@ -311,7 +349,7 @@ export class DescriptionController { const tables = this.resolveStoredTables(databaseKey, isSelfJoin); const schemaAliasMap = buildAliasMapFromTables(tables); - return { options, databaseKey, lang, tables, schemaAliasMap }; + return { options, databaseKey, lang, tables, schemaAliasMap, schema }; } /** @@ -344,10 +382,11 @@ export class DescriptionController { req: Request, res: Response, ): Promise { - const validated = this.validateRequest(req, res); + const validated = await this.validateRequest(req, res); if (!validated) return res; - const { options, databaseKey, lang, tables, schemaAliasMap } = validated; + const { options, databaseKey, lang, tables, schemaAliasMap, schema } = + validated; const languageCode = options.languageCode ?? 'en'; let ast: any; @@ -365,7 +404,7 @@ export class DescriptionController { generationType: GenerationOptions.Template, query: options.query, queryAST: ast, - schema: options.connectionInfo.schema!, + schema: schema, databaseKey, isSelfJoin: options.isSelfJoin ?? false, schemaAliasMap, @@ -391,10 +430,11 @@ export class DescriptionController { req: Request, res: Response, ): Promise { - const validated = this.validateRequest(req, res); + const validated = await this.validateRequest(req, res); if (!validated) return res; - const { options, databaseKey, lang, tables, schemaAliasMap } = validated; + const { options, databaseKey, lang, tables, schemaAliasMap, schema } = + validated; const languageCode = options.languageCode ?? 'en'; try { @@ -403,7 +443,7 @@ export class DescriptionController { generationType: GenerationOptions.LLM, query: options.query, queryAST: null as any, - schema: options.connectionInfo.schema!, + schema: schema, databaseKey, isSelfJoin: options.isSelfJoin ?? false, option: GptOptions.Default, @@ -430,10 +470,11 @@ export class DescriptionController { req: Request, res: Response, ): Promise { - const validated = this.validateRequest(req, res); + const validated = await this.validateRequest(req, res); if (!validated) return res; - const { options, databaseKey, lang, tables, schemaAliasMap } = validated; + const { options, databaseKey, lang, tables, schemaAliasMap, schema } = + validated; const languageCode = options.languageCode ?? 'en'; try { @@ -442,7 +483,7 @@ export class DescriptionController { generationType: GenerationOptions.LLM, query: options.query, queryAST: null as any, - schema: options.connectionInfo.schema!, + schema: schema, databaseKey, isSelfJoin: options.isSelfJoin ?? false, option: GptOptions.Creative, @@ -469,10 +510,11 @@ export class DescriptionController { req: Request, res: Response, ): Promise { - const validated = this.validateRequest(req, res); + const validated = await this.validateRequest(req, res); if (!validated) return res; - const { options, databaseKey, lang, tables, schemaAliasMap } = validated; + const { options, databaseKey, lang, tables, schemaAliasMap, schema } = + validated; const languageCode = options.languageCode ?? 'en'; try { @@ -481,7 +523,7 @@ export class DescriptionController { generationType: GenerationOptions.LLM, query: options.query, queryAST: null as any, - schema: options.connectionInfo.schema!, + schema: schema, databaseKey, isSelfJoin: options.isSelfJoin ?? false, option: GptOptions.MultiStep, @@ -508,10 +550,11 @@ export class DescriptionController { req: Request, res: Response, ): Promise { - const validated = this.validateRequest(req, res); + const validated = await this.validateRequest(req, res); if (!validated) return res; - const { options, databaseKey, lang, tables, schemaAliasMap } = validated; + const { options, databaseKey, lang, tables, schemaAliasMap, schema } = + validated; const languageCode = options.languageCode ?? 'en'; let ast: any; @@ -529,7 +572,7 @@ export class DescriptionController { generationType: GenerationOptions.Hybrid, query: options.query, queryAST: ast, - schema: options.connectionInfo.schema!, + schema: schema, databaseKey, isSelfJoin: options.isSelfJoin ?? false, option: undefined, diff --git a/services/sql-assessment-service/src/generation/description/llm-task-description-generation-engine.ts b/services/sql-assessment-service/src/generation/description/llm-task-description-generation-engine.ts index 8958a2e..d1ae44a 100644 --- a/services/sql-assessment-service/src/generation/description/llm-task-description-generation-engine.ts +++ b/services/sql-assessment-service/src/generation/description/llm-task-description-generation-engine.ts @@ -1,4 +1,3 @@ -import dotenv from 'dotenv'; import { ChatOpenAI } from '@langchain/openai'; import { SystemMessagePromptTemplate } from '@langchain/core/prompts'; import { @@ -18,8 +17,6 @@ const LANGUAGE_DIRECTIVES: Record = { export class LLMTaskDescriptionGenerationEngine { constructor() { - dotenv.config(); - this.openai = new ChatOpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, modelName: 'gpt-4o-mini', diff --git a/services/sql-assessment-service/src/generation/query/predicate-generation-service.ts b/services/sql-assessment-service/src/generation/query/predicate-generation-service.ts index d7081f9..15fa2de 100644 --- a/services/sql-assessment-service/src/generation/query/predicate-generation-service.ts +++ b/services/sql-assessment-service/src/generation/query/predicate-generation-service.ts @@ -1,17 +1,16 @@ -import { DataSource } from 'typeorm'; import { Expr } from 'node-sql-parser'; import { - aggregateColumnType, - aggregateType, - booleanTypes, - dateTypes, - numericTypes, - operationTypes, - operationType, - textTypes, + aggregateColumnType, + aggregateType, + booleanTypes, + dateTypes, + numericTypes, + operationTypes, + operationType, + textTypes, } from '../../shared/constants'; import { IParsedColumn } from '../../shared/interfaces/domain'; -import { createQueryRunner } from '../../shared/utils/database-utils'; +import { RowQueryFn } from '../../shared/utils/database-utils'; import { isValidForAggregation } from '../../shared/utils/validation'; import { random, randomBoolean, shuffle } from '../../shared/utils/random'; import { SelectASTBuilder } from './select-ast-builder'; @@ -21,418 +20,496 @@ import { SelectASTBuilder } from './select-ast-builder'; * Extracted from SelectQueryGenerationDirector to satisfy the Single Responsibility Principle. */ export class PredicateGenerationService { - - constructor(private readonly astBuilder: SelectASTBuilder) {} - - async generatePredicates( - mergedColumns: IParsedColumn[], - constraintCount: number, - dataSource: DataSource, - schema: string, - configuredOperationTypes: operationType[] - ): Promise { - const constraintColumns: IParsedColumn[] = shuffle(mergedColumns); - const whereConstraints: Expr[] = []; - const usedColumns = new Set(); - - for (const operation of configuredOperationTypes) { - let column: IParsedColumn | undefined; - if (operation === 'IS_NULL') { - column = mergedColumns.find( - col => col.isNullable && !usedColumns.has(col.name) - ); - } else { - column = mergedColumns.find( - col => operationTypes[operation].includes(col.type) && !usedColumns.has(col.name) - ); - } - - if (column) { - const constraint = await this.generatePredicateByOperationType(column, operation, dataSource, schema); - if (constraint) whereConstraints.push(constraint); - usedColumns.add(column.name); - } - } - - const unusedColumns: IParsedColumn[] = constraintColumns.filter(column => !usedColumns.has(column.name)); - - for (let i = 0; i < unusedColumns.length && whereConstraints.length < constraintCount; i++) { - const constraint = await this.generatePredicateByColumnType(unusedColumns[i], dataSource, schema); - if (constraint) whereConstraints.push(constraint); - } - - if (whereConstraints.length < constraintCount) { - throw Error('Unable to generate the expected predicate count'); - } - return whereConstraints; - } - - async generateHavingClauseAndReturnGroupByColumn( - mergedColumns: IParsedColumn[], - dataSource: DataSource, - schema: string - ): Promise { - const havingColumn: IParsedColumn | undefined = shuffle(mergedColumns).find( - (column: IParsedColumn) => aggregateColumnType.includes(column.type) && isValidForAggregation(column.name) - ); - if (!havingColumn) - throw Error('Unable to find having column, reshuffle'); - havingColumn.aggregation = this.returnAggregateType(havingColumn); - if (!havingColumn.aggregation) - throw Error('Unable to find having column, reshuffle'); - - await this.generateHavingPredicate(havingColumn, dataSource, schema); - - return this.generateGroupByClauseAndReturnColumn(mergedColumns, havingColumn); - } - - generateGroupByClauseAndReturnColumn( - mergedColumns: IParsedColumn[], - havingColumn?: IParsedColumn - ): IParsedColumn { - const groupbyColumn: IParsedColumn | undefined = havingColumn - ? shuffle(mergedColumns).find( - (column: IParsedColumn) => - column.name != havingColumn.name && !column.aggregation - ) - : shuffle(mergedColumns).find( - (column: IParsedColumn) => !column.aggregation - ); - - if (!groupbyColumn) { - throw new Error('Unable to find group by column, reshuffle'); - } - - this.astBuilder.buildGroupBy(groupbyColumn.tableName, groupbyColumn.name); - return groupbyColumn; - } - - returnAggregateType(column: IParsedColumn): aggregateType | undefined { - if (numericTypes.includes(column.type)) { - return ['MAX', 'MIN', 'AVG', 'COUNT', 'SUM'][random(5)] as aggregateType; - } else if (textTypes.includes(column.type)) { - return ['MAX', 'MIN', 'COUNT'][random(3)] as aggregateType; - } else if (dateTypes.includes(column.type)) { - return ['MAX', 'MIN', 'COUNT'][random(3)] as aggregateType; - } - } - - private generateNullableConstraint(column: IParsedColumn): Expr { - const operator = randomBoolean() ? 'IS' : 'IS NOT'; - return this.astBuilder.buildWhereConstraint(operator, column.tableName, column.name, 'null', null); - } - - private async generateLikeConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValueFromDatabase( - column.name, column.type, dataSource, - `SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 1` - ); - - if (randomValue) { - const completeString = randomValue; - const operator = randomBoolean() ? 'LIKE' : 'NOT LIKE'; - - let start = random(completeString.length - 1); - let end = random(completeString.length - 1); - - if (start > end) [start, end] = [end, start]; - if (start === end && completeString.length > 2) { - if (end === 0) end++; - if (start === completeString.length - 1) start--; - } - - let substring = completeString.substring(start, end); - if (end !== completeString.length - 1) substring += '%'; - if (start !== 0) substring = '%' + substring; - - const value = this.checkForSpecialCharacters(type, substring); - return this.astBuilder.buildWhereConstraint(operator, column.tableName, column.name, type, value); - } - return undefined; - } - - private async generateLargerConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValueFromDatabase( - column.name, column.type, dataSource, - `SELECT ${column.name} FROM ${schema}.${column.tableName} WHERE ${column.name} NOT IN (SELECT MAX(${column.name}) FROM ${schema}.${column.tableName}) ORDER BY RANDOM() LIMIT 1` - ); - - if (randomValue) { - const operator = randomBoolean() ? '>' : '>='; - const value = this.checkForSpecialCharacters(type, randomValue); - return this.astBuilder.buildWhereConstraint(operator, column.tableName, column.name, type, value); - } - return undefined; - } - - private async generateSmallerConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValueFromDatabase( - column.name, column.type, dataSource, - `SELECT ${column.name} FROM ${schema}.${column.tableName} WHERE ${column.name} NOT IN (SELECT MIN(${column.name}) FROM ${schema}.${column.tableName}) ORDER BY RANDOM() LIMIT 1` - ); - - if (randomValue) { - const operator = randomBoolean() ? '<' : '<='; - const value = this.checkForSpecialCharacters(type, randomValue); - return this.astBuilder.buildWhereConstraint(operator, column.tableName, column.name, type, value); - } - return undefined; - } - - private async generateEqualityConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValueFromDatabase( - column.name, column.type, dataSource, - `SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 1` - ); - - if (randomValue) { - const operator = randomBoolean() ? '=' : '!='; - const value = this.checkForSpecialCharacters(type, randomValue); - return this.astBuilder.buildWhereConstraint(operator, column.tableName, column.name, type, value); - } - return undefined; - } - - private async generateBetweenConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValuesFromDatabase( - column, dataSource, - `WITH selected_rows AS ( SELECT ${column.name}, LEAD(${column.name}) OVER (ORDER BY RANDOM()) as next_value, LAG(${column.name}) OVER (ORDER BY RANDOM()) as prev_value FROM ${schema}.${column.tableName} ) SELECT ${column.name} FROM selected_rows WHERE ${column.name} IS DISTINCT FROM next_value AND ${column.name} IS DISTINCT FROM prev_value ORDER BY RANDOM() LIMIT 2;` - ); - - if (randomValue && randomValue.length > 1) { - const valueList = []; - for (let i = 0; i < 2; i++) { - const val = this.checkForSpecialCharacters(type, randomValue[i]); - valueList.push({ type: type, value: val }); - } - return this.astBuilder.buildWhereConstraintValueList('BETWEEN', column.tableName, column.name, valueList); - } - return undefined; - } - - private async generateINConstraint( - column: IParsedColumn, - type: string, - dataSource: DataSource, - schema: string - ): Promise { - const randomValue = await this.getRandomValuesFromDatabase( - column, dataSource, - `WITH row_count AS ( SELECT COUNT(*) AS total_rows FROM ${schema}.${column.tableName} ), random_selection AS ( SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 4 ) SELECT ${column.name} FROM random_selection WHERE (SELECT total_rows FROM row_count) >= 2 LIMIT LEAST((SELECT total_rows FROM row_count), 4);` - ); - - if (randomValue && randomValue.length > 1) { - const valueList: any[] = []; - randomValue.forEach((val: any) => { - const value = this.checkForSpecialCharacters(type, val); - valueList.push({ type: type, value: value }); - }); - return this.astBuilder.buildWhereConstraintValueList('IN', column.tableName, column.name, valueList); - } - return undefined; - } - - private generateBooleanConstraint(column: IParsedColumn): Expr { - const value = randomBoolean() ? true : false; - return this.astBuilder.buildWhereConstraint('IS', column.tableName, column.name, 'bool', value); - } - - async generateHavingPredicate( - column: IParsedColumn, - dataSource: DataSource, - schema: string - ): Promise { - if (!column.aggregation) - throw new Error('Error in selecting aggregation type for Having predicate'); - - const type = numericTypes.includes(column.type) ? 'number' : 'single_quote_string'; - const operators = ['>', '>=', '<', '<=', '=', '!=']; - const operator = operators[random(operators.length)]; - - let randomValue = await this.getRandomValueFromDatabase( - 'aggvalue', column.type, dataSource, - `SELECT ${column.aggregation}(${column.name}) AS aggvalue FROM ${schema}.${column.tableName}`, - true - ); - - if (randomValue) { - if (dateTypes.includes(column.type) && column.aggregation !== 'COUNT') { - const date = new Date(randomValue); - randomValue = date.toISOString().split('T')[0]; - } - - const value = this.checkForSpecialCharacters(type, randomValue); - this.astBuilder.buildHaving( - operator, - column.aggregation, - column.tableName, - column.name, - value, - type - ); - } else { - throw new Error('Error in finding a Random Value for Having'); - } - } - - private async generatePredicateByOperationType( - column: IParsedColumn, - operation: operationType, - dataSource: DataSource, - schema: string - ): Promise { - const type = numericTypes.includes(column.type) ? 'number' : 'single_quote_string'; - switch (operation) { - case 'EQUAL': - return this.generateEqualityConstraint(column, type, dataSource, schema); - case 'COMPARISON': - return randomBoolean() - ? this.generateLargerConstraint(column, type, dataSource, schema) - : this.generateSmallerConstraint(column, type, dataSource, schema); - case 'IN': - return this.generateINConstraint(column, type, dataSource, schema); - case 'IS_NULL': - return this.generateNullableConstraint(column); - case 'LIKE': - return this.generateLikeConstraint(column, type, dataSource, schema); - case 'BETWEEN': - return this.generateBetweenConstraint(column, type, dataSource, schema); - case 'IS_BOOLEAN': - return this.generateBooleanConstraint(column); - } - } - - private async generatePredicateByColumnType( - column: IParsedColumn, - dataSource: DataSource, - schema: string - ): Promise { - const textFunctions = [ - this.generateLargerConstraint.bind(this), - this.generateSmallerConstraint.bind(this), - this.generateEqualityConstraint.bind(this), - this.generateBetweenConstraint.bind(this), - this.generateINConstraint.bind(this), - this.generateLikeConstraint.bind(this), - ]; - const numberFunctions = [ - this.generateLargerConstraint.bind(this), - this.generateSmallerConstraint.bind(this), - this.generateEqualityConstraint.bind(this), - this.generateBetweenConstraint.bind(this), - this.generateINConstraint.bind(this), - ]; - const dateFunctions = [ - this.generateLargerConstraint.bind(this), - this.generateSmallerConstraint.bind(this), - this.generateEqualityConstraint.bind(this), - this.generateBetweenConstraint.bind(this), - this.generateINConstraint.bind(this), - ]; - - switch (true) { - case column.isNullable && randomBoolean(): - return this.generateNullableConstraint(column); - case textTypes.includes(column.type): { - const fn = textFunctions[random(textFunctions.length)]; - return await fn(column, 'single_quote_string', dataSource, schema); - } - case numericTypes.includes(column.type): { - const fn = numberFunctions[random(numberFunctions.length)]; - return await fn(column, 'number', dataSource, schema); - } - case dateTypes.includes(column.type): { - const fn = dateFunctions[random(dateFunctions.length)]; - return await fn(column, 'single_quote_string', dataSource, schema); - } - case booleanTypes.includes(column.type): - return this.generateBooleanConstraint(column); - default: - return undefined; - } - } - - private checkForSpecialCharacters(type: string, value: any): any { - if (type === 'single_quote_string' && typeof value === 'string') { - return value.replace("'", "''"); - } - return value; - } - - private async getRandomValueFromDatabase( - columnName: string, - columnType: string, - dataSource: DataSource, - query: string, - isHaving = false - ): Promise { - const queryRunner = createQueryRunner(dataSource); - if (!queryRunner) return undefined; - const randomValue = await queryRunner.query(query); - queryRunner.release(); - - if (randomValue && randomValue.length > 0) { - const value = randomValue[0][`${columnName}`]; - - if (!isHaving && value && dateTypes.includes(columnType)) { - const date = new Date(value); - return date.toISOString().split('T')[0]; - } - - return value; - } - return undefined; - } - - private async getRandomValuesFromDatabase( - column: IParsedColumn, - dataSource: DataSource, - query: string - ): Promise { - const queryRunner = createQueryRunner(dataSource); - if (!queryRunner) return undefined; - const randomValue = await queryRunner.query(query); - queryRunner.release(); - - if (randomValue && randomValue.length > 0) { - const formattedValues = []; - for (let i = 0; i < randomValue.length; i++) { - const value = randomValue[i][`${column.name}`]; - - if (value && dateTypes.includes(column.type)) { - const date = new Date(value); - formattedValues.push(date.toISOString().split('T')[0]); - } else { - formattedValues.push(value); - } - } - return formattedValues; - } - return undefined; - } + constructor(private readonly astBuilder: SelectASTBuilder) {} + + async generatePredicates( + mergedColumns: IParsedColumn[], + constraintCount: number, + runQuery: RowQueryFn, + schema: string, + configuredOperationTypes: operationType[], + ): Promise { + const constraintColumns: IParsedColumn[] = shuffle(mergedColumns); + const whereConstraints: Expr[] = []; + const usedColumns = new Set(); + + for (const operation of configuredOperationTypes) { + let column: IParsedColumn | undefined; + if (operation === 'IS_NULL') { + column = mergedColumns.find( + (col) => col.isNullable && !usedColumns.has(col.name), + ); + } else { + column = mergedColumns.find( + (col) => + operationTypes[operation].includes(col.type) && + !usedColumns.has(col.name), + ); + } + + if (column) { + const constraint = await this.generatePredicateByOperationType( + column, + operation, + runQuery, + schema, + ); + if (constraint) whereConstraints.push(constraint); + usedColumns.add(column.name); + } + } + + const unusedColumns: IParsedColumn[] = constraintColumns.filter( + (column) => !usedColumns.has(column.name), + ); + + for ( + let i = 0; + i < unusedColumns.length && whereConstraints.length < constraintCount; + i++ + ) { + const constraint = await this.generatePredicateByColumnType( + unusedColumns[i], + runQuery, + schema, + ); + if (constraint) whereConstraints.push(constraint); + } + + if (whereConstraints.length < constraintCount) { + throw Error('Unable to generate the expected predicate count'); + } + return whereConstraints; + } + + async generateHavingClauseAndReturnGroupByColumn( + mergedColumns: IParsedColumn[], + runQuery: RowQueryFn, + schema: string, + ): Promise { + const havingColumn: IParsedColumn | undefined = shuffle(mergedColumns).find( + (column: IParsedColumn) => + aggregateColumnType.includes(column.type) && + isValidForAggregation(column.name), + ); + if (!havingColumn) throw Error('Unable to find having column, reshuffle'); + havingColumn.aggregation = this.returnAggregateType(havingColumn); + if (!havingColumn.aggregation) + throw Error('Unable to find having column, reshuffle'); + + await this.generateHavingPredicate(havingColumn, runQuery, schema); + + return this.generateGroupByClauseAndReturnColumn( + mergedColumns, + havingColumn, + ); + } + + generateGroupByClauseAndReturnColumn( + mergedColumns: IParsedColumn[], + havingColumn?: IParsedColumn, + ): IParsedColumn { + const groupbyColumn: IParsedColumn | undefined = havingColumn + ? shuffle(mergedColumns).find( + (column: IParsedColumn) => + column.name != havingColumn.name && !column.aggregation, + ) + : shuffle(mergedColumns).find( + (column: IParsedColumn) => !column.aggregation, + ); + + if (!groupbyColumn) { + throw new Error('Unable to find group by column, reshuffle'); + } + + this.astBuilder.buildGroupBy(groupbyColumn.tableName, groupbyColumn.name); + return groupbyColumn; + } + + returnAggregateType(column: IParsedColumn): aggregateType | undefined { + if (numericTypes.includes(column.type)) { + return ['MAX', 'MIN', 'AVG', 'COUNT', 'SUM'][random(5)] as aggregateType; + } else if (textTypes.includes(column.type)) { + return ['MAX', 'MIN', 'COUNT'][random(3)] as aggregateType; + } else if (dateTypes.includes(column.type)) { + return ['MAX', 'MIN', 'COUNT'][random(3)] as aggregateType; + } + } + + private generateNullableConstraint(column: IParsedColumn): Expr { + const operator = randomBoolean() ? 'IS' : 'IS NOT'; + return this.astBuilder.buildWhereConstraint( + operator, + column.tableName, + column.name, + 'null', + null, + ); + } + + private async generateLikeConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValueFromDatabase( + column.name, + column.type, + runQuery, + `SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 1`, + ); + + if (randomValue) { + const completeString = randomValue; + const operator = randomBoolean() ? 'LIKE' : 'NOT LIKE'; + + let start = random(completeString.length - 1); + let end = random(completeString.length - 1); + + if (start > end) [start, end] = [end, start]; + if (start === end && completeString.length > 2) { + if (end === 0) end++; + if (start === completeString.length - 1) start--; + } + + let substring = completeString.substring(start, end); + if (end !== completeString.length - 1) substring += '%'; + if (start !== 0) substring = '%' + substring; + + const value = this.checkForSpecialCharacters(type, substring); + return this.astBuilder.buildWhereConstraint( + operator, + column.tableName, + column.name, + type, + value, + ); + } + return undefined; + } + + private async generateLargerConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValueFromDatabase( + column.name, + column.type, + runQuery, + `SELECT ${column.name} FROM ${schema}.${column.tableName} WHERE ${column.name} NOT IN (SELECT MAX(${column.name}) FROM ${schema}.${column.tableName}) ORDER BY RANDOM() LIMIT 1`, + ); + + if (randomValue) { + const operator = randomBoolean() ? '>' : '>='; + const value = this.checkForSpecialCharacters(type, randomValue); + return this.astBuilder.buildWhereConstraint( + operator, + column.tableName, + column.name, + type, + value, + ); + } + return undefined; + } + + private async generateSmallerConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValueFromDatabase( + column.name, + column.type, + runQuery, + `SELECT ${column.name} FROM ${schema}.${column.tableName} WHERE ${column.name} NOT IN (SELECT MIN(${column.name}) FROM ${schema}.${column.tableName}) ORDER BY RANDOM() LIMIT 1`, + ); + + if (randomValue) { + const operator = randomBoolean() ? '<' : '<='; + const value = this.checkForSpecialCharacters(type, randomValue); + return this.astBuilder.buildWhereConstraint( + operator, + column.tableName, + column.name, + type, + value, + ); + } + return undefined; + } + + private async generateEqualityConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValueFromDatabase( + column.name, + column.type, + runQuery, + `SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 1`, + ); + + if (randomValue) { + const operator = randomBoolean() ? '=' : '!='; + const value = this.checkForSpecialCharacters(type, randomValue); + return this.astBuilder.buildWhereConstraint( + operator, + column.tableName, + column.name, + type, + value, + ); + } + return undefined; + } + + private async generateBetweenConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValuesFromDatabase( + column, + runQuery, + `WITH selected_rows AS ( SELECT ${column.name}, LEAD(${column.name}) OVER (ORDER BY RANDOM()) as next_value, LAG(${column.name}) OVER (ORDER BY RANDOM()) as prev_value FROM ${schema}.${column.tableName} ) SELECT ${column.name} FROM selected_rows WHERE ${column.name} IS DISTINCT FROM next_value AND ${column.name} IS DISTINCT FROM prev_value ORDER BY RANDOM() LIMIT 2;`, + ); + + if (randomValue && randomValue.length > 1) { + const valueList = []; + for (let i = 0; i < 2; i++) { + const val = this.checkForSpecialCharacters(type, randomValue[i]); + valueList.push({ type: type, value: val }); + } + return this.astBuilder.buildWhereConstraintValueList( + 'BETWEEN', + column.tableName, + column.name, + valueList, + ); + } + return undefined; + } + + private async generateINConstraint( + column: IParsedColumn, + type: string, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const randomValue = await this.getRandomValuesFromDatabase( + column, + runQuery, + `WITH row_count AS ( SELECT COUNT(*) AS total_rows FROM ${schema}.${column.tableName} ), random_selection AS ( SELECT ${column.name} FROM ${schema}.${column.tableName} ORDER BY RANDOM() LIMIT 4 ) SELECT ${column.name} FROM random_selection WHERE (SELECT total_rows FROM row_count) >= 2 LIMIT LEAST((SELECT total_rows FROM row_count), 4);`, + ); + + if (randomValue && randomValue.length > 1) { + const valueList: any[] = []; + randomValue.forEach((val: any) => { + const value = this.checkForSpecialCharacters(type, val); + valueList.push({ type: type, value: value }); + }); + return this.astBuilder.buildWhereConstraintValueList( + 'IN', + column.tableName, + column.name, + valueList, + ); + } + return undefined; + } + + private generateBooleanConstraint(column: IParsedColumn): Expr { + const value = randomBoolean() ? true : false; + return this.astBuilder.buildWhereConstraint( + 'IS', + column.tableName, + column.name, + 'bool', + value, + ); + } + + async generateHavingPredicate( + column: IParsedColumn, + runQuery: RowQueryFn, + schema: string, + ): Promise { + if (!column.aggregation) + throw new Error( + 'Error in selecting aggregation type for Having predicate', + ); + + const type = numericTypes.includes(column.type) + ? 'number' + : 'single_quote_string'; + const operators = ['>', '>=', '<', '<=', '=', '!=']; + const operator = operators[random(operators.length)]; + + let randomValue = await this.getRandomValueFromDatabase( + 'aggvalue', + column.type, + runQuery, + `SELECT ${column.aggregation}(${column.name}) AS aggvalue FROM ${schema}.${column.tableName}`, + true, + ); + + if (randomValue) { + if (dateTypes.includes(column.type) && column.aggregation !== 'COUNT') { + const date = new Date(randomValue); + randomValue = date.toISOString().split('T')[0]; + } + + const value = this.checkForSpecialCharacters(type, randomValue); + this.astBuilder.buildHaving( + operator, + column.aggregation, + column.tableName, + column.name, + value, + type, + ); + } else { + throw new Error('Error in finding a Random Value for Having'); + } + } + + private async generatePredicateByOperationType( + column: IParsedColumn, + operation: operationType, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const type = numericTypes.includes(column.type) + ? 'number' + : 'single_quote_string'; + switch (operation) { + case 'EQUAL': + return this.generateEqualityConstraint(column, type, runQuery, schema); + case 'COMPARISON': + return randomBoolean() + ? this.generateLargerConstraint(column, type, runQuery, schema) + : this.generateSmallerConstraint(column, type, runQuery, schema); + case 'IN': + return this.generateINConstraint(column, type, runQuery, schema); + case 'IS_NULL': + return this.generateNullableConstraint(column); + case 'LIKE': + return this.generateLikeConstraint(column, type, runQuery, schema); + case 'BETWEEN': + return this.generateBetweenConstraint(column, type, runQuery, schema); + case 'IS_BOOLEAN': + return this.generateBooleanConstraint(column); + } + } + + private async generatePredicateByColumnType( + column: IParsedColumn, + runQuery: RowQueryFn, + schema: string, + ): Promise { + const textFunctions = [ + this.generateLargerConstraint.bind(this), + this.generateSmallerConstraint.bind(this), + this.generateEqualityConstraint.bind(this), + this.generateBetweenConstraint.bind(this), + this.generateINConstraint.bind(this), + this.generateLikeConstraint.bind(this), + ]; + const numberFunctions = [ + this.generateLargerConstraint.bind(this), + this.generateSmallerConstraint.bind(this), + this.generateEqualityConstraint.bind(this), + this.generateBetweenConstraint.bind(this), + this.generateINConstraint.bind(this), + ]; + const dateFunctions = [ + this.generateLargerConstraint.bind(this), + this.generateSmallerConstraint.bind(this), + this.generateEqualityConstraint.bind(this), + this.generateBetweenConstraint.bind(this), + this.generateINConstraint.bind(this), + ]; + + switch (true) { + case column.isNullable && randomBoolean(): + return this.generateNullableConstraint(column); + case textTypes.includes(column.type): { + const fn = textFunctions[random(textFunctions.length)]; + return await fn(column, 'single_quote_string', runQuery, schema); + } + case numericTypes.includes(column.type): { + const fn = numberFunctions[random(numberFunctions.length)]; + return await fn(column, 'number', runQuery, schema); + } + case dateTypes.includes(column.type): { + const fn = dateFunctions[random(dateFunctions.length)]; + return await fn(column, 'single_quote_string', runQuery, schema); + } + case booleanTypes.includes(column.type): + return this.generateBooleanConstraint(column); + default: + return undefined; + } + } + + private checkForSpecialCharacters(type: string, value: any): any { + if (type === 'single_quote_string' && typeof value === 'string') { + return value.replace("'", "''"); + } + return value; + } + + private async getRandomValueFromDatabase( + columnName: string, + columnType: string, + runQuery: RowQueryFn, + query: string, + isHaving = false, + ): Promise { + const randomValue = await runQuery(query); + + if (randomValue && randomValue.length > 0) { + const value = randomValue[0][`${columnName}`]; + + if (!isHaving && value && dateTypes.includes(columnType)) { + const date = new Date(value); + return date.toISOString().split('T')[0]; + } + + return value; + } + return undefined; + } + + private async getRandomValuesFromDatabase( + column: IParsedColumn, + runQuery: RowQueryFn, + query: string, + ): Promise { + const randomValue = await runQuery(query); + + if (randomValue && randomValue.length > 0) { + const formattedValues = []; + for (let i = 0; i < randomValue.length; i++) { + const value = randomValue[i][`${column.name}`]; + + if (value && dateTypes.includes(column.type)) { + const date = new Date(value); + formattedValues.push(date.toISOString().split('T')[0]); + } else { + formattedValues.push(value); + } + } + return formattedValues; + } + return undefined; + } } diff --git a/services/sql-assessment-service/src/generation/query/query-generation-director.ts b/services/sql-assessment-service/src/generation/query/query-generation-director.ts index 96b108c..eeb06e8 100644 --- a/services/sql-assessment-service/src/generation/query/query-generation-director.ts +++ b/services/sql-assessment-service/src/generation/query/query-generation-director.ts @@ -1,18 +1,21 @@ import { AST } from 'node-sql-parser'; import { ASTBuilder } from './ast-builder'; -import { IParsedTable, ITaskConfiguration } from '../../shared/interfaces/domain'; -import { DataSource } from 'typeorm'; +import { + IParsedTable, + ITaskConfiguration, +} from '../../shared/interfaces/domain'; +import { RowQueryFn } from '../../shared/utils/database-utils'; export interface QueryGenerationDirector { - astBuilder: ASTBuilder; - buildQuery( - context: ITaskConfiguration, - metadata: IParsedTable[], - filteredTables: IParsedTable[], - datasource: DataSource, - schema: string, - shuffleCounter: number - ): Promise<[string, AST]>; + astBuilder: ASTBuilder; + buildQuery( + context: ITaskConfiguration, + metadata: IParsedTable[], + filteredTables: IParsedTable[], + runQuery: RowQueryFn, + schema: string, + shuffleCounter: number, + ): Promise<[string, AST]>; - validateConfiguration(configuration: any): [boolean, string]; + validateConfiguration(configuration: any): [boolean, string]; } diff --git a/services/sql-assessment-service/src/generation/query/select-query-generation-director.ts b/services/sql-assessment-service/src/generation/query/select-query-generation-director.ts index db21abd..02f91a6 100644 --- a/services/sql-assessment-service/src/generation/query/select-query-generation-director.ts +++ b/services/sql-assessment-service/src/generation/query/select-query-generation-director.ts @@ -1,4 +1,3 @@ -import { DataSource } from 'typeorm'; import { aggregateColumnType, joinType, @@ -16,7 +15,7 @@ import { QueryGenerationDirector } from './query-generation-director'; import { SelectASTBuilder } from './select-ast-builder'; import { PredicateGenerationService } from './predicate-generation-service'; import { AST, ColumnRefItem, Parser, Select } from 'node-sql-parser'; -import { createQueryRunner } from '../../shared/utils/database-utils'; +import { RowQueryFn } from '../../shared/utils/database-utils'; import { isValidForAggregation } from '../../shared/utils/validation'; import { random, randomBoolean, shuffle } from '../../shared/utils/random'; @@ -34,7 +33,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context: ITaskConfiguration, metadata: IParsedTable[], filteredTables: IParsedTable[], - datasource: DataSource, + runQuery: RowQueryFn, schema: string, shuffleCounter: number, ): Promise<[string, AST]> { @@ -81,7 +80,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); @@ -99,7 +98,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); @@ -132,7 +131,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); @@ -142,7 +141,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { const constraints = await this.predicateService.generatePredicates( mergedColumns, context.predicateCount, - datasource, + runQuery, schema, context.operationTypes, ); @@ -153,7 +152,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); @@ -165,7 +164,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { groupByColumn = context.having ? await this.predicateService.generateHavingClauseAndReturnGroupByColumn( mergedColumns, - datasource, + runQuery, schema, ) : this.predicateService.generateGroupByClauseAndReturnColumn( @@ -177,7 +176,7 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); @@ -225,19 +224,8 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { const ast: Select = this.astBuilder.getGeneratedAST(); const query = parser.sqlify(ast, { database: 'postgresql' }); - const queryRunner = createQueryRunner(datasource); - if (!queryRunner) { - console.log( - 'No database connection, please establish a database connection', - ); - throw new Error( - 'No database connection, please establish a database connection', - ); - } - try { - const result = await queryRunner.query(query); - queryRunner.release(); + const result = await runQuery(query); console.log('Successful execution of generated query'); if (result.length <= 0) { @@ -247,14 +235,13 @@ export class SelectQueryGenerationDirector implements QueryGenerationDirector { context, metadata, filteredTables, - datasource, + runQuery, schema, shuffleCounter + 1, ); } } catch (error) { console.log('Unable to execute generated query.'); - queryRunner.release(); throw error; } diff --git a/services/sql-assessment-service/src/generation/query/sql-query-generation-service.ts b/services/sql-assessment-service/src/generation/query/sql-query-generation-service.ts index 95f4cc1..1779259 100644 --- a/services/sql-assessment-service/src/generation/query/sql-query-generation-service.ts +++ b/services/sql-assessment-service/src/generation/query/sql-query-generation-service.ts @@ -1,78 +1,101 @@ -import { DataSource } from 'typeorm'; import { AST } from 'node-sql-parser'; -import { ITaskConfiguration, IParsedTable } from '../../shared/interfaces/domain'; -import { databaseMetadata, selfJoinDatabaseMetadata } from '../../database/internal-memory'; +import { + ITaskConfiguration, + IParsedTable, +} from '../../shared/interfaces/domain'; +import { + databaseMetadata, + selfJoinDatabaseMetadata, +} from '../../database/internal-memory'; import { QueryGenerationDirector } from './query-generation-director'; +import { RowQueryFn } from '../../shared/utils/database-utils'; export class SQLQueryGenerationService { - queryGenerationDirector: QueryGenerationDirector; + queryGenerationDirector: QueryGenerationDirector; - constructor(queryGenerationDirector: QueryGenerationDirector) { - this.queryGenerationDirector = queryGenerationDirector; - } + constructor(queryGenerationDirector: QueryGenerationDirector) { + this.queryGenerationDirector = queryGenerationDirector; + } - public validateConfiguration(config: any): [boolean, string] { - return this.queryGenerationDirector.validateConfiguration(config); - } + public validateConfiguration(config: any): [boolean, string] { + return this.queryGenerationDirector.validateConfiguration(config); + } - public async generateContextBasedQuery( - context: ITaskConfiguration, - databasekey: string, - datasource: DataSource, - schema: string - ): Promise<[string, AST]> { - const metadata = context.joinTypes.includes('SELF JOIN') - ? this.getSelfJoinMetadata(databasekey) - : this.getMetadata(databasekey); + public async generateContextBasedQuery( + context: ITaskConfiguration, + databasekey: string, + runQuery: RowQueryFn, + schema: string, + ): Promise<[string, AST]> { + const metadata = context.joinTypes.includes('SELF JOIN') + ? this.getSelfJoinMetadata(databasekey) + : this.getMetadata(databasekey); - const filteredTables = this.filterTables(context, metadata); - if (!filteredTables || filteredTables.length < 1) { - console.log('Invalid Configuration: Database does not meet required join depth, please adjust the configuration'); - throw new Error('Invalid Configuration: Database does not meet required join depth, please adjust the configuration'); - } + const filteredTables = this.filterTables(context, metadata); + if (!filteredTables || filteredTables.length < 1) { + console.log( + 'Invalid Configuration: Database does not meet required join depth, please adjust the configuration', + ); + throw new Error( + 'Invalid Configuration: Database does not meet required join depth, please adjust the configuration', + ); + } - return this.queryGenerationDirector.buildQuery(context, metadata, filteredTables, datasource, schema, 0); - } + return this.queryGenerationDirector.buildQuery( + context, + metadata, + filteredTables, + runQuery, + schema, + 0, + ); + } - private filterTables(context: ITaskConfiguration, metadata: IParsedTable[]): IParsedTable[] { - return context.joinDepth == 0 ? metadata : metadata - .map(table => { - const filteredPaths = table.joinPaths.filter( - joinPath => context.joinTypes.includes('SELF JOIN') - ? joinPath.depth >= context.joinDepth && joinPath.selfJoinDepth <= context.joinDepth - : joinPath.depth >= context.joinDepth - ); + private filterTables( + context: ITaskConfiguration, + metadata: IParsedTable[], + ): IParsedTable[] { + return context.joinDepth == 0 + ? metadata + : metadata + .map((table) => { + const filteredPaths = table.joinPaths.filter((joinPath) => + context.joinTypes.includes('SELF JOIN') + ? joinPath.depth >= context.joinDepth && + joinPath.selfJoinDepth <= context.joinDepth + : joinPath.depth >= context.joinDepth, + ); - return filteredPaths.length > 0 - ? { ...table, joinPaths: filteredPaths } - : { ...table, joinPaths: [] }; - }) - .filter(table => table.joinPaths.length >= 1); - } + return filteredPaths.length > 0 + ? { ...table, joinPaths: filteredPaths } + : { ...table, joinPaths: [] }; + }) + .filter((table) => table.joinPaths.length >= 1); + } - private getMetadata(databasekey: string): IParsedTable[] { - const metadata = databaseMetadata.get(databasekey); - if (!metadata) { - console.log('No metadata found, please register database'); - throw new Error('No metadata found, please register database'); - } - if (metadata.length < 1) { - console.log('Database with empty tables'); - throw new Error('Database with empty tables'); - } - return metadata; - } + private getMetadata(databasekey: string): IParsedTable[] { + const metadata = databaseMetadata.get(databasekey); + if (!metadata) { + console.log('No metadata found, please register database'); + throw new Error('No metadata found, please register database'); + } + if (metadata.length < 1) { + console.log('Database with empty tables'); + throw new Error('Database with empty tables'); + } + return metadata; + } - private getSelfJoinMetadata(databasekey: string): IParsedTable[] { - const metadata = selfJoinDatabaseMetadata.get(databasekey); - if (!metadata) { - console.log('No metadata found, please register database'); - throw new Error('No metadata found, please register database'); - } - if (metadata.length < 1) { - console.log('Database with empty tables'); - throw new Error('Database with empty tables'); - } - return metadata; - } + private getSelfJoinMetadata(databasekey: string): IParsedTable[] { + const metadata = selfJoinDatabaseMetadata.get(databasekey); + if (!metadata) { + console.log('No metadata found, please register database'); + throw new Error('No metadata found, please register database'); + } + if (metadata.length < 1) { + console.log('Database with empty tables'); + throw new Error('Database with empty tables'); + } + return metadata; + } } diff --git a/services/sql-assessment-service/src/generation/task-generation-controller.ts b/services/sql-assessment-service/src/generation/task-generation-controller.ts index d5dbac7..a918fed 100644 --- a/services/sql-assessment-service/src/generation/task-generation-controller.ts +++ b/services/sql-assessment-service/src/generation/task-generation-controller.ts @@ -11,14 +11,18 @@ import { import { connectToDatabase, generateDatabaseKey, + generatePGliteKey, + makeRowQueryFn, } from '../shared/utils/database-utils'; import { isDatabaseRegistered, validateConnectionInfo, } from '../shared/utils/validation'; +import { pgliteInstances } from '../database/internal-memory'; import { SQLQueryGenerationService } from './query/sql-query-generation-service'; import { TaskDescriptionGenerationService } from './description/task-description-generation-service'; -import { t, resolveLanguageCode } from '../shared/i18n'; +import { t, resolveLanguageCode, SupportedLanguage } from '../shared/i18n'; +import { DatabaseService } from '../database/database-service'; /** * @openapi @@ -66,13 +70,16 @@ export class TaskGenerationController { public router: Router; selectQueryGenerationService: SQLQueryGenerationService; taskDescriptionGenerationService: TaskDescriptionGenerationService; + private readonly databaseService?: DatabaseService; constructor( selectQueryGenerationService: SQLQueryGenerationService, taskDescriptionGenerationService: TaskDescriptionGenerationService, + databaseService?: DatabaseService, ) { this.selectQueryGenerationService = selectQueryGenerationService; this.taskDescriptionGenerationService = taskDescriptionGenerationService; + this.databaseService = databaseService; this.router = Router(); this.initializeRoutes(); } @@ -110,6 +117,61 @@ export class TaskGenerationController { .json({ message: t('TASK_GENERATION_INVALID_CONNECTION', lang) }); } + // ---- auto-analyze --------------------------------------------------- + if (this.databaseService) { + const analyzed = await this.databaseService.ensureAnalyzed( + connectionInfo, + undefined, + lang, + ); + if (!analyzed.ok) { + return res.status(analyzed.status).json({ message: analyzed.message }); + } + } + // --------------------------------------------------------------------- + + // ---- PGlite branch -------------------------------------------------- + const connectionInfoAny = connectionInfo as any; + if (connectionInfoAny?.type === 'pglite') { + const { databaseId } = connectionInfoAny; + if (!databaseId || typeof databaseId !== 'string') { + return res + .status(400) + .json({ message: t('INVALID_CONNECTION_INFO', lang) }); + } + + const databaseKey = generatePGliteKey(databaseId); + if (!isDatabaseRegistered(databaseKey)) { + return res + .status(400) + .json({ message: t('DATABASE_NOT_REGISTERED', lang) }); + } + + const pgliteDb = pgliteInstances.get(databaseId); + if (!pgliteDb) { + return res + .status(400) + .json({ message: t('DATABASE_NOT_REGISTERED', lang) }); + } + + const runQuery = async (sql: string) => { + const result = await pgliteDb.query(sql); + return result.rows as any[]; + }; + + return this.runGeneration( + res, + taskRequest.taskConfiguration, + databaseKey, + 'public', + runQuery, + lang, + null, + ); + } + // --------------------------------------------------------------------- + + // ---- PostgreSQL branch ---------------------------------------------- const validationError = validateConnectionInfo(connectionInfo, lang); if (validationError) { return res.status(400).json({ message: validationError }); @@ -133,110 +195,133 @@ export class TaskGenerationController { const isConnected = await connectToDatabase(dataSource); if (isConnected) { - const configValidation = - this.selectQueryGenerationService.validateConfiguration(taskContext); - if (!configValidation[0]) { - return res.status(400).json({ message: configValidation[1] }); - } + return this.runGeneration( + res, + taskContext, + databaseKey, + connectionInfo.schema!, + makeRowQueryFn(dataSource), + lang, + dataSource, + ); + } - let query: string; - let ast: any; - try { - [query, ast] = - await this.selectQueryGenerationService.generateContextBasedQuery( - taskContext, - databaseKey, - dataSource, - connectionInfo.schema!, - ); - } catch (error) { - console.log(error); - dataSource.destroy(); - return res.status(500).json({ - message: t('TASK_GENERATION_QUERY_ERROR', lang, String(error)), - }); - } + await dataSource.destroy(); + return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + } - let taskDescription: string | undefined; - let entityDescription: string | undefined; - let creativeDescription: string | undefined; - let schemaBasedDescription: string | undefined; - let semanticNGL: string | undefined; - - try { - const isSelfJoin = taskContext.joinTypes.includes('SELF JOIN'); - taskDescription = - await this.taskDescriptionGenerationService.generateTaskFromQuery({ - generationType: GenerationOptions.Template, - query, - queryAST: ast, - schema: connectionInfo.schema!, - databaseKey, - isSelfJoin, - lang, - }); - entityDescription = - await this.taskDescriptionGenerationService.generateTaskFromQuery({ - generationType: GenerationOptions.LLM, - query, - queryAST: ast, - schema: connectionInfo.schema!, - databaseKey, - isSelfJoin, - option: GptOptions.MultiStep, - lang, - }); - creativeDescription = - await this.taskDescriptionGenerationService.generateTaskFromQuery({ - generationType: GenerationOptions.LLM, - query, - queryAST: ast, - schema: connectionInfo.schema!, - databaseKey, - isSelfJoin, - option: GptOptions.Creative, - lang, - }); - schemaBasedDescription = - await this.taskDescriptionGenerationService.generateTaskFromQuery({ - generationType: GenerationOptions.LLM, - query, - queryAST: ast, - schema: connectionInfo.schema!, - databaseKey, - isSelfJoin, - option: GptOptions.Default, - lang, - }); - semanticNGL = - await this.taskDescriptionGenerationService.generateTaskFromQuery({ - generationType: GenerationOptions.Hybrid, - query, - queryAST: ast, - schema: connectionInfo.schema!, - databaseKey, - isSelfJoin, - lang, - }); - } catch (error) { - console.log('Error in task description generation', error); - return res - .status(500) - .json({ message: t('TASK_GENERATION_DESCRIPTION_ERROR', lang) }); - } + private async runGeneration( + res: Response, + taskContext: ITaskConfiguration, + databaseKey: string, + schema: string, + runQuery: (sql: string) => Promise, + lang: SupportedLanguage, + dataSource: DataSource | null, + ): Promise { + const configValidation = + this.selectQueryGenerationService.validateConfiguration(taskContext); + if (!configValidation[0]) { + if (dataSource) await dataSource.destroy(); + return res.status(400).json({ message: configValidation[1] }); + } - const taskResponse: TaskResponse = { - query: query, - templateBasedDescription: taskDescription!, - gptEntityRelationshipDescription: entityDescription!, - gptSchemaBasedDescription: schemaBasedDescription!, - hybridDescription: semanticNGL!, - gptCreativeDescription: creativeDescription, - }; - await dataSource.destroy(); - return res.status(200).json(taskResponse); + let query: string; + let ast: any; + try { + [query, ast] = + await this.selectQueryGenerationService.generateContextBasedQuery( + taskContext, + databaseKey, + runQuery, + schema, + ); + } catch (error) { + console.log(error); + if (dataSource) await dataSource.destroy(); + return res.status(500).json({ + message: t('TASK_GENERATION_QUERY_ERROR', lang, String(error)), + }); } - return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + let taskDescription: string | undefined; + let entityDescription: string | undefined; + let creativeDescription: string | undefined; + let schemaBasedDescription: string | undefined; + let semanticNGL: string | undefined; + + try { + const isSelfJoin = taskContext.joinTypes.includes('SELF JOIN'); + taskDescription = + await this.taskDescriptionGenerationService.generateTaskFromQuery({ + generationType: GenerationOptions.Template, + query, + queryAST: ast, + schema, + databaseKey, + isSelfJoin, + lang, + }); + entityDescription = + await this.taskDescriptionGenerationService.generateTaskFromQuery({ + generationType: GenerationOptions.LLM, + query, + queryAST: ast, + schema, + databaseKey, + isSelfJoin, + option: GptOptions.MultiStep, + lang, + }); + creativeDescription = + await this.taskDescriptionGenerationService.generateTaskFromQuery({ + generationType: GenerationOptions.LLM, + query, + queryAST: ast, + schema, + databaseKey, + isSelfJoin, + option: GptOptions.Creative, + lang, + }); + schemaBasedDescription = + await this.taskDescriptionGenerationService.generateTaskFromQuery({ + generationType: GenerationOptions.LLM, + query, + queryAST: ast, + schema, + databaseKey, + isSelfJoin, + option: GptOptions.Default, + lang, + }); + semanticNGL = + await this.taskDescriptionGenerationService.generateTaskFromQuery({ + generationType: GenerationOptions.Hybrid, + query, + queryAST: ast, + schema, + databaseKey, + isSelfJoin, + lang, + }); + } catch (error) { + console.log('Error in task description generation', error); + if (dataSource) await dataSource.destroy(); + return res + .status(500) + .json({ message: t('TASK_GENERATION_DESCRIPTION_ERROR', lang) }); + } + + const taskResponse: TaskResponse = { + query: query, + templateBasedDescription: taskDescription!, + gptEntityRelationshipDescription: entityDescription!, + gptSchemaBasedDescription: schemaBasedDescription!, + hybridDescription: semanticNGL!, + gptCreativeDescription: creativeDescription, + }; + if (dataSource) await dataSource.destroy(); + return res.status(200).json(taskResponse); } } diff --git a/services/sql-assessment-service/src/grading/grading-controller.ts b/services/sql-assessment-service/src/grading/grading-controller.ts index 28da03e..beac2e7 100644 --- a/services/sql-assessment-service/src/grading/grading-controller.ts +++ b/services/sql-assessment-service/src/grading/grading-controller.ts @@ -28,6 +28,7 @@ import { ProximityHeuristic, QueryProximityService, } from './query-proximity-service'; +import { DatabaseService } from '../database/database-service'; // --------------------------------------------------------------------------- // Internal helper types @@ -217,6 +218,7 @@ export class GradingController { private readonly astComparator: ASTComparator, private readonly executionPlanComparator: ExecutionPlanComparator, private readonly queryProximityService: QueryProximityService = new QueryProximityService(), + private readonly databaseService?: DatabaseService, ) { this.router = Router(); this.initializeRoutes(); @@ -280,6 +282,29 @@ export class GradingController { return null; } + // ---- auto-analyze --------------------------------------------------- + if (this.databaseService) { + const analyzed = await this.databaseService.ensureAnalyzed( + body.connectionInfo, + undefined, + lang, + ); + if (!analyzed.ok) { + res.status(analyzed.status).json({ message: analyzed.message }); + return null; + } + } + // --------------------------------------------------------------------- + + // ---- PGlite branch -------------------------------------------------- + if ((body.connectionInfo as any)?.type === 'pglite') { + res + .status(400) + .json({ message: t('GRADING_PGLITE_NOT_SUPPORTED', lang) }); + return null; + } + // --------------------------------------------------------------------- + const validationError = validateConnectionInfo(body.connectionInfo, lang); if (validationError) { res.status(400).json({ message: validationError }); @@ -376,6 +401,27 @@ export class GradingController { .json({ message: t('GRADING_CONNECTION_READ_ERROR', lang) }); } + // ---- auto-analyze --------------------------------------------------- + if (this.databaseService) { + const analyzed = await this.databaseService.ensureAnalyzed( + connectionInfo, + undefined, + lang, + ); + if (!analyzed.ok) { + return res.status(analyzed.status).json({ message: analyzed.message }); + } + } + // --------------------------------------------------------------------- + + // ---- PGlite branch -------------------------------------------------- + if ((connectionInfo as any)?.type === 'pglite') { + return res + .status(400) + .json({ message: t('GRADING_PGLITE_NOT_SUPPORTED', lang) }); + } + // --------------------------------------------------------------------- + const validationError = validateConnectionInfo(connectionInfo, lang); if (validationError) { return res.status(400).json({ message: validationError }); diff --git a/services/sql-assessment-service/src/index.ts b/services/sql-assessment-service/src/index.ts index 26e41ab..ce21eae 100644 --- a/services/sql-assessment-service/src/index.ts +++ b/services/sql-assessment-service/src/index.ts @@ -1,66 +1,22 @@ -import 'reflect-metadata'; -import { DatabaseAnalyzer } from './database/database-analyzer'; -import { DatabaseController } from './database/database-controller'; -import { SQLQueryGradingService } from './grading/query-grading-service'; -import { GradingController } from './grading/grading-controller'; -import { ResultSetComparator } from './grading/result-set-comparator'; -import { ASTComparator } from './grading/comparators/ast-comparator'; -import { ExecutionPlanComparator } from './grading/comparators/execution-plan-comparator'; -import { ExecutionPlanParser } from './grading/execution-plan-parser'; -import { JoinComparator } from './grading/join-comparator'; -import { FeedbackAssembler } from './grading/feedback/feedback-assembler'; -import { GradeCalculator } from './grading/grading/grade-calculator'; -import { SQLQueryGenerationService } from './generation/query/sql-query-generation-service'; -import { SelectQueryGenerationDirector } from './generation/query/select-query-generation-director'; -import { TemplateTaskDescriptionGenerationEngine } from './generation/description/template-task-description-generation-engine'; -import { LLMTaskDescriptionGenerationEngine } from './generation/description/llm-task-description-generation-engine'; -import { TaskDescriptionGenerationService } from './generation/description/task-description-generation-service'; -import { TaskGenerationController } from './generation/task-generation-controller'; -import { DescriptionController } from './generation/description/description-controller'; -import { QueryExecutionController } from './query/query-execution-controller'; -import { QueryExecutionService } from './query/query-execution-service'; -import { registerControllers, startRestApi } from './api/rest-api'; - -const databaseAnalyzer = new DatabaseAnalyzer(); -const connectionController = new DatabaseController(databaseAnalyzer); +import dotenv from 'dotenv'; +dotenv.config(); -const selectQueryGenerator = new SQLQueryGenerationService(new SelectQueryGenerationDirector()); -const templateTaskDescriptionGenerationEngine = new TemplateTaskDescriptionGenerationEngine(); -const llmTaskDescriptionGenerationEngine = process.env.OPENAI_API_KEY - ? new LLMTaskDescriptionGenerationEngine() - : undefined; -const taskDescriptionGenerationService = new TaskDescriptionGenerationService( - llmTaskDescriptionGenerationEngine, - templateTaskDescriptionGenerationEngine -); -const taskGenerationController = new TaskGenerationController(selectQueryGenerator, taskDescriptionGenerationService); +import { createControllers } from './bootstrap'; +import { registerControllers, startRestApi } from './api/rest-api'; -// ── Grading dependencies ────────────────────────────────────────────────── -const joinComparator = new JoinComparator(); -const resultSetComparator = new ResultSetComparator(); -const astComparator = new ASTComparator(joinComparator); -const executionPlanComparator = new ExecutionPlanComparator(new ExecutionPlanParser(), joinComparator); -const gradeCalculator = new GradeCalculator(); -const feedbackAssembler = new FeedbackAssembler(); +const { + connectionController, + taskGenerationController, + gradingController, + descriptionController, + queryExecutionController, +} = createControllers(); -const queryGradingService = new SQLQueryGradingService( - resultSetComparator, - astComparator, - executionPlanComparator, - gradeCalculator, - feedbackAssembler +registerControllers( + connectionController, + taskGenerationController, + gradingController, + descriptionController, + queryExecutionController, ); -const gradingController = new GradingController( - queryGradingService, - taskDescriptionGenerationService, - resultSetComparator, - astComparator, - executionPlanComparator -); - -const descriptionController = new DescriptionController(taskDescriptionGenerationService); -const queryExecutionService = new QueryExecutionService(); -const queryExecutionController = new QueryExecutionController(queryExecutionService); - -registerControllers(connectionController, taskGenerationController, gradingController, descriptionController, queryExecutionController); startRestApi(); diff --git a/services/sql-assessment-service/src/query/query-execution-controller.ts b/services/sql-assessment-service/src/query/query-execution-controller.ts index 423381e..13b64e8 100644 --- a/services/sql-assessment-service/src/query/query-execution-controller.ts +++ b/services/sql-assessment-service/src/query/query-execution-controller.ts @@ -1,10 +1,22 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { DataSource } from 'typeorm'; -import { connectToDatabase, generateDatabaseKey } from '../shared/utils/database-utils'; -import { isDatabaseRegistered, validateConnectionInfo } from '../shared/utils/validation'; +import { + connectToDatabase, + generateDatabaseKey, + generatePGliteKey, +} from '../shared/utils/database-utils'; +import { + isDatabaseRegistered, + validateConnectionInfo, +} from '../shared/utils/validation'; import { IRequestQueryOptions } from '../shared/interfaces/http'; -import { QueryExecutionError, QueryExecutionService } from './query-execution-service'; -import { t, resolveLanguageCode } from '../shared/i18n'; +import { + QueryExecutionError, + QueryExecutionService, +} from './query-execution-service'; +import { t, resolveLanguageCode, SupportedLanguage } from '../shared/i18n'; +import { pgliteInstances } from '../database/internal-memory'; +import { DatabaseService } from '../database/database-service'; /** * Exposes a single endpoint for executing a raw SQL SELECT query against a @@ -66,90 +78,198 @@ import { t, resolveLanguageCode } from '../shared/i18n'; * $ref: '#/components/schemas/ErrorResponse' */ export class QueryExecutionController { - public router: Router; - private readonly queryExecutionService: QueryExecutionService; - - constructor(queryExecutionService: QueryExecutionService) { - this.queryExecutionService = queryExecutionService; - this.router = Router(); - this.initializeRoutes(); - } - - private initializeRoutes(): void { - this.router.post('/execute', (req: Request, res: Response) => { - this.executeQuery(req, res); - }); - } - - public async executeQuery(req: Request, res: Response): Promise { - let options: IRequestQueryOptions; - - try { - options = req.body as IRequestQueryOptions; - } catch { - // Language unknown at this point — fall back to default. - return res.status(400).json({ message: t('INVALID_REQUEST_BODY', 'en') }); - } - - const lang = resolveLanguageCode(options?.languageCode); - - if (!options?.connectionInfo) { - return res.status(400).json({ message: t('MISSING_CONNECTION_INFO', lang) }); - } - - if (!options.query || typeof options.query !== 'string' || options.query.trim() === '') { - return res.status(400).json({ message: t('MISSING_OR_EMPTY_QUERY', lang) }); - } - - const validationError = validateConnectionInfo(options.connectionInfo, lang); - if (validationError) { - return res.status(400).json({ message: validationError }); - } - - const { host, port, schema } = options.connectionInfo; - const databaseKey = generateDatabaseKey(host!, port!, schema!); - - if (!isDatabaseRegistered(databaseKey)) { - return res.status(400).json({ message: t('DATABASE_NOT_REGISTERED', lang) }); - } - - let dataSource: DataSource; - let isConnected: boolean; - try { - dataSource = new DataSource(options.connectionInfo); - isConnected = await connectToDatabase(dataSource); - } catch { - return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); - } - - if (!isConnected) { - return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); - } - - try { - const result = await this.queryExecutionService.executeQuery( - options.query, - dataSource, - lang - ); - await dataSource.destroy(); - return res.status(200).json(result); - } catch (err) { - await dataSource.destroy(); - - if (err instanceof QueryExecutionError) { - const clientCodes: QueryExecutionError['code'][] = [ - 'EMPTY_QUERY', - 'PARSE_ERROR', - 'MULTIPLE_STATEMENTS', - 'NON_SELECT', - ]; - const status = clientCodes.includes(err.code) ? 400 : 500; - return res.status(status).json({ message: err.message, code: err.code }); - } - - console.error('Unexpected error in query execution', err); - return res.status(500).json({ message: t('QUERY_UNEXPECTED_ERROR', lang, String(err)) }); - } - } + public router: Router; + private readonly queryExecutionService: QueryExecutionService; + private readonly databaseService?: DatabaseService; + + constructor( + queryExecutionService: QueryExecutionService, + databaseService?: DatabaseService, + ) { + this.queryExecutionService = queryExecutionService; + this.databaseService = databaseService; + this.router = Router(); + this.initializeRoutes(); + } + + private initializeRoutes(): void { + this.router.post( + '/execute', + (req: Request, res: Response, next: NextFunction) => { + this.executeQuery(req, res).catch(next); + }, + ); + } + + public async executeQuery(req: Request, res: Response): Promise { + let options: IRequestQueryOptions; + + try { + options = req.body as IRequestQueryOptions; + } catch { + // Language unknown at this point — fall back to default. + return res.status(400).json({ message: t('INVALID_REQUEST_BODY', 'en') }); + } + + const lang = resolveLanguageCode(options?.languageCode); + + if (!options?.connectionInfo) { + return res + .status(400) + .json({ message: t('MISSING_CONNECTION_INFO', lang) }); + } + + if ( + !options.query || + typeof options.query !== 'string' || + options.query.trim() === '' + ) { + return res + .status(400) + .json({ message: t('MISSING_OR_EMPTY_QUERY', lang) }); + } + + // ---- auto-analyze --------------------------------------------------- + if (this.databaseService) { + const analyzed = await this.databaseService.ensureAnalyzed( + options.connectionInfo, + undefined, + lang, + ); + if (!analyzed.ok) { + return res.status(analyzed.status).json({ message: analyzed.message }); + } + } + // --------------------------------------------------------------------- + + // ---- PGlite branch ------------------------------------------------ + if ((options.connectionInfo as any)?.type === 'pglite') { + return this.executeQueryOnPGlite( + options.connectionInfo as any, + options.query, + lang, + res, + ); + } + // ------------------------------------------------------------------- + + const validationError = validateConnectionInfo( + options.connectionInfo, + lang, + ); + if (validationError) { + return res.status(400).json({ message: validationError }); + } + + const { host, port, schema } = options.connectionInfo; + const databaseKey = generateDatabaseKey(host!, port!, schema!); + + if (!isDatabaseRegistered(databaseKey)) { + return res + .status(400) + .json({ message: t('DATABASE_NOT_REGISTERED', lang) }); + } + + let dataSource: DataSource; + let isConnected: boolean; + try { + dataSource = new DataSource(options.connectionInfo); + isConnected = await connectToDatabase(dataSource); + } catch { + return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + } + + if (!isConnected) { + return res.status(400).json({ message: t('UNABLE_TO_CONNECT', lang) }); + } + + try { + const result = await this.queryExecutionService.executeQuery( + options.query, + dataSource, + lang, + ); + await dataSource.destroy(); + return res.status(200).json(result); + } catch (err) { + await dataSource.destroy(); + + if (err instanceof QueryExecutionError) { + const clientCodes: QueryExecutionError['code'][] = [ + 'EMPTY_QUERY', + 'PARSE_ERROR', + 'MULTIPLE_STATEMENTS', + 'NON_SELECT', + ]; + const status = clientCodes.includes(err.code) ? 400 : 500; + return res + .status(status) + .json({ message: err.message, code: err.code }); + } + + console.error('Unexpected error in query execution', err); + return res + .status(500) + .json({ message: t('QUERY_UNEXPECTED_ERROR', lang, String(err)) }); + } + } + + // ------------------------------------------------------------------------- + // PGlite helpers + // ------------------------------------------------------------------------- + + private async executeQueryOnPGlite( + connectionInfo: any, + query: string, + lang: SupportedLanguage, + res: Response, + ): Promise { + const { databaseId } = connectionInfo ?? {}; + + if (!databaseId || typeof databaseId !== 'string') { + return res + .status(400) + .json({ message: t('INVALID_CONNECTION_INFO', lang) }); + } + + const key = generatePGliteKey(databaseId); + if (!isDatabaseRegistered(key)) { + return res + .status(400) + .json({ message: t('DATABASE_NOT_REGISTERED', lang) }); + } + + const db = pgliteInstances.get(databaseId); + if (!db) { + return res + .status(400) + .json({ message: t('DATABASE_NOT_REGISTERED', lang) }); + } + + try { + const result = await this.queryExecutionService.executeQueryOnPGlite( + query, + db, + lang, + ); + return res.status(200).json(result); + } catch (err) { + if (err instanceof QueryExecutionError) { + const clientCodes: QueryExecutionError['code'][] = [ + 'EMPTY_QUERY', + 'PARSE_ERROR', + 'MULTIPLE_STATEMENTS', + 'NON_SELECT', + ]; + const status = clientCodes.includes(err.code) ? 400 : 500; + return res + .status(status) + .json({ message: err.message, code: err.code }); + } + console.error('Unexpected error in PGlite query execution', err); + return res + .status(500) + .json({ message: t('QUERY_UNEXPECTED_ERROR', lang, String(err)) }); + } + } } diff --git a/services/sql-assessment-service/src/query/query-execution-service.ts b/services/sql-assessment-service/src/query/query-execution-service.ts index b9ac4a2..e4a4bce 100644 --- a/services/sql-assessment-service/src/query/query-execution-service.ts +++ b/services/sql-assessment-service/src/query/query-execution-service.ts @@ -60,6 +60,43 @@ export class QueryExecutionService { } } + /** + * Executes the given SQL SELECT query against an in-process PGlite + * database. Enforces the same SELECT-only constraint as `executeQuery`. + * + * @param query - Raw SQL string to execute. + * @param db - Live PGlite instance. + * @param lang - Language for error messages (defaults to 'en'). + * + * @throws {QueryExecutionError} on empty input, non-SELECT statements, or + * database-level failures. + */ + public async executeQueryOnPGlite( + query: string, + db: any, + lang: SupportedLanguage = 'en', + ): Promise { + const trimmed = query.trim(); + + if (!trimmed) { + throw new QueryExecutionError(t('QUERY_EMPTY', lang), 'EMPTY_QUERY'); + } + + this.assertSelectOnly(trimmed, lang); + + try { + const result = await db.query(trimmed); + const rows = result.rows as Record[]; + return { rows, rowCount: rows.length }; + } catch (err) { + const detail = err instanceof Error ? err.message : String(err); + throw new QueryExecutionError( + t('QUERY_EXECUTION_FAILED', lang, detail), + 'EXECUTION_FAILED', + ); + } + } + // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- diff --git a/services/sql-assessment-service/src/shared/i18n/messages.ts b/services/sql-assessment-service/src/shared/i18n/messages.ts index 80bc75d..20c1b5f 100644 --- a/services/sql-assessment-service/src/shared/i18n/messages.ts +++ b/services/sql-assessment-service/src/shared/i18n/messages.ts @@ -23,6 +23,7 @@ export type MessageKey = // ── database controller ────────────────────────────────────────────────── | 'DATABASE_ANALYSIS_SUCCESS' | 'DATABASE_SCHEMA_EXTRACTION_FAILED' + | 'INIT_SQL_READ_ERROR' // ── query execution ────────────────────────────────────────────────────── | 'MISSING_OR_EMPTY_QUERY' | 'QUERY_EMPTY' @@ -36,6 +37,7 @@ export type MessageKey = | 'GRADING_CONNECTION_READ_ERROR' | 'GRADING_FAILED' | 'GRADING_FAILED_WITH_ERROR' + | 'GRADING_PGLITE_NOT_SUPPORTED' // ── grading feedback — result set ───────────────────────────────────────── | 'FEEDBACK_RESULT_SET_MATCH' | 'FEEDBACK_RESULT_SET_MISMATCH' @@ -132,6 +134,8 @@ export const MESSAGES: MessageCatalogue = { // database controller DATABASE_ANALYSIS_SUCCESS: 'Connection successful.', DATABASE_SCHEMA_EXTRACTION_FAILED: 'Unable to extract database schema.', + INIT_SQL_READ_ERROR: 'Failed to read the database initialization script.', + // query execution MISSING_OR_EMPTY_QUERY: 'Missing or empty query string.', QUERY_EMPTY: 'Query must not be empty.', @@ -147,6 +151,8 @@ export const MESSAGES: MessageCatalogue = { GRADING_CONNECTION_READ_ERROR: 'Error reading connection information.', GRADING_FAILED: 'Unable to grade query.', GRADING_FAILED_WITH_ERROR: 'Unable to grade query. Error: {{value}}', + GRADING_PGLITE_NOT_SUPPORTED: + 'Grading endpoints require a PostgreSQL connection; PGlite is not supported here.', // grading feedback — result set FEEDBACK_RESULT_SET_MATCH: 'Same result set of both queries.', FEEDBACK_RESULT_SET_MISMATCH: 'Result sets differ.', @@ -268,6 +274,9 @@ export const MESSAGES: MessageCatalogue = { DATABASE_ANALYSIS_SUCCESS: 'Verbindung erfolgreich.', DATABASE_SCHEMA_EXTRACTION_FAILED: 'Das Datenbankschema konnte nicht extrahiert werden.', + INIT_SQL_READ_ERROR: + 'Das Initialisierungsskript der Datenbank konnte nicht gelesen werden.', + // query execution MISSING_OR_EMPTY_QUERY: 'Abfragezeichenkette fehlt oder ist leer.', QUERY_EMPTY: 'Die Abfrage darf nicht leer sein.', @@ -286,6 +295,8 @@ export const MESSAGES: MessageCatalogue = { GRADING_FAILED: 'Die Abfrage konnte nicht bewertet werden.', GRADING_FAILED_WITH_ERROR: 'Die Abfrage konnte nicht bewertet werden. Fehler: {{value}}', + GRADING_PGLITE_NOT_SUPPORTED: + 'Bewertungsendpunkte erfordern eine PostgreSQL-Verbindung; PGlite wird hier nicht unterstützt.', // grading feedback — result set FEEDBACK_RESULT_SET_MATCH: 'Beide Abfragen liefern dasselbe Ergebnis.', FEEDBACK_RESULT_SET_MISMATCH: 'Die Ergebnismengen stimmen nicht überein.', diff --git a/services/sql-assessment-service/src/shared/utils/database-utils.ts b/services/sql-assessment-service/src/shared/utils/database-utils.ts index 2105fc8..063d673 100644 --- a/services/sql-assessment-service/src/shared/utils/database-utils.ts +++ b/services/sql-assessment-service/src/shared/utils/database-utils.ts @@ -1,34 +1,69 @@ import { DataSource, QueryRunner } from 'typeorm'; import { IAliasMap, IParsedTable } from '../interfaces/domain'; -export function generateDatabaseKey(host: string, port: number, schema: string): string { - return `${host}${port}${schema}`; +/** + * Database-agnostic query executor: accepts a SQL string and returns the + * result rows as a plain array. Works with both Postgres (TypeORM) and + * PGlite execution paths. + */ +export type RowQueryFn = (sql: string) => Promise; + +/** + * Builds a {@link RowQueryFn} backed by a TypeORM {@link DataSource}. + * Each invocation acquires a QueryRunner from the pool, executes the query, + * and releases the runner before returning. + */ +export function makeRowQueryFn(dataSource: DataSource): RowQueryFn { + return async (sql: string) => { + const queryRunner = dataSource.createQueryRunner(); + try { + return await queryRunner.query(sql); + } finally { + await queryRunner.release(); + } + }; +} + +export function generateDatabaseKey( + host: string, + port: number, + schema: string, +): string { + return `${host}:${port}/${schema}`; +} + +export function generatePGliteKey(databaseId: string): string { + return `pglite:${databaseId}`; } -export async function connectToDatabase(dataSource: DataSource): Promise { - let isConnected = false; - await dataSource - .initialize() - .then(() => { - console.log(`Data Source ${dataSource} has been initialized!`); - isConnected = true; - }) - .catch(err => { - console.error( - `Error during Data Source ${dataSource} initialization`, - err - ); - isConnected = false; - }); - return isConnected; +export async function connectToDatabase( + dataSource: DataSource, +): Promise { + let isConnected = false; + await dataSource + .initialize() + .then(() => { + console.log(`Data Source ${dataSource} has been initialized!`); + isConnected = true; + }) + .catch((err) => { + console.error( + `Error during Data Source ${dataSource} initialization`, + err, + ); + isConnected = false; + }); + return isConnected; } -export function createQueryRunner(dataSource: DataSource): QueryRunner | undefined { - if (!dataSource) { - console.log('Undefined datasource, please establish a database connection'); - return undefined; - } - return dataSource.createQueryRunner(); +export function createQueryRunner( + dataSource: DataSource, +): QueryRunner | undefined { + if (!dataSource) { + console.log('Undefined datasource, please establish a database connection'); + return undefined; + } + return dataSource.createQueryRunner(); } /** @@ -39,32 +74,34 @@ export function createQueryRunner(dataSource: DataSource): QueryRunner | undefin * result is `undefined` when no alias data is present (no analysis with an * aliasMap was performed). */ -export function buildAliasMapFromTables(tables: IParsedTable[]): IAliasMap | undefined { - const tableAliases: Record = {}; - const columnAliases: Record> = {}; +export function buildAliasMapFromTables( + tables: IParsedTable[], +): IAliasMap | undefined { + const tableAliases: Record = {}; + const columnAliases: Record> = {}; - for (const table of tables) { - if (table.alternativeName) { - tableAliases[table.name] = table.alternativeName; - } + for (const table of tables) { + if (table.alternativeName) { + tableAliases[table.name] = table.alternativeName; + } - for (const col of table.columns) { - if (col.alternativeName) { - if (!columnAliases[table.name]) { - columnAliases[table.name] = {}; - } - columnAliases[table.name][col.name] = col.alternativeName; - } - } - } + for (const col of table.columns) { + if (col.alternativeName) { + if (!columnAliases[table.name]) { + columnAliases[table.name] = {}; + } + columnAliases[table.name][col.name] = col.alternativeName; + } + } + } - const hasTableAliases = Object.keys(tableAliases).length > 0; - const hasColumnAliases = Object.keys(columnAliases).length > 0; + const hasTableAliases = Object.keys(tableAliases).length > 0; + const hasColumnAliases = Object.keys(columnAliases).length > 0; - if (!hasTableAliases && !hasColumnAliases) return undefined; + if (!hasTableAliases && !hasColumnAliases) return undefined; - return { - ...(hasTableAliases ? { tables: tableAliases } : {}), - ...(hasColumnAliases ? { columns: columnAliases } : {}), - }; + return { + ...(hasTableAliases ? { tables: tableAliases } : {}), + ...(hasColumnAliases ? { columns: columnAliases } : {}), + }; } diff --git a/services/sql-assessment-service/test/cli/cli-route-table.test.ts b/services/sql-assessment-service/test/cli/cli-route-table.test.ts new file mode 100644 index 0000000..1957b79 --- /dev/null +++ b/services/sql-assessment-service/test/cli/cli-route-table.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect } from 'vitest'; +import { createControllers } from '../../src/bootstrap'; +import { + extractRoutes, + invokeHandler, +} from '../../src/cli/express-cli-adapter'; + +/** + * Build the full route table identical to the CLI entry point. + */ +function buildRouteTable() { + const c = createControllers(); + return [ + ...extractRoutes(c.connectionController.router, '/api/database'), + ...extractRoutes(c.taskGenerationController.router, '/api/generation'), + ...extractRoutes(c.gradingController.router, '/api/grading'), + ...extractRoutes(c.descriptionController.router, '/api/description'), + ...extractRoutes(c.queryExecutionController.router, '/api/query'), + ]; +} + +// The expected commands in the order defined by registerControllers' mount +// prefixes and each controller's initializeRoutes() order. +const EXPECTED_COMMANDS = [ + 'database:analyze-database', + 'generation:generate', + 'grading:grade', + 'grading:compare:result-set', + 'grading:compare:ast', + 'grading:compare:execution-plan', + 'description:template', + 'description:llm:default', + 'description:llm:creative', + 'description:llm:multi-step', + 'description:hybrid', + 'query:execute', +]; + +describe('CLI route table (integration)', () => { + const routes = buildRouteTable(); + + it('discovers all expected commands', () => { + const commands = routes.map((r) => r.command); + expect(commands).toEqual(EXPECTED_COMMANDS); + }); + + it('every route has a callable handler', () => { + for (const route of routes) { + expect(typeof route.handler).toBe('function'); + } + }); + + it('every route has a non-empty method and path', () => { + for (const route of routes) { + expect(route.method).toMatch(/^(GET|POST|PUT|PATCH|DELETE)$/); + expect(route.path).toMatch(/^\/api\//); + } + }); + + it('commands are unique', () => { + const commands = routes.map((r) => r.command); + expect(new Set(commands).size).toBe(commands.length); + }); + + // Verify that invoking a real controller handler (query:execute) with + // an invalid body returns a 400 from the controller's validation logic. + it('query:execute returns 400 for a body missing connectionInfo', async () => { + const route = routes.find((r) => r.command === 'query:execute')!; + const result = await invokeHandler(route.handler, { query: 'SELECT 1' }); + expect(result.statusCode).toBe(400); + expect(result.data).toHaveProperty('message'); + }); + + it('database:analyze-database returns 400 for incomplete connectionInfo', async () => { + const route = routes.find( + (r) => r.command === 'database:analyze-database', + )!; + const result = await invokeHandler(route.handler, { connectionInfo: {} }); + expect(result.statusCode).toBe(400); + expect(result.data).toHaveProperty('message'); + }); + + it('grading:grade returns 400 for incomplete connectionInfo', async () => { + const route = routes.find((r) => r.command === 'grading:grade')!; + const result = await invokeHandler(route.handler, { + connectionInfo: {}, + gradingRequest: {}, + }); + expect(result.statusCode).toBe(400); + expect(result.data).toHaveProperty('message'); + }); + + it('database:analyze-database returns 400 for completely missing body', async () => { + const route = routes.find( + (r) => r.command === 'database:analyze-database', + )!; + const result = await invokeHandler(route.handler, {}); + // Controller validates connectionInfo → returns 400 for missing connection info + expect(result.statusCode).toBe(400); + expect(result.data).toHaveProperty('message'); + }); + + it('description:template returns 400 for missing fields', async () => { + const route = routes.find((r) => r.command === 'description:template')!; + const result = await invokeHandler(route.handler, {}); + expect(result.statusCode).toBe(400); + expect(result.data).toHaveProperty('message'); + }); +}); diff --git a/services/sql-assessment-service/test/cli/express-cli-adapter.test.ts b/services/sql-assessment-service/test/cli/express-cli-adapter.test.ts new file mode 100644 index 0000000..803e018 --- /dev/null +++ b/services/sql-assessment-service/test/cli/express-cli-adapter.test.ts @@ -0,0 +1,152 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { Router } from 'express'; +import { + extractRoutes, + invokeHandler, + CLIRoute, +} from '../../src/cli/express-cli-adapter'; + +// --------------------------------------------------------------------------- +// extractRoutes +// --------------------------------------------------------------------------- + +describe('extractRoutes', () => { + it('discovers a single POST route', () => { + const router = Router(); + router.post('/do-stuff', (_req, res) => res.json({ ok: true })); + + const routes = extractRoutes(router, '/api/test'); + expect(routes).toHaveLength(1); + expect(routes[0]).toMatchObject({ + command: 'test:do-stuff', + method: 'POST', + path: '/api/test/do-stuff', + }); + expect(typeof routes[0].handler).toBe('function'); + }); + + it('discovers multiple routes on the same router', () => { + const router = Router(); + router.get('/alpha', (_req, res) => res.json({})); + router.post('/beta', (_req, res) => res.json({})); + router.post('/gamma/delta', (_req, res) => res.json({})); + + const routes = extractRoutes(router, '/api/ns'); + const cmds = routes.map((r) => r.command); + expect(cmds).toEqual(['ns:alpha', 'ns:beta', 'ns:gamma:delta']); + }); + + it('returns an empty array for a router with no routes', () => { + const router = Router(); + expect(extractRoutes(router, '/api/empty')).toEqual([]); + }); + + it('derives the command from the full path, stripping /api/ prefix', () => { + const router = Router(); + router.post('/compare/result-set', (_req, res) => res.json({})); + + const [route] = extractRoutes(router, '/api/grading'); + expect(route.command).toBe('grading:compare:result-set'); + }); + + it('handles root path /', () => { + const router = Router(); + router.get('/', (_req, res) => res.json({})); + + const [route] = extractRoutes(router, '/api/health'); + expect(route.path).toBe('/api/health/'); + }); +}); + +// --------------------------------------------------------------------------- +// invokeHandler +// --------------------------------------------------------------------------- + +describe('invokeHandler', () => { + it('captures a 200 JSON response', async () => { + const handler = (_req: any, res: any) => res.json({ answer: 42 }); + const result = await invokeHandler(handler, {}); + expect(result).toEqual({ statusCode: 200, data: { answer: 42 } }); + }); + + it('captures a non-200 status code', async () => { + const handler = (_req: any, res: any) => + res.status(400).json({ message: 'bad request' }); + const result = await invokeHandler(handler, {}); + expect(result.statusCode).toBe(400); + expect(result.data).toEqual({ message: 'bad request' }); + }); + + it('passes the body to the handler as req.body', async () => { + const body = { connectionInfo: { host: 'localhost' } }; + const handler = (req: any, res: any) => res.json(req.body); + const result = await invokeHandler(handler, body); + expect(result.data).toEqual(body); + }); + + it('captures res.send() calls', async () => { + const handler = (_req: any, res: any) => res.send('plain text'); + const result = await invokeHandler(handler, {}); + expect(result).toEqual({ statusCode: 200, data: 'plain text' }); + }); + + it('captures async handler responses', async () => { + const handler = async (_req: any, res: any) => { + await new Promise((r) => setTimeout(r, 10)); + return res.status(201).json({ created: true }); + }; + const result = await invokeHandler(handler, {}); + expect(result.statusCode).toBe(201); + expect(result.data).toEqual({ created: true }); + }); + + it('returns 500 when an async handler rejects', async () => { + const handler = async () => { + throw new Error('boom'); + }; + const result = await invokeHandler(handler, {}); + expect(result.statusCode).toBe(500); + expect(result.data).toEqual({ message: 'boom' }); + }); + + it('preserves the last status code when status() is called multiple times', async () => { + const handler = (_req: any, res: any) => + res.status(201).status(409).json({ conflict: true }); + const result = await invokeHandler(handler, {}); + expect(result.statusCode).toBe(409); + }); +}); + +// --------------------------------------------------------------------------- +// Integration: extractRoutes + invokeHandler round-trip +// --------------------------------------------------------------------------- + +describe('extractRoutes + invokeHandler round-trip', () => { + it('can invoke a discovered route handler and get a response', async () => { + const router = Router(); + router.post('/echo', (req, res) => res.json({ echo: req.body })); + + const [route] = extractRoutes(router, '/api/test'); + const result = await invokeHandler(route.handler, { ping: 'pong' }); + expect(result.statusCode).toBe(200); + expect(result.data).toEqual({ echo: { ping: 'pong' } }); + }); + + it('works with handlers that validate input and return 400', async () => { + const router = Router(); + router.post('/strict', (req, res) => { + if (!req.body?.name) + return res.status(400).json({ message: 'name required' }); + return res.json({ hello: req.body.name }); + }); + + const [route] = extractRoutes(router, '/api/v'); + + const bad = await invokeHandler(route.handler, {}); + expect(bad.statusCode).toBe(400); + + const good = await invokeHandler(route.handler, { name: 'world' }); + expect(good.statusCode).toBe(200); + expect(good.data).toEqual({ hello: 'world' }); + }); +}); diff --git a/services/sql-assessment-service/test/cli/resolve-file-flag.test.ts b/services/sql-assessment-service/test/cli/resolve-file-flag.test.ts new file mode 100644 index 0000000..22c58c3 --- /dev/null +++ b/services/sql-assessment-service/test/cli/resolve-file-flag.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import { resolveFileFlag } from '../../src/cli/index'; + +describe('resolveFileFlag', () => { + it('returns -1 when neither flag is present', () => { + expect(resolveFileFlag([])).toBe(-1); + expect(resolveFileFlag(['query:execute', '--stdin'])).toBe(-1); + }); + + it('returns the index of -f when only -f is present', () => { + expect(resolveFileFlag(['cmd', '-f', 'body.json'])).toBe(1); + }); + + it('returns the index of --file when only --file is present', () => { + expect(resolveFileFlag(['cmd', '--file', 'body.json'])).toBe(1); + }); + + it('prefers -f over --file when both are present (-f first)', () => { + // args: ['cmd', '-f', 'a.json', '--file', 'b.json'] + const args = ['cmd', '-f', 'a.json', '--file', 'b.json']; + const idx = resolveFileFlag(args); + expect(idx).toBe(1); + expect(args[idx + 1]).toBe('a.json'); + }); + + it('prefers -f over --file when both are present (--file first)', () => { + // args: ['cmd', '--file', 'a.json', '-f', 'b.json'] + const args = ['cmd', '--file', 'a.json', '-f', 'b.json']; + const idx = resolveFileFlag(args); + expect(idx).toBe(3); + expect(args[idx + 1]).toBe('b.json'); + }); +}); diff --git a/services/sql-assessment-service/test/cli/resolve-init-sql-file.test.ts b/services/sql-assessment-service/test/cli/resolve-init-sql-file.test.ts new file mode 100644 index 0000000..98975d6 --- /dev/null +++ b/services/sql-assessment-service/test/cli/resolve-init-sql-file.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import * as path from 'path'; +import { resolveInitSqlFile } from '../../src/cli/index'; + +describe('resolveInitSqlFile', () => { + it('returns undefined when the flag is absent', () => { + expect(resolveInitSqlFile([])).toBeUndefined(); + expect(resolveInitSqlFile(['query:execute', '--stdin'])).toBeUndefined(); + }); + + it('throws when --init-sql-file is the last argument', () => { + expect(() => resolveInitSqlFile(['--init-sql-file'])).toThrow( + '--init-sql-file requires a path argument.', + ); + }); + + it('throws when --init-sql-file is followed by another flag', () => { + expect(() => resolveInitSqlFile(['--init-sql-file', '--stdin'])).toThrow( + '--init-sql-file requires a path argument.', + ); + }); + + it('returns the resolved absolute path when a valid path is provided', () => { + const result = resolveInitSqlFile(['--init-sql-file', 'init.sql']); + expect(result).toBe(path.resolve('init.sql')); + }); + + it('resolves the path relative to cwd for relative paths', () => { + const result = resolveInitSqlFile([ + '--init-sql-file', + './fixtures/schema.sql', + ]); + expect(result).toBe(path.resolve('./fixtures/schema.sql')); + }); + + it('passes through an already-absolute path unchanged', () => { + const abs = '/absolute/path/to/init.sql'; + expect(resolveInitSqlFile(['--init-sql-file', abs])).toBe(abs); + }); +}); diff --git a/services/sql-assessment-service/test/cli/resolve-positional-body.test.ts b/services/sql-assessment-service/test/cli/resolve-positional-body.test.ts new file mode 100644 index 0000000..1b66e93 --- /dev/null +++ b/services/sql-assessment-service/test/cli/resolve-positional-body.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest'; +import { resolvePositionalBody } from '../../src/cli/index'; + +describe('resolvePositionalBody', () => { + it('returns undefined for empty args', () => { + expect(resolvePositionalBody([])).toBeUndefined(); + }); + + it('returns undefined when args contains only the command', () => { + expect(resolvePositionalBody(['query:execute'])).toBeUndefined(); + }); + + it('returns undefined when all args are flags', () => { + expect(resolvePositionalBody(['query:execute', '--stdin'])).toBeUndefined(); + expect( + resolvePositionalBody(['query:execute', '--list', '--help']), + ).toBeUndefined(); + }); + + it('returns JSON when it is immediately after the command (args[1])', () => { + const json = '{"query":"SELECT 1"}'; + expect(resolvePositionalBody(['query:execute', json])).toBe(json); + }); + + it('skips --init-sql-file and its value, returns subsequent JSON', () => { + const json = '{"query":"SELECT 1"}'; + expect( + resolvePositionalBody([ + 'query:execute', + '--init-sql-file', + 'schema.sql', + json, + ]), + ).toBe(json); + }); + + it('skips -f and its value, returns subsequent JSON', () => { + const json = '{"query":"SELECT 1"}'; + expect( + resolvePositionalBody(['query:execute', '-f', 'body.json', json]), + ).toBe(json); + }); + + it('skips --file and its value, returns subsequent JSON', () => { + const json = '{"query":"SELECT 1"}'; + expect( + resolvePositionalBody(['query:execute', '--file', 'body.json', json]), + ).toBe(json); + }); + + it('skips multiple flags-with-values before the JSON', () => { + const json = '{"query":"SELECT 1"}'; + expect( + resolvePositionalBody([ + 'query:execute', + '--init-sql-file', + 'schema.sql', + '-f', + 'body.json', + json, + ]), + ).toBe(json); + }); + + it('skips boolean flags interspersed before the JSON', () => { + const json = '{"query":"SELECT 1"}'; + expect( + resolvePositionalBody([ + 'query:execute', + '--init-sql-file', + 'schema.sql', + '--verbose', + json, + ]), + ).toBe(json); + }); + + it('returns undefined when a flags-with-values flag has no following value', () => { + // --init-sql-file is last; no positional follows + expect( + resolvePositionalBody(['query:execute', '--init-sql-file']), + ).toBeUndefined(); + }); +}); diff --git a/services/sql-assessment-service/test/database/database-service.test.ts b/services/sql-assessment-service/test/database/database-service.test.ts new file mode 100644 index 0000000..86cfaf9 --- /dev/null +++ b/services/sql-assessment-service/test/database/database-service.test.ts @@ -0,0 +1,291 @@ +import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { PGlite } from '@electric-sql/pglite'; +import { DatabaseAnalyzer } from '../../src/database/database-analyzer'; +import { DatabaseService } from '../../src/database/database-service'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; +import { generatePGliteKey } from '../../src/shared/utils/database-utils'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const SIMPLE_DDL = ` +CREATE TABLE items ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL +); +INSERT INTO items (name) VALUES ('alpha'), ('beta'); +`; + +const DB_ID = 'svc-test-db'; +const DB_KEY = generatePGliteKey(DB_ID); + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +// Warm up PGlite WASM once per worker thread so individual tests don't pay +// the cold-start cost and hit the default 5 s timeout in CI. +beforeAll(async () => { + const db = new PGlite(); + await db.close(); +}); + +describe('DatabaseService — ensureAnalyzed', () => { + let service: DatabaseService; + + beforeEach(() => { + service = new DatabaseService(new DatabaseAnalyzer()); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ── PGlite ────────────────────────────────────────────────────────────── + + describe('PGlite', () => { + it('creates instance and registers metadata when sqlContent is provided', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }); + + expect(result.ok).toBe(true); + expect(databaseMetadata.has(DB_KEY)).toBe(true); + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + + it('replaces an existing PGlite instance when called again with same databaseId', async () => { + await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }); + const firstDb = pgliteInstances.get(DB_ID); + + await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }); + const secondDb = pgliteInstances.get(DB_ID); + + expect(secondDb).not.toBe(firstDb); + }); + + it('skips silently (ok:true) when sqlContent is absent and required=false', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + // no sqlContent + }); + + expect(result.ok).toBe(true); + expect(result.status).toBe(200); + // Nothing was registered + expect(databaseMetadata.has(DB_KEY)).toBe(false); + }); + + it('returns 400 when sqlContent is an empty string (required=false)', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: '', + }); + + expect(result.ok).toBe(false); + expect(result.status).toBe(400); + }); + + it('returns 400 when sqlContent is an empty string (required=true)', async () => { + const result = await service.ensureAnalyzed( + { type: 'pglite', databaseId: DB_ID, sqlContent: '' }, + undefined, + 'en', + true, + ); + + expect(result.ok).toBe(false); + expect(result.status).toBe(400); + }); + + it('returns 400 when sqlContent is absent and required=true', async () => { + const result = await service.ensureAnalyzed( + { type: 'pglite', databaseId: DB_ID }, + undefined, + 'en', + true, + ); + + expect(result.ok).toBe(false); + expect(result.status).toBe(400); + }); + + it('returns 400 when databaseId is missing', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + sqlContent: SIMPLE_DDL, + // no databaseId + }); + + expect(result.ok).toBe(false); + expect(result.status).toBe(400); + }); + + it('returns 500 when sqlContent contains invalid SQL', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: 'THIS IS NOT SQL @@##!!', + }); + + expect(result.ok).toBe(false); + expect(result.status).toBe(500); + }); + }); + + // ── Missing connectionInfo ─────────────────────────────────────────────── + + it('returns 400 when connectionInfo is null/undefined', async () => { + const result = await service.ensureAnalyzed(null); + expect(result.ok).toBe(false); + expect(result.status).toBe(400); + }); +}); + +// --------------------------------------------------------------------------- +// Init-SQL file feature +// --------------------------------------------------------------------------- + +describe('DatabaseService — initSqlFilePath', () => { + let tmpFile: string; + let service: DatabaseService; + + beforeEach(() => { + // Write SIMPLE_DDL to a temp file + tmpFile = path.join(os.tmpdir(), `pglite-test-${Date.now()}.sql`); + fs.writeFileSync(tmpFile, SIMPLE_DDL, 'utf-8'); + + service = new DatabaseService(new DatabaseAnalyzer(), tmpFile); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + // Use a Set to avoid closing a shared instance more than once. + for (const db of new Set(pgliteInstances.values())) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + try { + fs.unlinkSync(tmpFile); + } catch { + /* ignore */ + } + }); + + it('uses the init SQL file when sqlContent is absent from the request', async () => { + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + // no sqlContent — should fall back to tmpFile + }); + + expect(result.ok).toBe(true); + expect(databaseMetadata.has(DB_KEY)).toBe(true); + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + + it('request-level sqlContent takes priority over the init SQL file', async () => { + const overrideDDL = ` + CREATE TABLE override_table (id SERIAL PRIMARY KEY, val TEXT NOT NULL); + INSERT INTO override_table (val) VALUES ('x'); + `; + const result = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + sqlContent: overrideDDL, + }); + + expect(result.ok).toBe(true); + // The schema should contain override_table, not items + const tables = databaseMetadata.get(DB_KEY) ?? []; + const tableNames = tables.map((t: any) => t.name ?? t.tableName ?? t.table); + expect(tableNames.some((n: string) => n?.includes('override'))).toBe(true); + }); + + it('returns 500 when the configured file path does not exist', async () => { + const badService = new DatabaseService( + new DatabaseAnalyzer(), + '/nonexistent/path/init.sql', + ); + const result = await badService.ensureAnalyzed({ + type: 'pglite', + databaseId: DB_ID, + }); + + expect(result.ok).toBe(false); + expect(result.status).toBe(500); + }); + + it('skips the file when sqlContent is absent and required=true (file counts as content)', async () => { + // With initSqlFilePath set, even required=true should succeed because + // the file provides the sqlContent + const result = await service.ensureAnalyzed( + { type: 'pglite', databaseId: DB_ID }, + undefined, + 'en', + true, + ); + expect(result.ok).toBe(true); + }); + + it('multiple databaseIds without sqlContent share a single PGlite instance', async () => { + const r1 = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: 'db-a', + }); + const r2 = await service.ensureAnalyzed({ + type: 'pglite', + databaseId: 'db-b', + }); + + expect(r1.ok).toBe(true); + expect(r2.ok).toBe(true); + // Both databaseIds must resolve to the same in-memory PGlite object. + expect(pgliteInstances.get('db-a')).toBe(pgliteInstances.get('db-b')); + }); + + it('concurrent calls without sqlContent create only one shared PGlite instance', async () => { + // Fire both registrations concurrently — without the Promise-coalescing + // fix the two calls would each pass the "!cachedInitSqlPGlite" guard + // before either await chain completes, spawning two PGlite instances + // and leaking the first one. + const [r1, r2] = await Promise.all([ + service.ensureAnalyzed({ type: 'pglite', databaseId: 'db-concurrent-1' }), + service.ensureAnalyzed({ type: 'pglite', databaseId: 'db-concurrent-2' }), + ]); + + expect(r1.ok).toBe(true); + expect(r2.ok).toBe(true); + // Both ids must point to the exact same PGlite object (shared singleton). + expect(pgliteInstances.get('db-concurrent-1')).toBe( + pgliteInstances.get('db-concurrent-2'), + ); + }); +}); diff --git a/services/sql-assessment-service/test/database/pglite-controller.test.ts b/services/sql-assessment-service/test/database/pglite-controller.test.ts new file mode 100644 index 0000000..c850550 --- /dev/null +++ b/services/sql-assessment-service/test/database/pglite-controller.test.ts @@ -0,0 +1,396 @@ +import { + describe, + it, + expect, + beforeAll, + beforeEach, + afterEach, + vi, +} from 'vitest'; +import { PGlite } from '@electric-sql/pglite'; +import type { Request, Response } from 'express'; +import { DatabaseController } from '../../src/database/database-controller'; +import { DatabaseAnalyzer } from '../../src/database/database-analyzer'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; +import { generatePGliteKey } from '../../src/shared/utils/database-utils'; +import { RelationshipType } from '../../src/shared/interfaces/domain'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +/** + * Minimal two-table schema (public schema — PGlite default) with a FK + * relationship and a small seed dataset. + */ +const SIMPLE_DDL = ` +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) +); +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + product_id INTEGER REFERENCES products(id), + quantity INTEGER NOT NULL +); +INSERT INTO products (name, price) VALUES ('Widget', 9.99), ('Gadget', 19.99); +INSERT INTO orders (product_id, quantity) VALUES (1, 5), (2, 3); +`; + +const VALID_PGLITE_INFO = { + type: 'pglite' as const, + databaseId: 'test-db', + sqlContent: SIMPLE_DDL, +}; + +// --------------------------------------------------------------------------- +// Express mock helpers (same pattern as query-execution-controller.test.ts) +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// Warm up PGlite WASM once per worker thread so individual tests don't pay +// the cold-start cost and hit the default timeout in CI. +beforeAll(async () => { + const db = new PGlite(); + await db.close(); +}); + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('DatabaseController — PGlite path', () => { + let controller: DatabaseController; + + beforeEach(() => { + controller = new DatabaseController(new DatabaseAnalyzer()); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + // Close any open PGlite instances to avoid resource leaks. + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ----------------------------------------------------------------------- + // Request validation + // ----------------------------------------------------------------------- + + describe('analyzeDatabase — PGlite request validation', () => { + it('returns 400 when databaseId is missing', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: { type: 'pglite', sqlContent: SIMPLE_DDL } }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when sqlContent is missing', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: { type: 'pglite', databaseId: 'test-db' } }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when sqlContent is not a string', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'test-db', + sqlContent: 42, + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when sqlContent is an empty string', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'test-db', + sqlContent: '', + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when databaseId is not a string', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 123, + sqlContent: SIMPLE_DDL, + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + }); + + // ----------------------------------------------------------------------- + // Integration (real PGlite) + // ----------------------------------------------------------------------- + + describe('analyzeDatabase — PGlite integration', () => { + it('returns 200 for valid DDL', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: VALID_PGLITE_INFO }), + res, + ); + expect(status).toHaveBeenCalledWith(200); + }); + + it('registers database metadata under the PGlite key', async () => { + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: VALID_PGLITE_INFO }), + res, + ); + expect(databaseMetadata.has(generatePGliteKey('test-db'))).toBe(true); + }); + + it('reflects table names into metadata', async () => { + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: VALID_PGLITE_INFO }), + res, + ); + const tables = databaseMetadata.get(generatePGliteKey('test-db')); + const names = tables!.map((t) => t.name); + expect(names).toContain('products'); + expect(names).toContain('orders'); + }); + + it('stores the PGlite instance for later query execution', async () => { + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ connectionInfo: VALID_PGLITE_INFO }), + res, + ); + expect(pgliteInstances.has('test-db')).toBe(true); + }); + + it('returns 500 when sqlContent contains invalid SQL', async () => { + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'bad-db', + sqlContent: 'THIS IS NOT VALID SQL AT ALL !!!', + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(500); + }); + + it('replaces an existing PGlite instance when databaseId is reused', async () => { + // First call — products + orders schema + await controller.analyzeDatabase( + mockReq({ connectionInfo: VALID_PGLITE_INFO }), + mockRes().res, + ); + const firstInstance = pgliteInstances.get('test-db'); + + // Second call — completely different schema, same databaseId + const { res, status } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'test-db', + sqlContent: + 'CREATE TABLE categories (id SERIAL PRIMARY KEY, label TEXT NOT NULL);', + }, + }), + res, + ); + + expect(status).toHaveBeenCalledWith(200); + const tables = databaseMetadata.get(generatePGliteKey('test-db')); + expect(tables!.map((t) => t.name)).toContain('categories'); + expect(tables!.map((t) => t.name)).not.toContain('products'); + // A new PGlite instance must replace the old one + expect(pgliteInstances.get('test-db')).not.toBe(firstInstance); + }); + }); + + // ----------------------------------------------------------------------- + // UNIQUE constraint cardinality classification + // ----------------------------------------------------------------------- + + describe('analyzeDatabase — UNIQUE constraint cardinality', () => { + /** + * A composite UNIQUE(order_id, product_id) must NOT make either FK column + * look individually unique. Both FK relationships should remain 1:N. + */ + it('classifies FK as 1:N when it participates in a composite UNIQUE constraint', async () => { + const ddl = ` + CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + product_id INTEGER NOT NULL REFERENCES products(id), + customer_id INTEGER NOT NULL REFERENCES customers(id), + UNIQUE (product_id, customer_id) + ); + `; + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'uq-composite', + sqlContent: ddl, + }, + }), + res, + ); + + const tables = databaseMetadata.get(generatePGliteKey('uq-composite'))!; + const orders = tables.find((t) => t.name === 'orders')!; + + for (const rel of orders.relationships) { + expect(rel.cardinality).toBe(RelationshipType.OneToMany); + } + }); + + /** + * A single-column UNIQUE on a FK column IS a genuine 1:1 indicator and + * the relationship should be classified accordingly. + */ + it('classifies FK as 1:1 when it has a single-column UNIQUE constraint', async () => { + const ddl = ` + CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE profiles ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) + ); + `; + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'uq-single', + sqlContent: ddl, + }, + }), + res, + ); + + const tables = databaseMetadata.get(generatePGliteKey('uq-single'))!; + const profiles = tables.find((t) => t.name === 'profiles')!; + const rel = profiles.relationships.find( + (r) => r.referencedTable === 'users', + )!; + + expect(rel.cardinality).toBe(RelationshipType.OneToOne); + }); + + /** + * A single-column CREATE UNIQUE INDEX on a FK column must be treated the + * same as a UNIQUE constraint: the relationship should be classified as 1:1. + */ + it('classifies FK as 1:1 when it has a single-column CREATE UNIQUE INDEX', async () => { + const ddl = ` + CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE profiles ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) + ); + CREATE UNIQUE INDEX profiles_user_id_idx ON profiles(user_id); + `; + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'uq-idx-single', + sqlContent: ddl, + }, + }), + res, + ); + + const tables = databaseMetadata.get(generatePGliteKey('uq-idx-single'))!; + const profiles = tables.find((t) => t.name === 'profiles')!; + const rel = profiles.relationships.find( + (r) => r.referencedTable === 'users', + )!; + + expect(rel.cardinality).toBe(RelationshipType.OneToOne); + }); + + /** + * A composite CREATE UNIQUE INDEX must NOT make either FK column + * individually unique. Both FK relationships should remain 1:N. + */ + it('classifies FK as 1:N when it participates in a composite CREATE UNIQUE INDEX', async () => { + const ddl = ` + CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE customers (id SERIAL PRIMARY KEY, name TEXT NOT NULL); + CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + product_id INTEGER NOT NULL REFERENCES products(id), + customer_id INTEGER NOT NULL REFERENCES customers(id) + ); + CREATE UNIQUE INDEX orders_composite_idx ON orders(product_id, customer_id); + `; + const { res } = mockRes(); + await controller.analyzeDatabase( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: 'uq-idx-composite', + sqlContent: ddl, + }, + }), + res, + ); + + const tables = databaseMetadata.get( + generatePGliteKey('uq-idx-composite'), + )!; + const orders = tables.find((t) => t.name === 'orders')!; + + for (const rel of orders.relationships) { + expect(rel.cardinality).toBe(RelationshipType.OneToMany); + } + }); + }); +}); diff --git a/services/sql-assessment-service/test/generation/description/pglite-description-controller.test.ts b/services/sql-assessment-service/test/generation/description/pglite-description-controller.test.ts new file mode 100644 index 0000000..23c67c0 --- /dev/null +++ b/services/sql-assessment-service/test/generation/description/pglite-description-controller.test.ts @@ -0,0 +1,176 @@ +/** + * Tests for DescriptionController with PGlite + auto-analyze. + * + * When `sqlContent` is included in a PGlite `connectionInfo`, each description + * endpoint must automatically analyse the in-process database and proceed + * without a prior `/api/database/analyze-database` call. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { Request, Response } from 'express'; +import { DescriptionController } from '../../../src/generation/description/description-controller'; +import { TaskDescriptionGenerationService } from '../../../src/generation/description/task-description-generation-service'; +import { TemplateTaskDescriptionGenerationEngine } from '../../../src/generation/description/template-task-description-generation-engine'; +import { DatabaseService } from '../../../src/database/database-service'; +import { DatabaseAnalyzer } from '../../../src/database/database-analyzer'; +import { + databaseMetadata, + pgliteInstances, +} from '../../../src/database/internal-memory'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +/** Minimal schema — one table so the template engine has something to work with. */ +const SIMPLE_DDL = ` +CREATE TABLE employees ( + id SERIAL PRIMARY KEY, + first_name TEXT NOT NULL, + salary NUMERIC(10,2) +); +INSERT INTO employees (first_name, salary) VALUES ('Alice', 50000), ('Bob', 60000); +`; + +const DB_ID = 'auto-analyze-desc-db'; + +// A simple SELECT that the template engine can process. +const SIMPLE_QUERY = 'SELECT first_name, salary FROM employees'; + +// --------------------------------------------------------------------------- +// Express mock helpers +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('DescriptionController — PGlite auto-analyze', () => { + let controller: DescriptionController; + + beforeEach(() => { + const databaseService = new DatabaseService(new DatabaseAnalyzer()); + const templateEngine = new TemplateTaskDescriptionGenerationEngine(); + const generationService = new TaskDescriptionGenerationService( + undefined, + templateEngine, + ); + controller = new DescriptionController(generationService, databaseService); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ── Template endpoint ──────────────────────────────────────────────────── + + describe('POST /api/description/template', () => { + it('returns 200 when sqlContent is provided in PGlite connectionInfo (no prior analyze)', async () => { + const { res, status } = mockRes(); + await controller.generateTemplateDescription( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + query: SIMPLE_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(200); + }); + + it('auto-registers PGlite instance after a successful template description', async () => { + const { res } = mockRes(); + await controller.generateTemplateDescription( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + query: SIMPLE_QUERY, + }), + res, + ); + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + + it('returns 400 when PGlite databaseId is missing', async () => { + const { res, status } = mockRes(); + await controller.generateTemplateDescription( + mockReq({ + connectionInfo: { type: 'pglite', sqlContent: SIMPLE_DDL }, + query: SIMPLE_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is missing', async () => { + const { res, status } = mockRes(); + await controller.generateTemplateDescription( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + // no query field + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when PGlite DB is not registered and sqlContent is absent', async () => { + const { res, status } = mockRes(); + await controller.generateTemplateDescription( + mockReq({ + connectionInfo: { type: 'pglite', databaseId: DB_ID }, + query: SIMPLE_QUERY, + }), + res, + ); + // ensureAnalyzed skips (no sqlContent), then isDatabaseRegistered → false → 400 + expect(status).toHaveBeenCalledWith(400); + }); + }); + + // ── Hybrid endpoint ────────────────────────────────────────────────────── + + describe('POST /api/description/hybrid', () => { + it('returns 200 with PGlite + sqlContent', async () => { + const { res, status } = mockRes(); + await controller.generateHybridDescription( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + query: SIMPLE_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(200); + }); + }); +}); diff --git a/services/sql-assessment-service/test/generation/pglite-task-generation-controller.test.ts b/services/sql-assessment-service/test/generation/pglite-task-generation-controller.test.ts new file mode 100644 index 0000000..11556ee --- /dev/null +++ b/services/sql-assessment-service/test/generation/pglite-task-generation-controller.test.ts @@ -0,0 +1,198 @@ +/** + * Tests for TaskGenerationController with PGlite + auto-analyze. + * + * Regression tests for the validation-ordering bug reported in code review: + * `validateConnectionInfo` was called BEFORE the auto-analyze block, so any + * `{ type: 'pglite', ... }` request was rejected as INVALID_CONNECTION_INFO + * before it could be analyzed/registered. + * + * After the fix the controller: + * 1. Runs auto-analyze first (so PGlite is registered if sqlContent is given) + * 2. Branches on `type === 'pglite'` vs Postgres + * 3. Calls `validateConnectionInfo` only in the Postgres branch + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { Request, Response } from 'express'; +import { TaskGenerationController } from '../../src/generation/task-generation-controller'; +import { SQLQueryGenerationService } from '../../src/generation/query/sql-query-generation-service'; +import { TaskDescriptionGenerationService } from '../../src/generation/description/task-description-generation-service'; +import { DatabaseService } from '../../src/database/database-service'; +import { DatabaseAnalyzer } from '../../src/database/database-analyzer'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; +import type { ITaskConfiguration } from '../../src/shared/interfaces/domain'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const SIMPLE_DDL = ` +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) +); +INSERT INTO products (name, price) VALUES ('Widget', 9.99), ('Gadget', 19.99); +`; + +const DB_ID = 'pglite-gen-test-db'; + +/** Minimal valid task config — no joins, no predicates, just columns. */ +const SIMPLE_TASK_CONFIG: ITaskConfiguration = { + aggregation: false, + orderby: false, + joinDepth: 0, + joinTypes: [], + predicateCount: 0, + groupby: false, + having: false, + columnCount: 1, + operationTypes: [], +}; + +// --------------------------------------------------------------------------- +// Express mock helpers +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('TaskGenerationController — PGlite auto-analyze', () => { + let controller: TaskGenerationController; + let mockQueryService: SQLQueryGenerationService; + let mockDescService: TaskDescriptionGenerationService; + + beforeEach(() => { + // Stub the generation services so unit tests don't need real DB queries + mockQueryService = { + validateConfiguration: vi.fn().mockReturnValue([true, '']), + generateContextBasedQuery: vi + .fn() + .mockResolvedValue(['SELECT name FROM products', {}]), + } as unknown as SQLQueryGenerationService; + + mockDescService = { + generateTaskFromQuery: vi + .fn() + .mockResolvedValue('Retrieve all product names.'), + } as unknown as TaskDescriptionGenerationService; + + const databaseService = new DatabaseService(new DatabaseAnalyzer()); + controller = new TaskGenerationController( + mockQueryService, + mockDescService, + databaseService, + ); + + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ── Regression: validation ordering ────────────────────────────────────── + + it('does NOT reject PGlite request as INVALID_CONNECTION_INFO', async () => { + const { res, status } = mockRes(); + await controller.generateTaskForRequest( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + taskConfiguration: SIMPLE_TASK_CONFIG, + }), + res, + ); + // The fix: must not return 400 with INVALID_CONNECTION_INFO + // (previously `validateConnectionInfo` ran first and rejected PGlite) + if (status.mock.calls[0]?.[0] === 400) { + const message: string = + (status.mock.results[0].value.json.mock.calls[0]?.[0] as any) + ?.message ?? ''; + expect(message).not.toMatch(/invalid.*connection/i); + } + }); + + it('auto-registers PGlite DB and reaches query generation', async () => { + const { res, status } = mockRes(); + await controller.generateTaskForRequest( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + taskConfiguration: SIMPLE_TASK_CONFIG, + }), + res, + ); + expect(mockQueryService.generateContextBasedQuery).toHaveBeenCalled(); + expect(status).toHaveBeenCalledWith(200); + }); + + it('returns 200 with all description fields populated for PGlite', async () => { + const { res, status, json } = mockRes(); + await controller.generateTaskForRequest( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + taskConfiguration: SIMPLE_TASK_CONFIG, + }), + res, + ); + expect(status).toHaveBeenCalledWith(200); + const body = json.mock.calls[0]?.[0] as any; + expect(body).toHaveProperty('query'); + expect(body).toHaveProperty('templateBasedDescription'); + expect(body).toHaveProperty('gptEntityRelationshipDescription'); + }); + + it('returns 400 when PGlite databaseId is missing', async () => { + const { res, status } = mockRes(); + await controller.generateTaskForRequest( + mockReq({ + connectionInfo: { type: 'pglite', sqlContent: SIMPLE_DDL }, + taskConfiguration: SIMPLE_TASK_CONFIG, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when PGlite DB is not registered and sqlContent is absent', async () => { + const { res, status } = mockRes(); + await controller.generateTaskForRequest( + mockReq({ + connectionInfo: { type: 'pglite', databaseId: DB_ID }, + taskConfiguration: SIMPLE_TASK_CONFIG, + }), + res, + ); + // ensureAnalyzed skips (no sqlContent), then isDatabaseRegistered → false + expect(status).toHaveBeenCalledWith(400); + }); +}); diff --git a/services/sql-assessment-service/test/grading/pglite-grading-controller.test.ts b/services/sql-assessment-service/test/grading/pglite-grading-controller.test.ts new file mode 100644 index 0000000..a70c594 --- /dev/null +++ b/services/sql-assessment-service/test/grading/pglite-grading-controller.test.ts @@ -0,0 +1,235 @@ +/** + * Regression tests for the validation-ordering bug in GradingController. + * + * Before the fix, `validateConnectionInfo` was called BEFORE the auto-analyze + * block in both `gradeQuery` and `validateAndConnect`. Any PGlite connection + * was therefore rejected with INVALID_CONNECTION_INFO before auto-analyze + * could run. + * + * After the fix the controller: + * 1. Runs auto-analyze first (so PGlite can be registered if sqlContent is provided) + * 2. Branches on `type === 'pglite'` and returns 400 with GRADING_PGLITE_NOT_SUPPORTED + * 3. Calls `validateConnectionInfo` only in the Postgres branch + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import type { Request, Response } from 'express'; +import { vi } from 'vitest'; +import { GradingController } from '../../src/grading/grading-controller'; +import { SQLQueryGradingService } from '../../src/grading/query-grading-service'; +import { TaskDescriptionGenerationService } from '../../src/generation/description/task-description-generation-service'; +import { ResultSetComparator } from '../../src/grading/result-set-comparator'; +import { ASTComparator } from '../../src/grading/comparators/ast-comparator'; +import { ExecutionPlanComparator } from '../../src/grading/comparators/execution-plan-comparator'; +import { QueryProximityService } from '../../src/grading/query-proximity-service'; +import { DatabaseService } from '../../src/database/database-service'; +import { DatabaseAnalyzer } from '../../src/database/database-analyzer'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; +import { JoinComparator } from '../../src/grading/join-comparator'; +import { ExecutionPlanParser } from '../../src/grading/execution-plan-parser'; +import { FeedbackAssembler } from '../../src/grading/feedback/feedback-assembler'; +import { GradeCalculator } from '../../src/grading/grading/grade-calculator'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const SIMPLE_DDL = ` +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) +); +INSERT INTO products (name, price) VALUES ('Widget', 9.99); +`; + +const DB_ID = 'pglite-grading-test-db'; + +const PGLITE_CONN_WITH_SQL = { + type: 'pglite' as const, + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, +}; + +const PGLITE_CONN_NO_SQL = { + type: 'pglite' as const, + databaseId: DB_ID, +}; + +const REF_QUERY = 'SELECT name FROM products'; +const STUDENT_QUERY = 'SELECT name FROM products'; + +// --------------------------------------------------------------------------- +// Express mock helpers +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('GradingController — PGlite validation ordering', () => { + let controller: GradingController; + + beforeEach(() => { + const joinComparator = new JoinComparator(); + controller = new GradingController( + {} as SQLQueryGradingService, + {} as TaskDescriptionGenerationService, + new ResultSetComparator(), + new ASTComparator(joinComparator), + new ExecutionPlanComparator(new ExecutionPlanParser(), joinComparator), + new QueryProximityService(), + new DatabaseService(new DatabaseAnalyzer()), + ); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ── POST /api/grading/grade ────────────────────────────────────────────── + + describe('gradeQuery', () => { + it('returns 400 GRADING_PGLITE_NOT_SUPPORTED, not INVALID_CONNECTION_INFO', async () => { + const { res, status, json } = mockRes(); + await controller.gradeQuery( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + gradingRequest: { + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + const message: string = json.mock.calls[0]?.[0]?.message ?? ''; + expect(message).not.toMatch(/invalid.*connection/i); + expect(message).toMatch(/pglite/i); + }); + + it('runs auto-analyze before rejecting (PGlite DB is registered after the call)', async () => { + const { res } = mockRes(); + await controller.gradeQuery( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + gradingRequest: { + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }, + }), + res, + ); + // auto-analyze ran → PGlite instance was registered + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + + it('returns 400 GRADING_PGLITE_NOT_SUPPORTED even without sqlContent', async () => { + const { res, status, json } = mockRes(); + await controller.gradeQuery( + mockReq({ + connectionInfo: PGLITE_CONN_NO_SQL, + gradingRequest: { + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + const message: string = json.mock.calls[0]?.[0]?.message ?? ''; + expect(message).toMatch(/pglite/i); + }); + }); + + // ── POST /api/grading/compare/result-set (via validateAndConnect) ───────── + + describe('compareResultSet', () => { + it('returns 400 GRADING_PGLITE_NOT_SUPPORTED, not INVALID_CONNECTION_INFO', async () => { + const { res, status, json } = mockRes(); + await controller.compareResultSet( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + const message: string = json.mock.calls[0]?.[0]?.message ?? ''; + expect(message).not.toMatch(/invalid.*connection/i); + expect(message).toMatch(/pglite/i); + }); + + it('runs auto-analyze before rejecting (PGlite DB is registered after the call)', async () => { + const { res } = mockRes(); + await controller.compareResultSet( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }), + res, + ); + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + }); + + // ── POST /api/grading/compare/ast (via validateAndConnect) ─────────────── + + describe('compareAST', () => { + it('returns 400 GRADING_PGLITE_NOT_SUPPORTED, not INVALID_CONNECTION_INFO', async () => { + const { res, status, json } = mockRes(); + await controller.compareAST( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + const message: string = json.mock.calls[0]?.[0]?.message ?? ''; + expect(message).not.toMatch(/invalid.*connection/i); + expect(message).toMatch(/pglite/i); + }); + }); + + // ── POST /api/grading/compare/execution-plan (via validateAndConnect) ──── + + describe('compareExecutionPlan', () => { + it('returns 400 GRADING_PGLITE_NOT_SUPPORTED, not INVALID_CONNECTION_INFO', async () => { + const { res, status, json } = mockRes(); + await controller.compareExecutionPlan( + mockReq({ + connectionInfo: PGLITE_CONN_WITH_SQL, + referenceQuery: REF_QUERY, + studentQuery: STUDENT_QUERY, + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + const message: string = json.mock.calls[0]?.[0]?.message ?? ''; + expect(message).not.toMatch(/invalid.*connection/i); + expect(message).toMatch(/pglite/i); + }); + }); +}); diff --git a/services/sql-assessment-service/test/query/auto-analyze-query-execution-controller.test.ts b/services/sql-assessment-service/test/query/auto-analyze-query-execution-controller.test.ts new file mode 100644 index 0000000..5c54154 --- /dev/null +++ b/services/sql-assessment-service/test/query/auto-analyze-query-execution-controller.test.ts @@ -0,0 +1,139 @@ +/** + * Tests for the auto-analyze feature in QueryExecutionController. + * + * When `sqlContent` is included in a PGlite `connectionInfo`, the controller + * must automatically create/replace the PGlite instance and register its + * schema — without the caller having to invoke `/api/database/analyze-database` + * first. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { Request, Response } from 'express'; +import { QueryExecutionController } from '../../src/query/query-execution-controller'; +import { QueryExecutionService } from '../../src/query/query-execution-service'; +import { DatabaseService } from '../../src/database/database-service'; +import { DatabaseAnalyzer } from '../../src/database/database-analyzer'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const SIMPLE_DDL = ` +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) +); +INSERT INTO products (name, price) VALUES ('Widget', 9.99), ('Gadget', 19.99); +`; + +const DB_ID = 'auto-analyze-query-db'; + +// --------------------------------------------------------------------------- +// Express mock helpers +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('QueryExecutionController — PGlite auto-analyze', () => { + let controller: QueryExecutionController; + + beforeEach(() => { + const databaseService = new DatabaseService(new DatabaseAnalyzer()); + controller = new QueryExecutionController( + new QueryExecutionService(), + databaseService, + ); + databaseMetadata.clear(); + pgliteInstances.clear(); + }); + + afterEach(async () => { + for (const db of pgliteInstances.values()) { + await db?.close?.(); + } + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + it('executes a query successfully when sqlContent is provided (no prior analyze-database call)', async () => { + const { res, status, json } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + query: 'SELECT * FROM products ORDER BY id', + }), + res, + ); + + expect(status).toHaveBeenCalledWith(200); + const jsonArg = json.mock.calls[0][0]; + expect(jsonArg).toHaveProperty('rows'); + expect(jsonArg.rows).toHaveLength(2); + expect(jsonArg.rows[0]).toHaveProperty('name', 'Widget'); + }); + + it('auto-registers the database in internal-memory after execute with sqlContent', async () => { + const { res } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: SIMPLE_DDL, + }, + query: 'SELECT 1 AS n', + }), + res, + ); + + expect(pgliteInstances.has(DB_ID)).toBe(true); + }); + + it('returns 400 when PGlite databaseId is absent and no prior registration', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { type: 'pglite' }, + query: 'SELECT 1', + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 500 when sqlContent is syntactically invalid SQL', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { + type: 'pglite', + databaseId: DB_ID, + sqlContent: 'INVALID SQL @@##', + }, + query: 'SELECT 1', + }), + res, + ); + expect(status).toHaveBeenCalledWith(500); + }); +}); diff --git a/services/sql-assessment-service/test/query/pglite-query-execution-controller.test.ts b/services/sql-assessment-service/test/query/pglite-query-execution-controller.test.ts new file mode 100644 index 0000000..de26da8 --- /dev/null +++ b/services/sql-assessment-service/test/query/pglite-query-execution-controller.test.ts @@ -0,0 +1,209 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import type { Request, Response } from 'express'; +import { PGlite } from '@electric-sql/pglite'; +import { QueryExecutionController } from '../../src/query/query-execution-controller'; +import { QueryExecutionService } from '../../src/query/query-execution-service'; +import { + databaseMetadata, + pgliteInstances, +} from '../../src/database/internal-memory'; +import { generatePGliteKey } from '../../src/shared/utils/database-utils'; + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const SIMPLE_DDL = ` +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(10,2) +); +INSERT INTO products (name, price) VALUES ('Widget', 9.99), ('Gadget', 19.99); +`; + +const DB_ID = 'test-pglite-db'; +const DB_KEY = generatePGliteKey(DB_ID); + +const VALID_PGLITE_CONN = { type: 'pglite' as const, databaseId: DB_ID }; + +// --------------------------------------------------------------------------- +// Express mock helpers +// --------------------------------------------------------------------------- + +function mockReq(body: unknown): Request { + return { body } as Request; +} + +function mockRes() { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + return { res: { status, json } as unknown as Response, status, json }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('QueryExecutionController — PGlite path', () => { + let controller: QueryExecutionController; + let pgliteDb: PGlite; + + beforeEach(async () => { + pgliteDb = new PGlite(); + await pgliteDb.exec(SIMPLE_DDL); + pgliteInstances.set(DB_ID, pgliteDb); + // Register metadata key so isDatabaseRegistered() returns true + databaseMetadata.set(DB_KEY, []); + + controller = new QueryExecutionController(new QueryExecutionService()); + }); + + afterEach(async () => { + await pgliteDb.close(); + pgliteInstances.clear(); + databaseMetadata.clear(); + }); + + // ----------------------------------------------------------------------- + // Request validation + // ----------------------------------------------------------------------- + + describe('executeQuery — PGlite request validation', () => { + it('returns 400 when databaseId is missing', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: { type: 'pglite' }, query: 'SELECT 1' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when the PGlite database has not been registered', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { type: 'pglite', databaseId: 'non-existent-db' }, + query: 'SELECT 1', + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is missing', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_PGLITE_CONN }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is an empty string', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_PGLITE_CONN, query: '' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is whitespace only', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_PGLITE_CONN, query: ' ' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + }); + + // ----------------------------------------------------------------------- + // Integration (real PGlite instance) + // ----------------------------------------------------------------------- + + describe('executeQuery — PGlite integration', () => { + it('returns 200 for a valid SELECT', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: 'SELECT * FROM products', + }), + res, + ); + expect(status).toHaveBeenCalledWith(200); + }); + + it('returns the seeded rows with correct rowCount', async () => { + const { res, json } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: 'SELECT * FROM products ORDER BY id', + }), + res, + ); + const result = json.mock.calls[0][0] as { + rows: unknown[]; + rowCount: number; + }; + expect(result.rowCount).toBe(2); + expect(result.rows).toHaveLength(2); + }); + + it('returns empty rows when WHERE matches nothing', async () => { + const { res, json } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: "SELECT * FROM products WHERE name = 'NonExistent'", + }), + res, + ); + const result = json.mock.calls[0][0] as { + rows: unknown[]; + rowCount: number; + }; + expect(result.rows).toHaveLength(0); + expect(result.rowCount).toBe(0); + }); + + it('returns 400 for an INSERT statement', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: "INSERT INTO products (name, price) VALUES ('X', 1.0)", + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 for a DELETE statement', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: 'DELETE FROM products', + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 500 when query references a non-existent table', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: VALID_PGLITE_CONN, + query: 'SELECT * FROM non_existent_table', + }), + res, + ); + expect(status).toHaveBeenCalledWith(500); + }); + }); +}); diff --git a/services/sql-assessment-service/test/query/query-execution-controller.test.ts b/services/sql-assessment-service/test/query/query-execution-controller.test.ts index 51b09f7..46c86dd 100644 --- a/services/sql-assessment-service/test/query/query-execution-controller.test.ts +++ b/services/sql-assessment-service/test/query/query-execution-controller.test.ts @@ -1,22 +1,59 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { Request, Response } from 'express'; +import { Request, Response, NextFunction } from 'express'; + +// --------------------------------------------------------------------------- +// Module-level mocks (hoisted by vitest) +// +// connectToDatabase – return true without touching a real PG server. +// DataSource – stub destroy() so the catch block does not throw on an +// uninitialised pool. +// The synchronous factory avoids async-hoisting edge cases in vitest v4. +// --------------------------------------------------------------------------- +vi.mock('../../src/shared/utils/database-utils', () => ({ + generateDatabaseKey: (host: string, port: number, schema: string) => + `${host}:${port}/${schema}`, + generatePGliteKey: (id: string) => `pglite:${id}`, + connectToDatabase: vi.fn().mockResolvedValue(true), + makeRowQueryFn: vi.fn(), + createQueryRunner: vi.fn(), + buildAliasMapFromTables: vi.fn(), +})); + +vi.mock('typeorm', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + DataSource: vi.fn(function (this: Record) { + this.initialize = vi.fn().mockResolvedValue(undefined); + this.destroy = vi.fn().mockResolvedValue(undefined); + }), + }; +}); import { QueryExecutionController } from '../../src/query/query-execution-controller'; -import { QueryExecutionService, QueryExecutionError } from '../../src/query/query-execution-service'; +import { + QueryExecutionService, + QueryExecutionError, +} from '../../src/query/query-execution-service'; import { databaseMetadata } from '../../src/database/internal-memory'; +import { generateDatabaseKey } from '../../src/shared/utils/database-utils'; // --------------------------------------------------------------------------- // Express mock helpers // --------------------------------------------------------------------------- function mockReq(body: unknown): Request { - return { body } as Request; + return { body } as Request; } -function mockRes(): { res: Response; status: ReturnType; json: ReturnType } { - const json = vi.fn().mockReturnThis(); - const status = vi.fn().mockReturnValue({ json }); - const res = { status, json } as unknown as Response; - return { res, status, json }; +function mockRes(): { + res: Response; + status: ReturnType; + json: ReturnType; +} { + const json = vi.fn().mockReturnThis(); + const status = vi.fn().mockReturnValue({ json }); + const res = { status, json } as unknown as Response; + return { res, status, json }; } // --------------------------------------------------------------------------- @@ -24,27 +61,31 @@ function mockRes(): { res: Response; status: ReturnType; json: Ret // --------------------------------------------------------------------------- const VALID_CONNECTION = { - type: 'postgres' as const, - host: 'localhost', - port: 5432, - username: 'user', - password: 'pass', - schema: 'northwind', + type: 'postgres' as const, + host: 'localhost', + port: 5432, + username: 'user', + password: 'pass', + schema: 'northwind', }; -const DB_KEY = `localhost5432northwind`; +const DB_KEY = generateDatabaseKey( + VALID_CONNECTION.host, + VALID_CONNECTION.port, + VALID_CONNECTION.schema, +); let controller: QueryExecutionController; let serviceMock: QueryExecutionService; beforeEach(() => { - // Register the fake database key so isDatabaseRegistered() returns true. - databaseMetadata.set(DB_KEY, []); + // Register the fake database key so isDatabaseRegistered() returns true. + databaseMetadata.set(DB_KEY, []); - // Replace service with a spy-wrapped instance; individual tests override - // executeQuery as needed. - serviceMock = new QueryExecutionService(); - controller = new QueryExecutionController(serviceMock); + // Replace service with a spy-wrapped instance; individual tests override + // executeQuery as needed. + serviceMock = new QueryExecutionService(); + controller = new QueryExecutionController(serviceMock); }); // --------------------------------------------------------------------------- @@ -52,152 +93,191 @@ beforeEach(() => { // --------------------------------------------------------------------------- describe('QueryExecutionController', () => { - describe('POST /api/query/execute — request validation', () => { - - it('returns 400 when connectionInfo is missing', async () => { - const { res, status } = mockRes(); - await controller.executeQuery(mockReq({ query: 'SELECT 1' }), res); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when query field is missing', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ connectionInfo: VALID_CONNECTION }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when query is an empty string', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ connectionInfo: VALID_CONNECTION, query: '' }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when query is whitespace only', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ connectionInfo: VALID_CONNECTION, query: ' ' }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when query is not a string (number)', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ connectionInfo: VALID_CONNECTION, query: 42 }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when host is missing from connectionInfo', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ - connectionInfo: { ...VALID_CONNECTION, host: undefined }, - query: 'SELECT 1', - }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when schema is missing from connectionInfo', async () => { - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ - connectionInfo: { ...VALID_CONNECTION, schema: undefined }, - query: 'SELECT 1', - }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - it('returns 400 when the database has not been registered', async () => { - databaseMetadata.delete(DB_KEY); - const { res, status } = mockRes(); - await controller.executeQuery( - mockReq({ connectionInfo: VALID_CONNECTION, query: 'SELECT 1' }), - res - ); - expect(status).toHaveBeenCalledWith(400); - }); - - }); - - // ------------------------------------------------------------------------- - // Controller service-error mapping - // ------------------------------------------------------------------------- - - describe('POST /api/query/execute — service error mapping', () => { - - it('returns 400 with EMPTY_QUERY code when service throws EMPTY_QUERY', async () => { - vi.spyOn(serviceMock, 'executeQuery').mockRejectedValueOnce( - new QueryExecutionError('Query must not be empty.', 'EMPTY_QUERY') - ); - const { res, status, json } = mockRes(); - // Bypass DB connection by pre-seeding metadata and mocking connectToDatabase - // The connection will fail because there is no real PG server, so we spy on - // the service directly and test the error→status mapping logic. - // - // We need connectToDatabase to succeed — mock at the module level via - // the service spy path: simulate that the service itself is called - // (meaning connection succeeded) and it throws. - // - // For isolation we test the mapping through a separate helper. - const error = new QueryExecutionError('Query must not be empty.', 'EMPTY_QUERY'); - expect(error.code).toBe('EMPTY_QUERY'); - }); - - it('returns 400 for NON_SELECT errors reported by the service', async () => { - const error = new QueryExecutionError('Only SELECT queries are permitted.', 'NON_SELECT'); - expect(error.code).toBe('NON_SELECT'); - // The controller maps client-side codes (NON_SELECT) to 400 - const clientCodes = ['EMPTY_QUERY', 'PARSE_ERROR', 'MULTIPLE_STATEMENTS', 'NON_SELECT']; - expect(clientCodes).toContain(error.code); - }); - - it('returns 500 for EXECUTION_FAILED errors reported by the service', async () => { - const error = new QueryExecutionError('Query execution failed.', 'EXECUTION_FAILED'); - // EXECUTION_FAILED is not in the client-codes list → maps to 500 - const clientCodes = ['EMPTY_QUERY', 'PARSE_ERROR', 'MULTIPLE_STATEMENTS', 'NON_SELECT']; - expect(clientCodes).not.toContain(error.code); - }); - - }); - - // ------------------------------------------------------------------------- - // QueryExecutionError class contract - // ------------------------------------------------------------------------- - - describe('QueryExecutionError', () => { - - it('has name "QueryExecutionError"', () => { - const err = new QueryExecutionError('msg', 'PARSE_ERROR'); - expect(err.name).toBe('QueryExecutionError'); - }); - - it('exposes the code on the instance', () => { - const err = new QueryExecutionError('msg', 'NON_SELECT'); - expect(err.code).toBe('NON_SELECT'); - }); - - it('is an instance of Error', () => { - const err = new QueryExecutionError('msg', 'EMPTY_QUERY'); - expect(err).toBeInstanceOf(Error); - }); - - it('preserves the message', () => { - const err = new QueryExecutionError('Only SELECT queries are permitted.', 'NON_SELECT'); - expect(err.message).toBe('Only SELECT queries are permitted.'); - }); - - }); + describe('POST /api/query/execute — request validation', () => { + it('returns 400 when connectionInfo is missing', async () => { + const { res, status } = mockRes(); + await controller.executeQuery(mockReq({ query: 'SELECT 1' }), res); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query field is missing', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is an empty string', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION, query: '' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is whitespace only', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION, query: ' ' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when query is not a string (number)', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION, query: 42 }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when host is missing from connectionInfo', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { ...VALID_CONNECTION, host: undefined }, + query: 'SELECT 1', + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when schema is missing from connectionInfo', async () => { + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ + connectionInfo: { ...VALID_CONNECTION, schema: undefined }, + query: 'SELECT 1', + }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + + it('returns 400 when the database has not been registered', async () => { + databaseMetadata.delete(DB_KEY); + const { res, status } = mockRes(); + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION, query: 'SELECT 1' }), + res, + ); + expect(status).toHaveBeenCalledWith(400); + }); + }); + + // ------------------------------------------------------------------------- + // Controller service-error mapping + // ------------------------------------------------------------------------- + + describe('POST /api/query/execute — service error mapping', () => { + it('returns 400 with EMPTY_QUERY code when service throws EMPTY_QUERY', async () => { + vi.spyOn(serviceMock, 'executeQuery').mockRejectedValueOnce( + new QueryExecutionError('Query must not be empty.', 'EMPTY_QUERY'), + ); + const { res, status, json } = mockRes(); + + await controller.executeQuery( + mockReq({ connectionInfo: VALID_CONNECTION, query: 'SELECT 1' }), + res, + ); + + expect(status).toHaveBeenCalledWith(400); + expect(json).toHaveBeenCalledWith( + expect.objectContaining({ code: 'EMPTY_QUERY' }), + ); + }); + + it('returns 400 for NON_SELECT errors reported by the service', async () => { + const error = new QueryExecutionError( + 'Only SELECT queries are permitted.', + 'NON_SELECT', + ); + expect(error.code).toBe('NON_SELECT'); + // The controller maps client-side codes (NON_SELECT) to 400 + const clientCodes = [ + 'EMPTY_QUERY', + 'PARSE_ERROR', + 'MULTIPLE_STATEMENTS', + 'NON_SELECT', + ]; + expect(clientCodes).toContain(error.code); + }); + + it('returns 500 for EXECUTION_FAILED errors reported by the service', async () => { + const error = new QueryExecutionError( + 'Query execution failed.', + 'EXECUTION_FAILED', + ); + // EXECUTION_FAILED is not in the client-codes list → maps to 500 + const clientCodes = [ + 'EMPTY_QUERY', + 'PARSE_ERROR', + 'MULTIPLE_STATEMENTS', + 'NON_SELECT', + ]; + expect(clientCodes).not.toContain(error.code); + }); + }); + + // ------------------------------------------------------------------------- + // QueryExecutionError class contract + // ------------------------------------------------------------------------- + + describe('QueryExecutionError', () => { + it('has name "QueryExecutionError"', () => { + const err = new QueryExecutionError('msg', 'PARSE_ERROR'); + expect(err.name).toBe('QueryExecutionError'); + }); + + it('exposes the code on the instance', () => { + const err = new QueryExecutionError('msg', 'NON_SELECT'); + expect(err.code).toBe('NON_SELECT'); + }); + + it('is an instance of Error', () => { + const err = new QueryExecutionError('msg', 'EMPTY_QUERY'); + expect(err).toBeInstanceOf(Error); + }); + + it('preserves the message', () => { + const err = new QueryExecutionError( + 'Only SELECT queries are permitted.', + 'NON_SELECT', + ); + expect(err.message).toBe('Only SELECT queries are permitted.'); + }); + }); + + // ------------------------------------------------------------------------- + // Route-handler error propagation + // ------------------------------------------------------------------------- + + describe('POST /execute route handler', () => { + it('forwards unexpected executeQuery rejections to next()', async () => { + const boom = new Error('unexpected DB crash'); + vi.spyOn(controller, 'executeQuery').mockRejectedValueOnce(boom); + + const req = mockReq({}) as Request; + const { res } = mockRes(); + const next = vi.fn() as unknown as NextFunction; + + // Resolve as soon as next() is invoked so we can await deterministically. + const nextCalled = new Promise((resolve) => { + (next as ReturnType).mockImplementation(() => resolve()); + }); + + // Invoke the handler registered by initializeRoutes() directly. + const [routeLayer] = (controller.router as any).stack; + routeLayer.route.stack[0].handle(req, res, next); + await nextCalled; + + expect(next).toHaveBeenCalledWith(boom); + }); + }); }); diff --git a/services/sql-assessment-service/vitest.config.ts b/services/sql-assessment-service/vitest.config.ts index 67e5c17..dcb2f5b 100644 --- a/services/sql-assessment-service/vitest.config.ts +++ b/services/sql-assessment-service/vitest.config.ts @@ -1,20 +1,22 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - test: { - // Look for tests only in the /test directory - include: ['test/**/*.test.ts'], - // Run global setup before any test file - setupFiles: ['test/setup.ts'], - // Node environment — no DOM needed for a backend service - environment: 'node', - // Show a detailed per-test reporter in the terminal - reporters: ['verbose'], - coverage: { - provider: 'v8', - include: ['src/**/*.ts'], - exclude: ['src/index.ts', 'src/test-page/**'], - reporter: ['text', 'lcov'], - }, - }, + test: { + // Look for tests only in the /test directory + include: ['test/**/*.test.ts'], + // Run global setup before any test file + setupFiles: ['test/setup.ts'], + // Node environment — no DOM needed for a backend service + environment: 'node', + // Show a detailed per-test reporter in the terminal + reporters: ['verbose'], + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + exclude: ['src/index.ts', 'src/test-page/**'], + reporter: ['text', 'lcov'], + }, + testTimeout: 8000, + hookTimeout: 8000, + }, });